diff options
author | Brion Vibber <brion@pobox.com> | 2010-03-22 13:56:16 -0700 |
---|---|---|
committer | Brion Vibber <brion@pobox.com> | 2010-03-22 13:56:16 -0700 |
commit | e89908f26140c217e01b2f8f755712f38f3935f3 (patch) | |
tree | 6b241fc2e33f3528cf48b415ef67906826b02e24 /classes | |
parent | 714d920faea302b55857cc3bec4e9e6160ea136a (diff) | |
parent | eb563937df921e5fc67ca0c87e229feb2907fd19 (diff) |
Merge branch '0.9.x' of git@gitorious.org:statusnet/mainline into 1.0.x
Conflicts:
lib/channel.php
scripts/imdaemon.php
Diffstat (limited to 'classes')
-rw-r--r-- | classes/File.php | 35 | ||||
-rw-r--r-- | classes/File_oembed.php | 6 | ||||
-rw-r--r-- | classes/File_redirection.php | 111 | ||||
-rw-r--r-- | classes/Foreign_user.php | 1 | ||||
-rw-r--r-- | classes/Notice.php | 26 | ||||
-rw-r--r-- | classes/Profile.php | 15 | ||||
-rw-r--r-- | classes/Profile_role.php | 1 | ||||
-rw-r--r-- | classes/Safe_DataObject.php | 43 | ||||
-rw-r--r-- | classes/Subscription.php | 72 | ||||
-rw-r--r-- | classes/User.php | 64 | ||||
-rw-r--r-- | classes/User_group.php | 17 |
11 files changed, 274 insertions, 117 deletions
diff --git a/classes/File.php b/classes/File.php index 4ecd3b959..33273bbdc 100644 --- a/classes/File.php +++ b/classes/File.php @@ -67,7 +67,14 @@ class File extends Memcached_DataObject return $att; } - function saveNew($redir_data, $given_url) { + /** + * Save a new file record. + * + * @param array $redir_data lookup data eg from File_redirection::where() + * @param string $given_url + * @return File + */ + function saveNew(array $redir_data, $given_url) { $x = new File; $x->url = $given_url; if (!empty($redir_data['protected'])) $x->protected = $redir_data['protected']; @@ -77,19 +84,36 @@ class File extends Memcached_DataObject if (isset($redir_data['time']) && $redir_data['time'] > 0) $x->date = intval($redir_data['time']); $file_id = $x->insert(); + $x->saveOembed($redir_data, $given_url); + return $x; + } + + /** + * Save embedding information for this file, if applicable. + * + * Normally this won't need to be called manually, as File::saveNew() + * takes care of it. + * + * @param array $redir_data lookup data eg from File_redirection::where() + * @param string $given_url + * @return boolean success + */ + public function saveOembed($redir_data, $given_url) + { if (isset($redir_data['type']) && (('text/html' === substr($redir_data['type'], 0, 9) || 'application/xhtml+xml' === substr($redir_data['type'], 0, 21))) && ($oembed_data = File_oembed::_getOembed($given_url))) { - $fo = File_oembed::staticGet('file_id', $file_id); + $fo = File_oembed::staticGet('file_id', $this->id); if (empty($fo)) { - File_oembed::saveNew($oembed_data, $file_id); + File_oembed::saveNew($oembed_data, $this->id); + return true; } else { common_log(LOG_WARNING, "Strangely, a File_oembed object exists for new file $file_id", __FILE__); } } - return $x; + return false; } function processNew($given_url, $notice_id=null) { @@ -105,6 +129,7 @@ class File extends Memcached_DataObject $redir_url = $redir_data['url']; } elseif (is_string($redir_data)) { $redir_url = $redir_data; + $redir_data = array(); } else { throw new ServerException("Can't process url '$given_url'"); } @@ -260,7 +285,7 @@ class File extends Memcached_DataObject $enclosure->mimetype=$this->mimetype; if(! isset($this->filename)){ - $notEnclosureMimeTypes = array('text/html','application/xhtml+xml'); + $notEnclosureMimeTypes = array(null,'text/html','application/xhtml+xml'); $mimetype = strtolower($this->mimetype); $semicolon = strpos($mimetype,';'); if($semicolon){ diff --git a/classes/File_oembed.php b/classes/File_oembed.php index 11f160718..041b44740 100644 --- a/classes/File_oembed.php +++ b/classes/File_oembed.php @@ -81,6 +81,12 @@ class File_oembed extends Memcached_DataObject } } + /** + * Save embedding info for a new file. + * + * @param object $data Services_oEmbed_Object_* + * @param int $file_id + */ function saveNew($data, $file_id) { $file_oembed = new File_oembed; $file_oembed->file_id = $file_id; diff --git a/classes/File_redirection.php b/classes/File_redirection.php index 08a6e8d8b..f128b3e07 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -58,24 +58,30 @@ class File_redirection extends Memcached_DataObject return $request; } - function _redirectWhere_imp($short_url, $redirs = 10, $protected = false) { + /** + * Check if this URL is a redirect and return redir info. + * + * Most code should call File_redirection::where instead, to check if we + * already know that redirection and avoid extra hits to the web. + * + * The URL is hit and any redirects are followed, up to 10 levels or until + * a protected URL is reached. + * + * @param string $in_url + * @return mixed one of: + * string - target URL, if this is a direct link or can't be followed + * array - redirect info if this is an *unknown* redirect: + * associative array with the following elements: + * code: HTTP status code + * redirects: count of redirects followed + * url: URL string of final target + * type (optional): MIME type from Content-Type header + * size (optional): byte size from Content-Length header + * time (optional): timestamp from Last-Modified header + */ + public function lookupWhere($short_url, $redirs = 10, $protected = false) { if ($redirs < 0) return false; - // let's see if we know this... - $a = File::staticGet('url', $short_url); - - if (!empty($a)) { - // this is a direct link to $a->url - return $a->url; - } else { - $b = File_redirection::staticGet('url', $short_url); - if (!empty($b)) { - // this is a redirect to $b->file_id - $a = File::staticGet('id', $b->file_id); - return $a->url; - } - } - if(strpos($short_url,'://') === false){ return $short_url; } @@ -93,12 +99,13 @@ class File_redirection extends Memcached_DataObject } } catch (Exception $e) { // Invalid URL or failure to reach server + common_log(LOG_ERR, "Error while following redirects for $short_url: " . $e->getMessage()); return $short_url; } if ($response->getRedirectCount() && File::isProtected($response->getUrl())) { // Bump back up the redirect chain until we find a non-protected URL - return self::_redirectWhere_imp($short_url, $response->getRedirectCount() - 1, true); + return self::lookupWhere($short_url, $response->getRedirectCount() - 1, true); } $ret = array('code' => $response->getStatus() @@ -115,11 +122,60 @@ class File_redirection extends Memcached_DataObject return $ret; } - function where($in_url) { - $ret = File_redirection::_redirectWhere_imp($in_url); + /** + * Check if this URL is a redirect and return redir info. + * If a File record is present for this URL, it is not considered a redirect. + * If a File_redirection record is present for this URL, the recorded target is returned. + * + * If no File or File_redirect record is present, the URL is hit and any + * redirects are followed, up to 10 levels or until a protected URL is + * reached. + * + * @param string $in_url + * @return mixed one of: + * string - target URL, if this is a direct link or a known redirect + * array - redirect info if this is an *unknown* redirect: + * associative array with the following elements: + * code: HTTP status code + * redirects: count of redirects followed + * url: URL string of final target + * type (optional): MIME type from Content-Type header + * size (optional): byte size from Content-Length header + * time (optional): timestamp from Last-Modified header + */ + public function where($in_url) { + // let's see if we know this... + $a = File::staticGet('url', $in_url); + + if (!empty($a)) { + // this is a direct link to $a->url + return $a->url; + } else { + $b = File_redirection::staticGet('url', $in_url); + if (!empty($b)) { + // this is a redirect to $b->file_id + $a = File::staticGet('id', $b->file_id); + return $a->url; + } + } + + $ret = File_redirection::lookupWhere($in_url); return $ret; } + /** + * Shorten a URL with the current user's configured shortening + * options, if applicable. + * + * If it cannot be shortened or the "short" URL is longer than the + * original, the original is returned. + * + * If the referenced item has not been seen before, embedding data + * may be saved. + * + * @param string $long_url + * @return string + */ function makeShort($long_url) { $canon = File_redirection::_canonUrl($long_url); @@ -141,11 +197,20 @@ class File_redirection extends Memcached_DataObject // store it $file = File::staticGet('url', $long_url); if (empty($file)) { + // Check if the target URL is itself a redirect... $redir_data = File_redirection::where($long_url); - $file = File::saveNew($redir_data, $long_url); - $file_id = $file->id; - if (!empty($redir_data['oembed']['json'])) { - File_oembed::saveNew($redir_data['oembed']['json'], $file_id); + if (is_array($redir_data)) { + // We haven't seen the target URL before. + // Save file and embedding data about it! + $file = File::saveNew($redir_data, $long_url); + $file_id = $file->id; + if (!empty($redir_data['oembed']['json'])) { + File_oembed::saveNew($redir_data['oembed']['json'], $file_id); + } + } else if (is_string($redir_data)) { + // The file is a known redirect target. + $file = File::staticGet('url', $redir_data); + $file_id = $file->id; } } else { $file_id = $file->id; diff --git a/classes/Foreign_user.php b/classes/Foreign_user.php index 8b3e03dfb..0dd94ffb9 100644 --- a/classes/Foreign_user.php +++ b/classes/Foreign_user.php @@ -41,6 +41,7 @@ class Foreign_user extends Memcached_DataObject function updateKeys(&$orig) { + $this->_connect(); $parts = array(); foreach (array('id', 'service', 'uri', 'nickname') as $k) { if (strcmp($this->$k, $orig->$k) != 0) { diff --git a/classes/Notice.php b/classes/Notice.php index 4c7e6ab4b..f7194e339 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -119,6 +119,9 @@ class Notice extends Memcached_DataObject // NOTE: we don't clear queue items $result = parent::delete(); + + $this->blowOnDelete(); + return $result; } /** @@ -421,6 +424,18 @@ class Notice extends Memcached_DataObject $profile->blowNoticeCount(); } + /** + * Clear cache entries related to this notice at delete time. + * Necessary to avoid breaking paging on public, profile timelines. + */ + function blowOnDelete() + { + $this->blowOnInsert(); + + self::blow('profile:notice_ids:%d;last', $this->profile_id); + self::blow('public;last'); + } + /** save all urls in the notice to the db * * follow redirects and save all available file information @@ -589,7 +604,6 @@ class Notice extends Memcached_DataObject array(), 'public', $offset, $limit, $since_id, $max_id); - return Notice::getStreamByIds($ids); } @@ -1128,6 +1142,7 @@ class Notice extends Memcached_DataObject if ($source) { $xs->elementStart('source'); + $xs->element('id', null, $profile->profileurl); $xs->element('title', null, $profile->nickname . " - " . common_config('site', 'name')); $xs->element('link', array('href' => $profile->profileurl)); $user = User::staticGet('id', $profile->id); @@ -1143,13 +1158,14 @@ class Notice extends Memcached_DataObject } $xs->element('icon', null, $profile->avatarUrl(AVATAR_PROFILE_SIZE)); + $xs->element('updated', null, common_date_w3dtf($this->created)); } if ($source) { $xs->elementEnd('source'); } - $xs->element('title', null, $this->content); + $xs->element('title', null, common_xml_safe_str($this->content)); if ($author) { $xs->raw($profile->asAtomAuthor()); @@ -1225,7 +1241,11 @@ class Notice extends Memcached_DataObject } } - $xs->element('content', array('type' => 'html'), $this->rendered); + $xs->element( + 'content', + array('type' => 'html'), + common_xml_safe_str($this->rendered) + ); $tag = new Notice_tag(); $tag->notice_id = $this->id; diff --git a/classes/Profile.php b/classes/Profile.php index 0322c9358..eded1ff71 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -147,14 +147,16 @@ class Profile extends Memcached_DataObject return ($this->fullname) ? $this->fullname : $this->nickname; } - # Get latest notice on or before date; default now - function getCurrentNotice($dt=null) + /** + * Get the most recent notice posted by this user, if any. + * + * @return mixed Notice or null + */ + function getCurrentNotice() { $notice = new Notice(); $notice->profile_id = $this->id; - if ($dt) { - $notice->whereAdd('created < "' . $dt . '"'); - } + // @fixme change this to sort on notice.id only when indexes are updated $notice->orderBy('created DESC, notice.id DESC'); $notice->limit(1); if ($notice->find(true)) { @@ -730,6 +732,9 @@ class Profile extends Memcached_DataObject function hasRight($right) { $result = false; + if ($this->hasRole(Profile_role::DELETED)) { + return false; + } if (Event::handle('UserRightsCheck', array($this, $right, &$result))) { switch ($right) { diff --git a/classes/Profile_role.php b/classes/Profile_role.php index d0a0b31f0..e7aa1f0f0 100644 --- a/classes/Profile_role.php +++ b/classes/Profile_role.php @@ -53,6 +53,7 @@ class Profile_role extends Memcached_DataObject const ADMINISTRATOR = 'administrator'; const SANDBOXED = 'sandboxed'; const SILENCED = 'silenced'; + const DELETED = 'deleted'; // Pending final deletion of notices... public static function isValid($role) { diff --git a/classes/Safe_DataObject.php b/classes/Safe_DataObject.php index 021f7b506..e926cb0d5 100644 --- a/classes/Safe_DataObject.php +++ b/classes/Safe_DataObject.php @@ -43,6 +43,25 @@ class Safe_DataObject extends DB_DataObject } /** + * Magic function called at clone() time. + * + * We use this to drop connection with some global resources. + * This supports the fairly common pattern where individual + * items being read in a loop via a single object are cloned + * for individual processing, then fall out of scope when the + * loop comes around again. + * + * As that triggers the destructor, we want to make sure that + * the original object doesn't have its database result killed. + * It will still be freed properly when the original object + * gets destroyed. + */ + function __clone() + { + $this->_DB_resultid = false; + } + + /** * Magic function called at serialize() time. * * We use this to drop a couple process-specific references @@ -77,6 +96,30 @@ class Safe_DataObject extends DB_DataObject $this->_link_loaded = false; } + /** + * Magic function called when someone attempts to call a method + * that doesn't exist. DB_DataObject uses this to implement + * setters and getters for fields, but neglects to throw an error + * when you just misspell an actual method name. This leads to + * silent failures which can cause all kinds of havoc. + * + * @param string $method + * @param array $params + * @return mixed + * @throws Exception + */ + function __call($method, $params) + { + $return = null; + // Yes, that's _call with one underscore, which does the + // actual implementation. + if ($this->_call($method, $params, $return)) { + return $return; + } else { + throw new Exception('Call to undefined method ' . + get_class($this) . '::' . $method); + } + } /** * Work around memory-leak bugs... diff --git a/classes/Subscription.php b/classes/Subscription.php index 9cef2df1a..0679c0925 100644 --- a/classes/Subscription.php +++ b/classes/Subscription.php @@ -62,6 +62,14 @@ class Subscription extends Memcached_DataObject static function start($subscriber, $other) { + // @fixme should we enforce this as profiles in callers instead? + if ($subscriber instanceof User) { + $subscriber = $subscriber->getProfile(); + } + if ($other instanceof User) { + $other = $other->getProfile(); + } + if (!$subscriber->hasRight(Right::SUBSCRIBE)) { throw new Exception(_('You have been banned from subscribing.')); } @@ -75,26 +83,13 @@ class Subscription extends Memcached_DataObject } if (Event::handle('StartSubscribe', array($subscriber, $other))) { - - $sub = new Subscription(); - - $sub->subscriber = $subscriber->id; - $sub->subscribed = $other->id; - $sub->created = common_sql_now(); - - $result = $sub->insert(); - - if (!$result) { - common_log_db_error($sub, 'INSERT', __FILE__); - throw new Exception(_('Could not save subscription.')); - } - + $sub = self::saveNew($subscriber->id, $other->id); $sub->notify(); self::blow('user:notices_with_friends:%d', $subscriber->id); - $subscriber->blowSubscriptionsCount(); - $other->blowSubscribersCount(); + $subscriber->blowSubscriptionCount(); + $other->blowSubscriberCount(); $otherUser = User::staticGet('id', $other->id); @@ -103,20 +98,11 @@ class Subscription extends Memcached_DataObject !self::exists($other, $subscriber) && !$subscriber->hasBlocked($other)) { - $auto = new Subscription(); - - $auto->subscriber = $subscriber->id; - $auto->subscribed = $other->id; - $auto->created = common_sql_now(); - - $result = $auto->insert(); - - if (!$result) { - common_log_db_error($auto, 'INSERT', __FILE__); - throw new Exception(_('Could not save subscription.')); + try { + self::start($other, $subscriber); + } catch (Exception $e) { + common_log(LOG_ERR, "Exception during autosubscribe of {$other->nickname} to profile {$subscriber->id}: {$e->getMessage()}"); } - - $auto->notify(); } Event::handle('EndSubscribe', array($subscriber, $other)); @@ -125,6 +111,30 @@ class Subscription extends Memcached_DataObject return true; } + /** + * Low-level subscription save. + * Outside callers should use Subscription::start() + */ + protected function saveNew($subscriber_id, $other_id) + { + $sub = new Subscription(); + + $sub->subscriber = $subscriber_id; + $sub->subscribed = $other_id; + $sub->jabber = 1; + $sub->sms = 1; + $sub->created = common_sql_now(); + + $result = $sub->insert(); + + if (!$result) { + common_log_db_error($sub, 'INSERT', __FILE__); + throw new Exception(_('Could not save subscription.')); + } + + return $sub; + } + function notify() { # XXX: add other notifications (Jabber, SMS) here @@ -203,8 +213,8 @@ class Subscription extends Memcached_DataObject self::blow('user:notices_with_friends:%d', $subscriber->id); - $subscriber->blowSubscriptionsCount(); - $other->blowSubscribersCount(); + $subscriber->blowSubscriptionCount(); + $other->blowSubscriberCount(); Event::handle('EndUnsubscribe', array($subscriber, $other)); } diff --git a/classes/User.php b/classes/User.php index 15ec4ad94..1ba940a69 100644 --- a/classes/User.php +++ b/classes/User.php @@ -70,7 +70,11 @@ class User extends Memcached_DataObject function getProfile() { - return Profile::staticGet('id', $this->id); + $profile = Profile::staticGet('id', $this->id); + if (empty($profile)) { + throw new UserNoProfileException($this); + } + return $profile; } function isSubscribed($other) @@ -82,6 +86,7 @@ class User extends Memcached_DataObject function updateKeys(&$orig) { + $this->_connect(); $parts = array(); foreach (array('nickname', 'email', 'incomingemail', 'sms', 'carrier', 'smsemail', 'language', 'timezone') as $k) { if (strcmp($this->$k, $orig->$k) != 0) { @@ -127,13 +132,15 @@ class User extends Memcached_DataObject return !in_array($nickname, $blacklist); } - function getCurrentNotice($dt=null) + /** + * Get the most recent notice posted by this user, if any. + * + * @return mixed Notice or null + */ + function getCurrentNotice() { $profile = $this->getProfile(); - if (!$profile) { - return null; - } - return $profile->getCurrentNotice($dt); + return $profile->getCurrentNotice(); } function getCarrier() @@ -141,19 +148,12 @@ class User extends Memcached_DataObject return Sms_carrier::staticGet('id', $this->carrier); } + /** + * @deprecated use Subscription::start($sub, $other); + */ function subscribeTo($other) { - $sub = new Subscription(); - $sub->subscriber = $this->id; - $sub->subscribed = $other->id; - - $sub->created = common_sql_now(); // current time - - if (!$sub->insert()) { - return false; - } - - return true; + return Subscription::start($this->getProfile(), $other); } function hasBlocked($other) @@ -334,17 +334,7 @@ class User extends Memcached_DataObject common_log(LOG_WARNING, sprintf("Default user %s does not exist.", $defnick), __FILE__); } else { - $defsub = new Subscription(); - $defsub->subscriber = $user->id; - $defsub->subscribed = $defuser->id; - $defsub->created = $user->created; - - $result = $defsub->insert(); - - if (!$result) { - common_log_db_error($defsub, 'INSERT', __FILE__); - return false; - } + Subscription::start($user, $defuser); } } @@ -460,21 +450,13 @@ class User extends Memcached_DataObject function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) { $profile = $this->getProfile(); - if (!$profile) { - return null; - } else { - return $profile->getTaggedNotices($tag, $offset, $limit, $since_id, $before_id); - } + return $profile->getTaggedNotices($tag, $offset, $limit, $since_id, $before_id); } function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) { $profile = $this->getProfile(); - if (!$profile) { - return null; - } else { - return $profile->getNotices($offset, $limit, $since_id, $before_id); - } + return $profile->getNotices($offset, $limit, $since_id, $before_id); } function favoriteNotices($offset=0, $limit=NOTICES_PER_PAGE, $own=false) @@ -615,14 +597,12 @@ class User extends Memcached_DataObject function getSubscriptions($offset=0, $limit=null) { $profile = $this->getProfile(); - assert(!empty($profile)); return $profile->getSubscriptions($offset, $limit); } function getSubscribers($offset=0, $limit=null) { $profile = $this->getProfile(); - assert(!empty($profile)); return $profile->getSubscribers($offset, $limit); } @@ -686,9 +666,7 @@ class User extends Memcached_DataObject function delete() { $profile = $this->getProfile(); - if ($profile) { - $profile->delete(); - } + $profile->delete(); $related = array('Fave', 'Confirm_address', diff --git a/classes/User_group.php b/classes/User_group.php index 7be55163a..110f08301 100644 --- a/classes/User_group.php +++ b/classes/User_group.php @@ -295,7 +295,7 @@ class User_group extends Memcached_DataObject } // If not, check local groups. - + $group = Local_group::staticGet('nickname', $nickname); if (!empty($group)) { return User_group::staticGet('id', $group->group_id); @@ -371,16 +371,15 @@ class User_group extends Memcached_DataObject if ($source) { $xs->elementStart('source'); + $xs->element('id', null, $this->permalink()); $xs->element('title', null, $profile->nickname . " - " . common_config('site', 'name')); $xs->element('link', array('href' => $this->permalink())); - } - - if ($source) { + $xs->element('updated', null, $this->modified); $xs->elementEnd('source'); } $xs->element('title', null, $this->nickname); - $xs->element('summary', null, $this->description); + $xs->element('summary', null, common_xml_safe_str($this->description)); $xs->element('link', array('rel' => 'alternate', 'href' => $this->permalink())); @@ -390,7 +389,11 @@ class User_group extends Memcached_DataObject $xs->element('published', null, common_date_w3dtf($this->created)); $xs->element('updated', null, common_date_w3dtf($this->modified)); - $xs->element('content', array('type' => 'html'), $this->description); + $xs->element( + 'content', + array('type' => 'html'), + common_xml_safe_str($this->description) + ); $xs->elementEnd('entry'); @@ -455,7 +458,7 @@ class User_group extends Memcached_DataObject $group = new User_group(); $group->query('BEGIN'); - + if (empty($uri)) { // fill in later... $uri = null; |