diff options
-rw-r--r-- | actions/avatarsettings.php | 39 | ||||
-rw-r--r-- | actions/joingroup.php | 2 | ||||
-rw-r--r-- | actions/showgroup.php | 6 | ||||
-rwxr-xr-x | classes/Group_inbox.php | 2 | ||||
-rwxr-xr-x | classes/Group_member.php | 5 | ||||
-rw-r--r-- | classes/Notice.php | 177 | ||||
-rw-r--r-- | classes/Profile.php | 34 | ||||
-rw-r--r-- | classes/User.php | 25 | ||||
-rw-r--r-- | lib/imagefile.php | 105 | ||||
-rw-r--r-- | lib/util.php | 98 |
10 files changed, 348 insertions, 145 deletions
diff --git a/actions/avatarsettings.php b/actions/avatarsettings.php index ffbeb5486..2c7af9b7a 100644 --- a/actions/avatarsettings.php +++ b/actions/avatarsettings.php @@ -205,37 +205,10 @@ class AvatarsettingsAction extends AccountSettingsAction function uploadAvatar() { - switch ($_FILES['avatarfile']['error']) { - case UPLOAD_ERR_OK: // success, jump out - break; - case UPLOAD_ERR_INI_SIZE: - case UPLOAD_ERR_FORM_SIZE: - $this->showForm(_('That file is too big.')); - return; - case UPLOAD_ERR_PARTIAL: - @unlink($_FILES['avatarfile']['tmp_name']); - $this->showForm(_('Partial upload.')); - return; - default: - $this->showForm(_('System error uploading file.')); - return; - } - - $info = @getimagesize($_FILES['avatarfile']['tmp_name']); - - if (!$info) { - @unlink($_FILES['avatarfile']['tmp_name']); - $this->showForm(_('Not an image or corrupt file.')); - return; - } - - switch ($info[2]) { - case IMAGETYPE_GIF: - case IMAGETYPE_JPEG: - case IMAGETYPE_PNG: - break; - default: - $this->showForm(_('Unsupported image file format.')); + try { + $imagefile = ImageFile::fromUpload('avatarfile'); + } catch (Exception $e) { + $this->showForm($e->getMessage()); return; } @@ -243,13 +216,13 @@ class AvatarsettingsAction extends AccountSettingsAction $profile = $user->getProfile(); - if ($profile->setOriginal($_FILES['avatarfile']['tmp_name'])) { + if ($profile->setOriginal($imagefile->filename)) { $this->showForm(_('Avatar updated.'), true); } else { $this->showForm(_('Failed updating avatar.')); } - @unlink($_FILES['avatarfile']['tmp_name']); + $imagefile->unlink(); } /** diff --git a/actions/joingroup.php b/actions/joingroup.php index 45470f088..1888ecdab 100644 --- a/actions/joingroup.php +++ b/actions/joingroup.php @@ -91,7 +91,7 @@ class JoingroupAction extends Action $cur = common_current_user(); - if ($cur->isMember($group)) { + if ($cur->isMember($this->group)) { $this->clientError(_('You are already a member of that group'), 403); return false; } diff --git a/actions/showgroup.php b/actions/showgroup.php index 0a499aff9..534043c24 100644 --- a/actions/showgroup.php +++ b/actions/showgroup.php @@ -146,6 +146,12 @@ class ShowgroupAction extends Action $this->showPage(); } + /** + * Local menu + * + * @return void + */ + function showLocalNav() { $nav = new GroupNav($this, $this->group); diff --git a/classes/Group_inbox.php b/classes/Group_inbox.php index 826889636..523506ec5 100755 --- a/classes/Group_inbox.php +++ b/classes/Group_inbox.php @@ -2,7 +2,7 @@ /** * Table Definition for group_inbox */ -require_once 'classes/Memcached_DataObject'; +require_once 'classes/Memcached_DataObject.php'; class Group_inbox extends Memcached_DataObject { diff --git a/classes/Group_member.php b/classes/Group_member.php index 32243fe45..a2ea00e81 100755 --- a/classes/Group_member.php +++ b/classes/Group_member.php @@ -21,4 +21,9 @@ class Group_member extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE + + function &pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('Group_member', $kv); + } } diff --git a/classes/Notice.php b/classes/Notice.php index 3eb653066..5fa078205 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -156,8 +156,9 @@ class Notice extends Memcached_DataObject # XXX: do we need to change this for remote users? - common_save_replies($notice); + $notice->saveReplies(); $notice->saveTags(); + $notice->saveGroups(); # Clear the cache for subscribed users, so they'll update at next request # XXX: someone clever could prepend instead of clearing the cache @@ -195,6 +196,36 @@ class Notice extends Memcached_DataObject $this->blowRepliesCache($blowLast); $this->blowPublicCache($blowLast); $this->blowTagCache($blowLast); + $this->blowGroupCache($blowLast); + } + + function blowGroupCache($blowLast=false) + { + $cache = common_memcache(); + if ($cache) { + $group_inbox = new Group_inbox(); + $group_inbox->notice_id = $this->id; + if ($group_inbox->find()) { + while ($group_inbox->fetch()) { + $cache->delete(common_cache_key('group:notices:'.$group_inbox->group_id)); + if ($blowLast) { + $cache->delete(common_cache_key('group:notices:'.$group_inbox->group_id.';last')); + } + $member = new Group_member(); + $member->group_id = $group_inbox->group_id; + if ($member->find()) { + while ($member->fetch()) { + $cache->delete(common_cache_key('user:notices_with_friends:' . $member->profile_id)); + if ($blowLast) { + $cache->delete(common_cache_key('user:notices_with_friends:' . $member->profile_id . ';last')); + } + } + } + } + } + $group_inbox->free(); + unset($group_inbox); + } } function blowTagCache($blowLast=false) @@ -549,5 +580,147 @@ class Notice extends Memcached_DataObject return; } -} + function saveGroups() + { + $enabled = common_config('inboxes', 'enabled'); + if ($enabled !== true && $enabled !== 'transitional') { + return; + } + + /* extract all !group */ + $count = preg_match_all('/(?:^|\s)!([A-Za-z0-9]{1,64})/', + strtolower($this->content), + $match); + if (!$count) { + return true; + } + + $profile = $this->getProfile(); + + /* Add them to the database */ + + foreach (array_unique($match[1]) as $nickname) { + /* XXX: remote groups. */ + $group = User_group::staticGet('nickname', $nickname); + + if (!$group) { + continue; + } + + if ($profile->isMember($group)) { + + $gi = new Group_inbox(); + + $gi->group_id = $group->id; + $gi->notice_id = $this->id; + $gi->created = common_sql_now(); + $result = $gi->insert(); + + if (!$result) { + common_log_db_error($gi, 'INSERT', __FILE__); + } + + // FIXME: do this in an offline daemon + + $inbox = new Notice_inbox(); + $qry = 'INSERT INTO notice_inbox (user_id, notice_id, created, source) ' . + 'SELECT user.id, ' . $this->id . ', "' . $this->created . '", 2 ' . + 'FROM user JOIN group_member ON user.id = group_member.profile_id ' . + 'WHERE group_member.group_id = ' . $group->id . ' ' . + 'AND NOT EXISTS (SELECT user_id, notice_id ' . + 'FROM notice_inbox ' . + 'WHERE user_id = user.id ' . + 'AND notice_id = ' . $this->id . ' )'; + if ($enabled === 'transitional') { + $qry .= ' AND user.inboxed = 1'; + } + $result = $inbox->query($qry); + } + } + } + + function saveReplies() + { + // Alternative reply format + $tname = false; + if (preg_match('/^T ([A-Z0-9]{1,64}) /', $this->content, $match)) { + $tname = $match[1]; + } + // extract all @messages + $cnt = preg_match_all('/(?:^|\s)@([a-z0-9]{1,64})/', $this->content, $match); + + $names = array(); + + if ($cnt || $tname) { + // XXX: is there another way to make an array copy? + $names = ($tname) ? array_unique(array_merge(array(strtolower($tname)), $match[1])) : array_unique($match[1]); + } + + $sender = Profile::staticGet($this->profile_id); + + $replied = array(); + + // store replied only for first @ (what user/notice what the reply directed, + // we assume first @ is it) + + for ($i=0; $i<count($names); $i++) { + $nickname = $names[$i]; + $recipient = common_relative_profile($sender, $nickname, $this->created); + if (!$recipient) { + continue; + } + if ($i == 0 && ($recipient->id != $sender->id) && !$this->reply_to) { // Don't save reply to self + $reply_for = $recipient; + $recipient_notice = $reply_for->getCurrentNotice(); + if ($recipient_notice) { + $orig = clone($this); + $this->reply_to = $recipient_notice->id; + $this->update($orig); + } + } + // Don't save replies from blocked profile to local user + $recipient_user = User::staticGet('id', $recipient->id); + if ($recipient_user && $recipient_user->hasBlocked($sender)) { + continue; + } + $reply = new Reply(); + $reply->notice_id = $this->id; + $reply->profile_id = $recipient->id; + $id = $reply->insert(); + if (!$id) { + $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError'); + common_log(LOG_ERR, 'DB error inserting reply: ' . $last_error->message); + common_server_error(sprintf(_('DB error inserting reply: %s'), $last_error->message)); + return; + } else { + $replied[$recipient->id] = 1; + } + } + + // Hash format replies, too + $cnt = preg_match_all('/(?:^|\s)@#([a-z0-9]{1,64})/', $this->content, $match); + if ($cnt) { + foreach ($match[1] as $tag) { + $tagged = Profile_tag::getTagged($sender->id, $tag); + foreach ($tagged as $t) { + if (!$replied[$t->id]) { + // Don't save replies from blocked profile to local user + $t_user = User::staticGet('id', $t->id); + if ($t_user && $t_user->hasBlocked($sender)) { + continue; + } + $reply = new Reply(); + $reply->notice_id = $this->id; + $reply->profile_id = $t->id; + $id = $reply->insert(); + if (!$id) { + common_log_db_error($reply, 'INSERT', __FILE__); + return; + } + } + } + } + } + } +} diff --git a/classes/Profile.php b/classes/Profile.php index 31bdf71d5..ab5a48e57 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -121,7 +121,7 @@ class Profile extends Memcached_DataObject return $avatar; } - function crop_avatars($x, $y, $w, $h) + function crop_avatars($x, $y, $w, $h) { $avatar = $this->getOriginalAvatar(); @@ -139,7 +139,7 @@ class Profile extends Memcached_DataObject return true; } - function delete_avatars($original=true) + function delete_avatars($original=true) { $avatar = new Avatar(); $avatar->profile_id = $this->id; @@ -187,4 +187,34 @@ class Profile extends Memcached_DataObject 'profile:notices:'.$this->id, $offset, $limit, $since_id, $before_id); } + + function isMember($group) + { + $mem = new Group_member(); + + $mem->group_id = $group->id; + $mem->profile_id = $this->id; + + if ($mem->find()) { + return true; + } else { + return false; + } + } + + function isAdmin($group) + { + $mem = new Group_member(); + + $mem->group_id = $group->id; + $mem->profile_id = $this->id; + $mem->is_admin = 1; + + if ($mem->find()) { + return true; + } else { + return false; + } + } + } diff --git a/classes/User.php b/classes/User.php index 9a1ebddc4..5dadd6b44 100644 --- a/classes/User.php +++ b/classes/User.php @@ -494,31 +494,14 @@ class User extends Memcached_DataObject function isMember($group) { - $mem = new Group_member(); - - $mem->group_id = $group->id; - $mem->profile_id = $this->id; - - if ($mem->find()) { - return true; - } else { - return false; - } + $profile = $this->getProfile(); + return $profile->isMember($group); } function isAdmin($group) { - $mem = new Group_member(); - - $mem->group_id = $group->id; - $mem->profile_id = $this->id; - $mem->is_admin = 1; - - if ($mem->find()) { - return true; - } else { - return false; - } + $profile = $this->getProfile(); + return $profile->isAdmin($group); } function getGroups($offset=0, $limit=null) diff --git a/lib/imagefile.php b/lib/imagefile.php new file mode 100644 index 000000000..f15a72c76 --- /dev/null +++ b/lib/imagefile.php @@ -0,0 +1,105 @@ +<?php +/** + * Laconica, the distributed open-source microblogging tool + * + * Abstraction for an image file + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category Image + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @author Zach Copley <zach@controlyourself.ca> + * @copyright 2008-2009 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +if (!defined('LACONICA')) { + exit(1); +} + +/** + * A wrapper on uploaded files + * + * Makes it slightly easier to accept an image file from upload. + * + * @category Image + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @author Zach Copley <zach@controlyourself.ca> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +class ImageFile +{ + var $filename = null; + var $type = null; + + function __construct($filename=null, $type=null) + { + $this->filename = $filename; + $this->type = $type; + } + + static function fromUpload($param='upload') + { + switch ($_FILES[$param]['error']) { + case UPLOAD_ERR_OK: // success, jump out + break; + case UPLOAD_ERR_INI_SIZE: + case UPLOAD_ERR_FORM_SIZE: + throw new Exception(_('That file is too big.')); + return; + case UPLOAD_ERR_PARTIAL: + @unlink($_FILES[$param]['tmp_name']); + throw new Exception(_('Partial upload.')); + return; + default: + throw new Exception(_('System error uploading file.')); + return; + } + + $imagefile = new ImageFile($_FILES[$param]['tmp_name']); + $info = @getimagesize($imagefile->filename); + + if (!$info) { + @unlink($imagefile->filename); + throw new Exception(_('Not an image or corrupt file.')); + return; + } + + switch ($info[2]) { + case IMAGETYPE_GIF: + case IMAGETYPE_JPEG: + case IMAGETYPE_PNG: + $imagefile->type = $info[2]; + break; + default: + @unlink($imagefile->filename); + throw new Exception(_('Unsupported image file format.')); + return; + } + + return $imagefile; + } + + function unlink() + { + @unlink($this->filename); + } +}
\ No newline at end of file diff --git a/lib/util.php b/lib/util.php index 61f5d6c16..d30a56c77 100644 --- a/lib/util.php +++ b/lib/util.php @@ -424,6 +424,7 @@ function common_render_content($text, $notice) $r = preg_replace('/(^|\s+)@([A-Za-z0-9]{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r); $r = preg_replace('/^T ([A-Z0-9]{1,64}) /e', "'T '.common_at_link($id, '\\1').' '", $r); $r = preg_replace('/(^|\s+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r); + $r = preg_replace('/(^|\s)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r); return $r; } @@ -595,6 +596,17 @@ function common_at_link($sender_id, $nickname) } } +function common_group_link($sender_id, $nickname) +{ + $sender = Profile::staticGet($sender_id); + $group = User_group::staticGet('nickname', common_canonical_nickname($nickname)); + if ($group && $sender->isMember($group)) { + return '<span class="vcard"><a href="'.htmlspecialchars($group->permalink()).'" class="url"><span class="fn nickname">'.$nickname.'</span></a></span>'; + } else { + return $nickname; + } +} + function common_at_hash_link($sender_id, $tag) { $user = User::staticGet($sender_id); @@ -1052,95 +1064,11 @@ function common_redirect($url, $code=307) $xo->startXML('a', '-//W3C//DTD XHTML 1.0 Strict//EN', 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'); - $xo->output('a', array('href' => $url), $url); + $xo->element('a', array('href' => $url), $url); $xo->endXML(); exit; } -function common_save_replies($notice) -{ - // Alternative reply format - $tname = false; - if (preg_match('/^T ([A-Z0-9]{1,64}) /', $notice->content, $match)) { - $tname = $match[1]; - } - // extract all @messages - $cnt = preg_match_all('/(?:^|\s)@([a-z0-9]{1,64})/', $notice->content, $match); - - $names = array(); - - if ($cnt || $tname) { - // XXX: is there another way to make an array copy? - $names = ($tname) ? array_unique(array_merge(array(strtolower($tname)), $match[1])) : array_unique($match[1]); - } - - $sender = Profile::staticGet($notice->profile_id); - - $replied = array(); - - // store replied only for first @ (what user/notice what the reply directed, - // we assume first @ is it) - - for ($i=0; $i<count($names); $i++) { - $nickname = $names[$i]; - $recipient = common_relative_profile($sender, $nickname, $notice->created); - if (!$recipient) { - continue; - } - if ($i == 0 && ($recipient->id != $sender->id) && !$notice->reply_to) { // Don't save reply to self - $reply_for = $recipient; - $recipient_notice = $reply_for->getCurrentNotice(); - if ($recipient_notice) { - $orig = clone($notice); - $notice->reply_to = $recipient_notice->id; - $notice->update($orig); - } - } - // Don't save replies from blocked profile to local user - $recipient_user = User::staticGet('id', $recipient->id); - if ($recipient_user && $recipient_user->hasBlocked($sender)) { - continue; - } - $reply = new Reply(); - $reply->notice_id = $notice->id; - $reply->profile_id = $recipient->id; - $id = $reply->insert(); - if (!$id) { - $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError'); - common_log(LOG_ERR, 'DB error inserting reply: ' . $last_error->message); - common_server_error(sprintf(_('DB error inserting reply: %s'), $last_error->message)); - return; - } else { - $replied[$recipient->id] = 1; - } - } - - // Hash format replies, too - $cnt = preg_match_all('/(?:^|\s)@#([a-z0-9]{1,64})/', $notice->content, $match); - if ($cnt) { - foreach ($match[1] as $tag) { - $tagged = Profile_tag::getTagged($sender->id, $tag); - foreach ($tagged as $t) { - if (!$replied[$t->id]) { - // Don't save replies from blocked profile to local user - $t_user = User::staticGet('id', $t->id); - if ($t_user && $t_user->hasBlocked($sender)) { - continue; - } - $reply = new Reply(); - $reply->notice_id = $notice->id; - $reply->profile_id = $t->id; - $id = $reply->insert(); - if (!$id) { - common_log_db_error($reply, 'INSERT', __FILE__); - return; - } - } - } - } - } -} - function common_broadcast_notice($notice, $remote=false) { |