From: Mikael Nordfeldth Date: Tue, 24 Jun 2014 13:52:54 +0000 (+0200) Subject: Favorite functionality put into plugin (not done yet) X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=db7cc7fa7553e0be302d518360648940756f131c;p=quix0rs-gnu-social.git Favorite functionality put into plugin (not done yet) Now we have to fix any code in the core which directly uses the Fave class or any other favorite stuff, since it is pluginised and thus might not be available on some installations. --- diff --git a/actions/apifavoritecreate.php b/actions/apifavoritecreate.php deleted file mode 100644 index 607fb76edc..0000000000 --- a/actions/apifavoritecreate.php +++ /dev/null @@ -1,173 +0,0 @@ -. - * - * @category API - * @package StatusNet - * @author Craig Andrews - * @author Evan Prodromou - * @author Zach Copley - * @copyright 2009 StatusNet, Inc. - * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET')) { - exit(1); -} - -/** - * Favorites the status specified in the ID parameter as the authenticating user. - * Returns the favorite status when successful. - * - * @category API - * @package StatusNet - * @author Craig Andrews - * @author Evan Prodromou - * @author Zach Copley - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ -class ApiFavoriteCreateAction extends ApiAuthAction -{ - var $notice = null; - - /** - * Take arguments for running - * - * @param array $args $_REQUEST args - * - * @return boolean success flag - */ - function prepare($args) - { - parent::prepare($args); - - $this->user = $this->auth_user; - $this->notice = Notice::getKV($this->arg('id')); - if ($this->notice->repeat_of != '' ) { - common_log(LOG_DEBUG, 'Trying to Fave '.$this->notice->id.', repeat of '.$this->notice->repeat_of); - common_log(LOG_DEBUG, 'Will Fave '.$this->notice->repeat_of.' instead'); - $real_notice_id = $this->notice->repeat_of; - $this->notice = Notice::getKV($real_notice_id); - } - - return true; - } - - /** - * Handle the request - * - * Check the format and show the user info - * - * @param array $args $_REQUEST data (unused) - * - * @return void - */ - function handle($args) - { - parent::handle($args); - - if ($_SERVER['REQUEST_METHOD'] != 'POST') { - $this->clientError( - // TRANS: Client error. POST is a HTTP command. It should not be translated. - _('This method requires a POST.'), - 400, - $this->format - ); - return; - } - - if (!in_array($this->format, array('xml', 'json'))) { - $this->clientError( - // TRANS: Client error displayed when coming across a non-supported API method. - _('API method not found.'), - 404, - $this->format - ); - return; - } - - if (empty($this->notice)) { - $this->clientError( - // TRANS: Client error displayed when requesting a status with a non-existing ID. - _('No status found with that ID.'), - 404, - $this->format - ); - return; - } - - // Note: Twitter lets you fave things repeatedly via API. - - if ($this->user->hasFave($this->notice)) { - $this->clientError( - // TRANS: Client error displayed when trying to mark a notice favourite that already is a favourite. - _('This status is already a favorite.'), - 403, - $this->format - ); - return; - } - - $fave = Fave::addNew($this->user->getProfile(), $this->notice); - - if (empty($fave)) { - $this->clientError( - // TRANS: Client error displayed when marking a notice as favourite fails. - _('Could not create favorite.'), - 403, - $this->format - ); - return; - } - - $this->notify($fave, $this->notice, $this->user); - $this->user->blowFavesCache(); - - if ($this->format == 'xml') { - $this->showSingleXmlStatus($this->notice); - } elseif ($this->format == 'json') { - $this->show_single_json_status($this->notice); - } - } - - /** - * Notify the author of the favorite that the user likes their notice - * - * @param Favorite $fave the favorite in question - * @param Notice $notice the notice that's been faved - * @param User $user the user doing the favoriting - * - * @return void - */ - function notify($fave, $notice, $user) - { - $other = User::getKV('id', $notice->profile_id); - if ($other && $other->id != $user->id) { - if ($other->email && $other->emailnotifyfav) { - mail_notify_fave($other, $user, $notice); - } - // XXX: notify by IM - // XXX: notify by SMS - } - } -} diff --git a/actions/apifavoritedestroy.php b/actions/apifavoritedestroy.php deleted file mode 100644 index 02f81cf4df..0000000000 --- a/actions/apifavoritedestroy.php +++ /dev/null @@ -1,154 +0,0 @@ -. - * - * @category API - * @package StatusNet - * @author Craig Andrews - * @author Evan Prodromou - * @author Zach Copley - * @copyright 2009 StatusNet, Inc. - * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET')) { - exit(1); -} - -/** - * Un-favorites the status specified in the ID parameter as the authenticating user. - * Returns the un-favorited status in the requested format when successful. - * - * @category API - * @package StatusNet - * @author Craig Andrews - * @author Evan Prodromou - * @author Zach Copley - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ -class ApiFavoriteDestroyAction extends ApiAuthAction -{ - var $notice = null; - - /** - * Take arguments for running - * - * @param array $args $_REQUEST args - * - * @return boolean success flag - */ - function prepare($args) - { - parent::prepare($args); - - $this->user = $this->auth_user; - $this->notice = Notice::getKV($this->arg('id')); - if ($this->notice->repeat_of != '' ) { - common_log(LOG_DEBUG, 'Trying to unFave '.$this->notice->id); - common_log(LOG_DEBUG, 'Will unFave '.$this->notice->repeat_of.' instead'); - $real_notice_id = $this->notice->repeat_of; - $this->notice = Notice::getKV($real_notice_id); - } - - return true; - } - - /** - * Handle the request - * - * Check the format and show the user info - * - * @param array $args $_REQUEST data (unused) - * - * @return void - */ - function handle($args) - { - parent::handle($args); - - if ($_SERVER['REQUEST_METHOD'] != 'POST') { - $this->clientError( - // TRANS: Client error. POST is a HTTP command. It should not be translated. - _('This method requires a POST.'), - 400, - $this->format - ); - return; - } - - if (!in_array($this->format, array('xml', 'json'))) { - $this->clientError( - // TRANS: Client error displayed when coming across a non-supported API method. - _('API method not found.'), - 404, - $this->format - ); - return; - } - - if (empty($this->notice)) { - $this->clientError( - // TRANS: Client error displayed when trying to remove a favourite with an invalid ID. - _('No status found with that ID.'), - 404, - $this->format - ); - return; - } - - $fave = new Fave(); - $fave->user_id = $this->user->id; - $fave->notice_id = $this->notice->id; - - if (!$fave->find(true)) { - $this->clientError( - // TRANS: Client error displayed when trying to remove a favourite that was not a favourite. - _('That status is not a favorite.'), - 403, - $this->favorite - ); - return; - } - - $result = $fave->delete(); - - if (!$result) { - common_log_db_error($fave, 'DELETE', __FILE__); - $this->clientError( - // TRANS: Client error displayed when removing a favourite has failed. - _('Could not delete favorite.'), - 404, - $this->format - ); - return; - } - - $this->user->blowFavesCache(); - - if ($this->format == 'xml') { - $this->showSingleXmlStatus($this->notice); - } elseif ($this->format == 'json') { - $this->show_single_json_status($this->notice); - } - } -} diff --git a/actions/apistatusesfavs.php b/actions/apistatusesfavs.php deleted file mode 100644 index 01a5d3014e..0000000000 --- a/actions/apistatusesfavs.php +++ /dev/null @@ -1,136 +0,0 @@ -. - * - * @category API - * @package GNUsocial - * @author Hannes Mannerheim - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://www.gnu.org/software/social/ - */ - -if (!defined('GNUSOCIAL')) { exit(1); } - -/** - * Show up to 100 favs of a notice - * - */ -class ApiStatusesFavsAction extends ApiAuthAction -{ - const MAXCOUNT = 100; - - var $original = null; // Notice object for which to retrieve favs - var $cnt = self::MAXCOUNT; - - /** - * Take arguments for running - * - * @param array $args $_REQUEST args - * - * @return boolean success flag - */ - protected function prepare(array $args=array()) - { - parent::prepare($args); - - if ($this->format !== 'json') { - $this->clientError('This method currently only serves JSON.', 415); - } - - $id = $this->trimmed('id'); - - $this->original = Notice::getKV('id', $id); - - if (!($this->original instanceof Notice)) { - // TRANS: Client error displayed trying to display redents of a non-exiting notice. - $this->clientError(_('No such notice.'), 400); - } - - $cnt = $this->trimmed('count'); - - if (empty($cnt) || !is_integer($cnt)) { - $cnt = 100; - } else { - $this->cnt = min((int)$cnt, self::MAXCOUNT); - } - - return true; - } - - /** - * Handle the request - * - * Get favs and return them as json object - * - * @param array $args $_REQUEST data (unused) - * - * @return void - */ - protected function handle() - { - parent::handle(); - - $fave = new Fave(); - $fave->selectAdd(); - $fave->selectAdd('user_id'); - $fave->notice_id = $this->original->id; - $fave->orderBy('modified'); - if (!is_null($this->cnt)) { - $fave->limit(0, $this->cnt); - } - - $ids = $fave->fetchAll('user_id'); - - // get nickname and profile image - $ids_with_profile_data = array(); - $i=0; - foreach($ids as $id) { - $profile = Profile::getKV('id', $id); - $ids_with_profile_data[$i]['user_id'] = $id; - $ids_with_profile_data[$i]['nickname'] = $profile->nickname; - $ids_with_profile_data[$i]['fullname'] = $profile->fullname; - $ids_with_profile_data[$i]['profileurl'] = $profile->profileurl; - $profile = new Profile(); - $profile->id = $id; - $avatarurl = $profile->avatarUrl(24); - $ids_with_profile_data[$i]['avatarurl'] = $avatarurl; - $i++; - } - - $this->initDocument('json'); - $this->showJsonObjects($ids_with_profile_data); - $this->endDocument('json'); - } - - /** - * Return true if read only. - * - * MAY override - * - * @param array $args other arguments - * - * @return boolean is read only action? - */ - - function isReadOnly($args) - { - return true; - } -} diff --git a/actions/apitimelinefavorites.php b/actions/apitimelinefavorites.php deleted file mode 100644 index 13dc842447..0000000000 --- a/actions/apitimelinefavorites.php +++ /dev/null @@ -1,263 +0,0 @@ -. - * - * @category API - * @package StatusNet - * @author Craig Andrews - * @author Evan Prodromou - * @author Zach Copley - * @copyright 2009-2010 StatusNet, Inc. - * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET')) { - exit(1); -} - -/** - * Returns the 20 most recent favorite notices for the authenticating user or user - * specified by the ID parameter in the requested format. - * - * @category API - * @package StatusNet - * @author Craig Andrews - * @author Evan Prodromou - * @author Zach Copley - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ -class ApiTimelineFavoritesAction extends ApiBareAuthAction -{ - var $notices = null; - - /** - * Take arguments for running - * - * @param array $args $_REQUEST args - * - * @return boolean success flag - */ - protected function prepare(array $args=array()) - { - parent::prepare($args); - - $this->target = $this->getTargetProfile($this->arg('id')); - - if (!($this->target instanceof Profile)) { - // TRANS: Client error displayed when requesting most recent favourite notices by a user for a non-existing user. - $this->clientError(_('No such user.'), 404); - } - - $this->notices = $this->getNotices(); - - return true; - } - - /** - * Handle the request - * - * Just show the notices - * - * @return void - */ - protected function handle() - { - parent::handle(); - $this->showTimeline(); - } - - /** - * Show the timeline of notices - * - * @return void - */ - function showTimeline() - { - $sitename = common_config('site', 'name'); - $title = sprintf( - // TRANS: Title for timeline of most recent favourite notices by a user. - // TRANS: %1$s is the StatusNet sitename, %2$s is a user nickname. - _('%1$s / Favorites from %2$s'), - $sitename, - $this->target->nickname - ); - - $taguribase = TagURI::base(); - $id = "tag:$taguribase:Favorites:" . $this->target->id; - - $subtitle = sprintf( - // TRANS: Subtitle for timeline of most recent favourite notices by a user. - // TRANS: %1$s is the StatusNet sitename, %2$s is a user's full name, - // TRANS: %3$s is a user nickname. - _('%1$s updates favorited by %2$s / %3$s.'), - $sitename, - $this->target->getBestName(), - $this->target->nickname - ); - - $logo = $this->target->avatarUrl(AVATAR_PROFILE_SIZE); - $link = common_local_url('showfavorites', - array('nickname' => $this->target->nickname)); - $self = $this->getSelfUri(); - - switch($this->format) { - case 'xml': - $this->showXmlTimeline($this->notices); - break; - case 'rss': - $this->showRssTimeline( - $this->notices, - $title, - $link, - $subtitle, - null, - $logo, - $self - ); - break; - case 'atom': - header('Content-Type: application/atom+xml; charset=utf-8'); - - $atom = new AtomNoticeFeed($this->auth_user); - - $atom->setId($id); - $atom->setTitle($title); - $atom->setSubtitle($subtitle); - $atom->setLogo($logo); - $atom->setUpdated('now'); - - $atom->addLink($link); - $atom->setSelfLink($self); - - $atom->addEntryFromNotices($this->notices); - - $this->raw($atom->getString()); - break; - case 'json': - $this->showJsonTimeline($this->notices); - break; - case 'as': - header('Content-Type: ' . ActivityStreamJSONDocument::CONTENT_TYPE); - $doc = new ActivityStreamJSONDocument($this->auth_user); - $doc->setTitle($title); - $doc->addLink($link,'alternate', 'text/html'); - $doc->addItemsFromNotices($this->notices); - $this->raw($doc->asString()); - break; - default: - // TRANS: Client error displayed when coming across a non-supported API method. - $this->clientError(_('API method not found.'), 404); - } - } - - /** - * Get notices - * - * @return array notices - */ - function getNotices() - { - $notices = array(); - - common_debug("since id = " . $this->since_id . " max id = " . $this->max_id); - - if (!empty($this->auth_user) && $this->auth_user->id == $this->target->id) { - $notice = $this->target->favoriteNotices( - true, - ($this->page-1) * $this->count, - $this->count, - $this->since_id, - $this->max_id - ); - } else { - $notice = $this->target->favoriteNotices( - false, - ($this->page-1) * $this->count, - $this->count, - $this->since_id, - $this->max_id - ); - } - - while ($notice->fetch()) { - $notices[] = clone($notice); - } - - return $notices; - } - - /** - * Is this action read only? - * - * @param array $args other arguments - * - * @return boolean true - */ - function isReadOnly($args) - { - return true; - } - - /** - * When was this feed last modified? - * - * @return string datestamp of the latest notice in the stream - */ - function lastModified() - { - if (!empty($this->notices) && (count($this->notices) > 0)) { - return strtotime($this->notices[0]->created); - } - - return null; - } - - /** - * An entity tag for this stream - * - * Returns an Etag based on the action name, language, user ID, and - * timestamps of the first and last notice in the timeline - * - * @return string etag - */ - function etag() - { - if (!empty($this->notices) && (count($this->notices) > 0)) { - - $last = count($this->notices) - 1; - - return '"' . implode( - ':', - array($this->arg('action'), - common_user_cache_hash($this->auth_user), - common_language(), - $this->target->id, - strtotime($this->notices[0]->created), - strtotime($this->notices[$last]->created)) - ) - . '"'; - } - - return null; - } -} diff --git a/actions/atompubfavoritefeed.php b/actions/atompubfavoritefeed.php deleted file mode 100644 index 837a9da3e9..0000000000 --- a/actions/atompubfavoritefeed.php +++ /dev/null @@ -1,372 +0,0 @@ -. - * - * @category AtomPub - * @package StatusNet - * @author Evan Prodromou - * @copyright 2010 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET')) { - // This check helps protect against security problems; - // your code file can't be executed directly from the web. - exit(1); -} - -/** - * Feed of ActivityStreams 'favorite' actions - * - * @category AtomPub - * @package StatusNet - * @author Evan Prodromou - * @copyright 2010 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ -class AtompubfavoritefeedAction extends ApiAuthAction -{ - private $_profile = null; - private $_faves = null; - - /** - * For initializing members of the class. - * - * @param array $argarray misc. arguments - * - * @return boolean true - */ - function prepare($argarray) - { - parent::prepare($argarray); - - $this->_profile = Profile::getKV('id', $this->trimmed('profile')); - - if (empty($this->_profile)) { - // TRANS: Client exception thrown when requesting a favorite feed for a non-existing profile. - throw new ClientException(_('No such profile.'), 404); - } - - $offset = ($this->page-1) * $this->count; - $limit = $this->count + 1; - - $this->_faves = Fave::byProfile($this->_profile->id, - $offset, - $limit); - - return true; - } - - /** - * Handler method - * - * @param array $argarray is ignored since it's now passed in in prepare() - * - * @return void - */ - function handle($argarray=null) - { - parent::handle($argarray); - - switch ($_SERVER['REQUEST_METHOD']) { - case 'HEAD': - case 'GET': - $this->showFeed(); - break; - case 'POST': - $this->addFavorite(); - break; - default: - // TRANS: Client exception thrown when using an unsupported HTTP method. - throw new ClientException(_('HTTP method not supported.'), 405); - return; - } - - return; - } - - /** - * Show a feed of favorite activity streams objects - * - * @return void - */ - function showFeed() - { - header('Content-Type: application/atom+xml; charset=utf-8'); - - $url = common_local_url('AtomPubFavoriteFeed', - array('profile' => $this->_profile->id)); - - $feed = new Atom10Feed(true); - - $feed->addNamespace('activity', - 'http://activitystrea.ms/spec/1.0/'); - - $feed->addNamespace('poco', - 'http://portablecontacts.net/spec/1.0'); - - $feed->addNamespace('media', - 'http://purl.org/syndication/atommedia'); - - $feed->id = $url; - - $feed->setUpdated('now'); - - $feed->addAuthor($this->_profile->getBestName(), - $this->_profile->getURI()); - - // TRANS: Title for Atom favorites feed. - // TRANS: %s is a user nickname. - $feed->setTitle(sprintf(_("%s favorites"), - $this->_profile->getBestName())); - - // TRANS: Subtitle for Atom favorites feed. - // TRANS: %1$s is a user nickname, %2$s is the StatusNet sitename. - $feed->setSubtitle(sprintf(_('Notices %1$s has favorited on %2$s'), - $this->_profile->getBestName(), - common_config('site', 'name'))); - - $feed->addLink(common_local_url('showfavorites', - array('nickname' => - $this->_profile->nickname))); - - $feed->addLink($url, - array('rel' => 'self', - 'type' => 'application/atom+xml')); - - // If there's more... - - if ($this->page > 1) { - $feed->addLink($url, - array('rel' => 'first', - 'type' => 'application/atom+xml')); - - $feed->addLink(common_local_url('AtomPubFavoriteFeed', - array('profile' => - $this->_profile->id), - array('page' => - $this->page - 1)), - array('rel' => 'prev', - 'type' => 'application/atom+xml')); - } - - if ($this->_faves->N > $this->count) { - - $feed->addLink(common_local_url('AtomPubFavoriteFeed', - array('profile' => - $this->_profile->id), - array('page' => - $this->page + 1)), - array('rel' => 'next', - 'type' => 'application/atom+xml')); - } - - $i = 0; - - while ($this->_faves->fetch()) { - - // We get one more than needed; skip that one - - $i++; - - if ($i > $this->count) { - break; - } - - $act = $this->_faves->asActivity(); - $feed->addEntryRaw($act->asString(false, false, false)); - } - - $this->raw($feed->getString()); - } - - /** - * add a new favorite - * - * @return void - */ - function addFavorite() - { - // XXX: Refactor this; all the same for atompub - - if (empty($this->auth_user) || - $this->auth_user->id != $this->_profile->id) { - // TRANS: Client exception thrown when trying to set a favorite for another user. - throw new ClientException(_("Cannot add someone else's". - " subscription."), 403); - } - - $xml = file_get_contents('php://input'); - - $dom = DOMDocument::loadXML($xml); - - if ($dom->documentElement->namespaceURI != Activity::ATOM || - $dom->documentElement->localName != 'entry') { - // TRANS: Client error displayed when not using an Atom entry. - throw new ClientException(_('Atom post must be an Atom entry.')); - return; - } - - $activity = new Activity($dom->documentElement); - - $fave = null; - - if (Event::handle('StartAtomPubNewActivity', array(&$activity))) { - - if ($activity->verb != ActivityVerb::FAVORITE) { - // TRANS: Client exception thrown when trying use an incorrect activity verb for the Atom pub method. - throw new ClientException(_('Can only handle favorite activities.')); - return; - } - - $note = $activity->objects[0]; - - if (!in_array($note->type, array(ActivityObject::NOTE, - ActivityObject::BLOGENTRY, - ActivityObject::STATUS))) { - // TRANS: Client exception thrown when trying favorite an object that is not a notice. - throw new ClientException(_('Can only fave notices.')); - return; - } - - $notice = Notice::getKV('uri', $note->id); - - if (empty($notice)) { - // XXX: import from listed URL or something - // TRANS: Client exception thrown when trying favorite a notice without content. - throw new ClientException(_('Unknown notice.')); - } - - $old = Fave::pkeyGet(array('user_id' => $this->auth_user->id, - 'notice_id' => $notice->id)); - - if (!empty($old)) { - // TRANS: Client exception thrown when trying favorite an already favorited notice. - throw new ClientException(_('Already a favorite.')); - } - - $profile = $this->auth_user->getProfile(); - - $fave = Fave::addNew($profile, $notice); - - if (!empty($fave)) { - $this->_profile->blowFavesCache(); - $this->notify($fave, $notice, $this->auth_user); - } - - Event::handle('EndAtomPubNewActivity', array($activity, $fave)); - } - - if (!empty($fave)) { - $act = $fave->asActivity(); - - header('Content-Type: application/atom+xml; charset=utf-8'); - header('Content-Location: ' . $act->selfLink); - - $this->startXML(); - $this->raw($act->asString(true, true, true)); - $this->endXML(); - } - } - - /** - * Return true if read only. - * - * MAY override - * - * @param array $args other arguments - * - * @return boolean is read only action? - */ - function isReadOnly($args) - { - if ($_SERVER['REQUEST_METHOD'] == 'GET' || - $_SERVER['REQUEST_METHOD'] == 'HEAD') { - return true; - } else { - return false; - } - } - - /** - * Return last modified, if applicable. - * - * MAY override - * - * @return string last modified http header - */ - function lastModified() - { - // For comparison with If-Last-Modified - // If not applicable, return null - return null; - } - - /** - * Return etag, if applicable. - * - * MAY override - * - * @return string etag http header - */ - function etag() - { - return null; - } - - /** - * Does this require authentication? - * - * @return boolean true if delete, else false - */ - function requiresAuth() - { - if ($_SERVER['REQUEST_METHOD'] == 'GET' || - $_SERVER['REQUEST_METHOD'] == 'HEAD') { - return false; - } else { - return true; - } - } - - /** - * Notify the author of the favorite that the user likes their notice - * - * @param Favorite $fave the favorite in question - * @param Notice $notice the notice that's been faved - * @param User $user the user doing the favoriting - * - * @return void - */ - function notify($fave, $notice, $user) - { - $other = User::getKV('id', $notice->profile_id); - if ($other && $other->id != $user->id) { - if ($other->email && $other->emailnotifyfav) { - mail_notify_fave($other, $user, $notice); - } - // XXX: notify by IM - // XXX: notify by SMS - } - } -} diff --git a/actions/atompubshowfavorite.php b/actions/atompubshowfavorite.php deleted file mode 100644 index 436e88e693..0000000000 --- a/actions/atompubshowfavorite.php +++ /dev/null @@ -1,222 +0,0 @@ -. - * - * @category AtomPub - * @package StatusNet - * @author Evan Prodromou - * @copyright 2010 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET')) { - // This check helps protect against security problems; - // your code file can't be executed directly from the web. - exit(1); -} - -/** - * Show a single favorite in Atom Activity Streams format. - * - * Can also be used to delete a favorite. - * - * @category Action - * @package StatusNet - * @author Evan Prodromou - * @copyright 2010 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ -class AtompubshowfavoriteAction extends ApiAuthAction -{ - private $_profile = null; - private $_notice = null; - private $_fave = null; - - /** - * For initializing members of the class. - * - * @param array $argarray misc. arguments - * - * @return boolean true - */ - function prepare($argarray) - { - parent::prepare($argarray); - - $profileId = $this->trimmed('profile'); - $noticeId = $this->trimmed('notice'); - - $this->_profile = Profile::getKV('id', $profileId); - - if (empty($this->_profile)) { - // TRANS: Client exception. - throw new ClientException(_('No such profile.'), 404); - } - - $this->_notice = Notice::getKV('id', $noticeId); - - if (empty($this->_notice)) { - // TRANS: Client exception thrown when referencing a non-existing notice. - throw new ClientException(_('No such notice.'), 404); - } - - $this->_fave = Fave::pkeyGet(array('user_id' => $profileId, - 'notice_id' => $noticeId)); - - if (empty($this->_fave)) { - // TRANS: Client exception thrown when referencing a non-existing favorite. - throw new ClientException(_('No such favorite.'), 404); - } - - return true; - } - - /** - * Handler method - * - * @param array $argarray is ignored since it's now passed in in prepare() - * - * @return void - */ - function handle($argarray=null) - { - parent::handle($argarray); - - switch ($_SERVER['REQUEST_METHOD']) { - case GET: - case HEAD: - $this->showFave(); - break; - case DELETE: - $this->deleteFave(); - break; - default: - // TRANS: Client exception thrown using an unsupported HTTP method. - throw new ClientException(_('HTTP method not supported.'), - 405); - } - return true; - } - - /** - * Show a single favorite, in ActivityStreams format - * - * @return void - */ - function showFave() - { - $activity = $this->_fave->asActivity(); - - header('Content-Type: application/atom+xml; charset=utf-8'); - - $this->startXML(); - $this->raw($activity->asString(true, true, true)); - $this->endXML(); - - return; - } - - /** - * Delete the favorite - * - * @return void - */ - function deleteFave() - { - if (empty($this->auth_user) || - $this->auth_user->id != $this->_profile->id) { - // TRANS: Client exception thrown when trying to remove a favorite notice of another user. - throw new ClientException(_("Cannot delete someone else's". - " favorite."), 403); - } - - $this->_fave->delete(); - - return; - } - - /** - * Return true if read only. - * - * MAY override - * - * @param array $args other arguments - * - * @return boolean is read only action? - */ - function isReadOnly($args) - { - if ($_SERVER['REQUEST_METHOD'] == 'GET' || - $_SERVER['REQUEST_METHOD'] == 'HEAD') { - return true; - } else { - return false; - } - } - - /** - * Return last modified, if applicable. - * - * MAY override - * - * @return string last modified http header - */ - function lastModified() - { - return max(strtotime($this->_profile->modified), - strtotime($this->_notice->modified), - strtotime($this->_fave->modified)); - } - - /** - * Return etag, if applicable. - * - * MAY override - * - * @return string etag http header - */ - function etag() - { - $mtime = strtotime($this->_fave->modified); - - return 'W/"' . implode(':', array('AtomPubShowFavorite', - $this->_profile->id, - $this->_notice->id, - $mtime)) . '"'; - } - - /** - * Does this require authentication? - * - * @return boolean true if delete, else false - */ - function requiresAuth() - { - if ($_SERVER['REQUEST_METHOD'] == 'GET' || - $_SERVER['REQUEST_METHOD'] == 'HEAD') { - return false; - } else { - return true; - } - } -} diff --git a/actions/disfavor.php b/actions/disfavor.php deleted file mode 100644 index ef9ee1ee2b..0000000000 --- a/actions/disfavor.php +++ /dev/null @@ -1,91 +0,0 @@ - - * @author Robin Millette - * @author Mikael Nordfeldth - * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 - * @link http://www.gnu.org/software/social/ - * - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * 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 . - */ - -if (!defined('GNUSOCIAL')) { exit(1); } - -/** - * DisfavorAction class. - * - * @category Action - * @package GNUsocial - * @author Evan Prodromou - * @author Robin Millette - * @author Mikael Nordfeldth - * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 - * @link http://www.gnu.org/software/social/ - */ -class DisfavorAction extends FormAction -{ - public function showForm($msg=null, $success=false) - { - if ($success) { - common_redirect(common_local_url('showfavorites', - array('nickname' => $this->scoped->nickname)), 303); - } - parent::showForm($msg, $success); - } - - protected function handlePost() - { - $id = $this->trimmed('notice'); - $notice = Notice::getKV($id); - if (!$notice instanceof Notice) { - $this->serverError(_('Notice not found')); - } - - $fave = new Fave(); - $fave->user_id = $this->scoped->id; - $fave->notice_id = $notice->id; - if (!$fave->find(true)) { - throw new NoResultException($fave); - } - $result = $fave->delete(); - if (!$result) { - common_log_db_error($fave, 'DELETE', __FILE__); - // TRANS: Server error displayed when removing a favorite from the database fails. - $this->serverError(_('Could not delete favorite.')); - } - $this->scoped->blowFavesCache(); - if (StatusNet::isAjax()) { - $this->startHTML('text/xml;charset=utf-8'); - $this->elementStart('head'); - // TRANS: Title for page on which favorites can be added. - $this->element('title', null, _('Add to favorites')); - $this->elementEnd('head'); - $this->elementStart('body'); - $favor = new FavorForm($this, $notice); - $favor->show(); - $this->elementEnd('body'); - $this->endHTML(); - exit; - } - } -} diff --git a/actions/favor.php b/actions/favor.php deleted file mode 100644 index 8c19f9da5c..0000000000 --- a/actions/favor.php +++ /dev/null @@ -1,106 +0,0 @@ - - * @author Robin Millette - * @author Mikael Nordfeldth - * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 - * @link http://www.gnu.org/software/social/ - * - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * 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 . - */ - -if (!defined('GNUSOCIAL')) { exit(1); } - -require_once INSTALLDIR.'/lib/mail.php'; - -/** - * FavorAction class. - * - * @category Action - * @package GNUsocial - * @author Evan Prodromou - * @author Robin Millette - * @author Mikael Nordfeldth - * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 - * @link http://www.gnu.org/software/social/ - */ -class FavorAction extends FormAction -{ - protected $needPost = true; - - protected function handlePost() - { - $id = $this->trimmed('notice'); - $notice = Notice::getKV($id); - if (!($notice instanceof Notice)) { - $this->serverError(_('Notice not found')); - } - if ($this->scoped->hasFave($notice)) { - // TRANS: Client error displayed when trying to mark a notice as favorite that already is a favorite. - $this->clientError(_('This notice is already a favorite!')); - } - $fave = Fave::addNew($this->scoped, $notice); - if (!$fave) { - // TRANS: Server error displayed when trying to mark a notice as favorite fails in the database. - $this->serverError(_('Could not create favorite.')); - } - $this->notify($notice, $this->scoped->getUser()); - $this->scoped->blowFavesCache(); - if (StatusNet::isAjax()) { - $this->startHTML('text/xml;charset=utf-8'); - $this->elementStart('head'); - // TRANS: Page title for page on which favorite notices can be unfavourited. - $this->element('title', null, _('Disfavor favorite.')); - $this->elementEnd('head'); - $this->elementStart('body'); - $disfavor = new DisFavorForm($this, $notice); - $disfavor->show(); - $this->elementEnd('body'); - $this->endHTML(); - exit; - } - common_redirect(common_local_url('showfavorites', - array('nickname' => $this->scoped->nickname)), - 303); - } - - /** - * Notifies a user when their notice is favorited. - * - * @param class $notice favorited notice - * @param class $user user declaring a favorite - * - * @return void - */ - function notify($notice, $user) - { - $other = User::getKV('id', $notice->profile_id); - if ($other && $other->id != $user->id) { - if ($other->email && $other->emailnotifyfav) { - mail_notify_fave($other, $user, $notice); - } - // XXX: notify by IM - // XXX: notify by SMS - } - } -} diff --git a/actions/favorited.php b/actions/favorited.php deleted file mode 100644 index ff4a99cd60..0000000000 --- a/actions/favorited.php +++ /dev/null @@ -1,189 +0,0 @@ -. - * - * @category Public - * @package StatusNet - * @author Zach Copley - * @author Evan Prodromou - * @copyright 2008-2009 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR.'/lib/publicgroupnav.php'; -require_once INSTALLDIR.'/lib/noticelist.php'; - -/** - * List of popular notices - * - * We provide a list of the most popular notices. Popularity - * is measured by - * - * @category Personal - * @package StatusNet - * @author Zach Copley - * @author Evan Prodromou - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ -class FavoritedAction extends Action -{ - var $page = null; - - /** - * Title of the page - * - * @return string Title of the page - */ - - function title() - { - if ($this->page == 1) { - // TRANS: Page title for first page of favorited notices. - return _('Popular notices'); - } else { - // TRANS: Page title for all but first page of favorited notices. - // TRANS: %d is the page number being displayed. - return sprintf(_('Popular notices, page %d'), $this->page); - } - } - - /** - * Instructions for use - * - * @return instructions for use - */ - function getInstructions() - { - // TRANS: Description on page displaying favorited notices. - return _('The most popular notices on the site right now.'); - } - - /** - * Is this page read-only? - * - * @return boolean true - */ - function isReadOnly($args) - { - return true; - } - - /** - * Take arguments for running - * - * @param array $args $_REQUEST args - * - * @return boolean success flag - * - * @todo move queries from showContent() to here - */ - function prepare($args) - { - parent::prepare($args); - $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; - - common_set_returnto($this->selfUrl()); - - return true; - } - - /** - * Handle request - * - * Shows a page with list of favorite notices - * - * @param array $args $_REQUEST args; handled in prepare() - * - * @return void - */ - function handle($args) - { - parent::handle($args); - - $this->showPage(); - } - - /** - * Show the page notice - * - * Shows instructions for the page - * - * @return void - */ - function showPageNotice() - { - $instr = $this->getInstructions(); - $output = common_markup_to_html($instr); - - $this->elementStart('div', 'instructions'); - $this->raw($output); - $this->elementEnd('div'); - } - - function showEmptyList() - { - // TRANS: Text displayed instead of a list when a site does not yet have any favourited notices. - $message = _('Favorite notices appear on this page but no one has favorited one yet.') . ' '; - - if (common_logged_in()) { - // TRANS: Additional text displayed instead of a list when a site does not yet have any favourited notices for logged in users. - $message .= _('Be the first to add a notice to your favorites by clicking the fave button next to any notice you like.'); - } - else { - // TRANS: Additional text displayed instead of a list when a site does not yet have any favourited notices for not logged in users. - // TRANS: %%action.register%% is a registration link. "[link text](link)" is Mark Down. Do not change the formatting. - $message .= _('Why not [register an account](%%action.register%%) and be the first to add a notice to your favorites!'); - } - - $this->elementStart('div', 'guide'); - $this->raw(common_markup_to_html($message)); - $this->elementEnd('div'); - } - - /** - * Content area - * - * Shows the list of popular notices - * - * @return void - */ - function showContent() - { - $stream = new PopularNoticeStream(Profile::current()); - $notice = $stream->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE+1); - - $nl = new NoticeList($notice, $this); - - $cnt = $nl->show(); - - if ($cnt == 0) { - $this->showEmptyList(); - } - - $this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE, - $this->page, 'favorited'); - } -} diff --git a/actions/favoritesrss.php b/actions/favoritesrss.php deleted file mode 100644 index de901b0b46..0000000000 --- a/actions/favoritesrss.php +++ /dev/null @@ -1,131 +0,0 @@ - - * @author Robin Millette - * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 - * @link http://status.net/ - * - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * 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 . - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR.'/lib/rssaction.php'; - -/** - * RSS feed for user favorites action class. - * - * Formatting of RSS handled by Rss10Action - * - * @category Action - * @package StatusNet - * @author Evan Prodromou - * @author Robin Millette - * @author Zach Copley - * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 - * @link http://status.net/ - */ -class FavoritesrssAction extends Rss10Action -{ - /** The user whose favorites to display */ - - var $user = null; - - /** - * Find the user to display by supplied nickname - * - * @param array $args Arguments from $_REQUEST - * - * @return boolean success - */ - function prepare($args) - { - parent::prepare($args); - - $nickname = $this->trimmed('nickname'); - $this->user = User::getKV('nickname', $nickname); - - if (!$this->user) { - // TRANS: Client error displayed when trying to get the RSS feed with favorites of a user that does not exist. - $this->clientError(_('No such user.')); - } else { - $this->notices = $this->getNotices($this->limit); - return true; - } - } - - /** - * Get notices - * - * @param integer $limit max number of notices to return - * - * @return array notices - */ - function getNotices($limit=0) - { - $user = $this->user; - $notice = $user->favoriteNotices(false, 0, $limit); - $notices = array(); - while ($notice->fetch()) { - $notices[] = clone($notice); - } - return $notices; - } - - /** - * Get channel. - * - * @return array associative array on channel information - */ - function getChannel() - { - $user = $this->user; - $c = array('url' => common_local_url('favoritesrss', - array('nickname' => - $user->nickname)), - // TRANS: Title of RSS feed with favourite notices of a user. - // TRANS: %s is a user's nickname. - 'title' => sprintf(_("%s's favorite notices"), $user->nickname), - 'link' => common_local_url('showfavorites', - array('nickname' => - $user->nickname)), - // TRANS: Desciption of RSS feed with favourite notices of a user. - // TRANS: %1$s is a user's nickname, %2$s is the name of the StatusNet site. - 'description' => sprintf(_('Updates favored by %1$s on %2$s!'), - $user->nickname, common_config('site', 'name'))); - return $c; - } - - /** - * Get image. - * - * @return void - */ - function getImage() - { - return null; - } - -} diff --git a/actions/showfavorites.php b/actions/showfavorites.php deleted file mode 100644 index 0e028416da..0000000000 --- a/actions/showfavorites.php +++ /dev/null @@ -1,261 +0,0 @@ -. - * - * @category Personal - * @package StatusNet - * @author Evan Prodromou - * @copyright 2008-2011 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR.'/lib/personalgroupnav.php'; -require_once INSTALLDIR.'/lib/noticelist.php'; -require_once INSTALLDIR.'/lib/feedlist.php'; - -/** - * List of replies - * - * @category Personal - * @package StatusNet - * @author Evan Prodromou - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ -class ShowfavoritesAction extends Action -{ - /** User we're getting the faves of */ - var $user = null; - /** Page of the faves we're on */ - var $page = null; - - /** - * Is this a read-only page? - * - * @return boolean true - */ - function isReadOnly($args) - { - return true; - } - - /** - * Title of the page - * - * Includes name of user and page number. - * - * @return string title of page - */ - function title() - { - if ($this->page == 1) { - // TRANS: Title for first page of favourite notices of a user. - // TRANS: %s is the user for whom the favourite notices are displayed. - return sprintf(_('%s\'s favorite notices'), $this->user->nickname); - } else { - // TRANS: Title for all but the first page of favourite notices of a user. - // TRANS: %1$s is the user for whom the favourite notices are displayed, %2$d is the page number. - return sprintf(_('%1$s\'s favorite notices, page %2$d'), - $this->user->nickname, - $this->page); - } - } - - /** - * Prepare the object - * - * Check the input values and initialize the object. - * Shows an error page on bad input. - * - * @param array $args $_REQUEST data - * - * @return boolean success flag - */ - function prepare($args) - { - parent::prepare($args); - - $nickname = common_canonical_nickname($this->arg('nickname')); - - $this->user = User::getKV('nickname', $nickname); - - if (!$this->user) { - // TRANS: Client error displayed when trying to display favourite notices for a non-existing user. - $this->clientError(_('No such user.')); - } - - $this->page = $this->trimmed('page'); - - if (!$this->page) { - $this->page = 1; - } - - common_set_returnto($this->selfUrl()); - - $cur = common_current_user(); - - if (!empty($cur) && $cur->id == $this->user->id) { - - // Show imported/gateway notices as well as local if - // the user is looking at their own favorites - - $this->notice = $this->user->favoriteNotices(true, ($this->page-1)*NOTICES_PER_PAGE, - NOTICES_PER_PAGE + 1); - } else { - $this->notice = $this->user->favoriteNotices(false, ($this->page-1)*NOTICES_PER_PAGE, - NOTICES_PER_PAGE + 1); - } - - if (empty($this->notice)) { - // TRANS: Server error displayed when favourite notices could not be retrieved from the database. - $this->serverError(_('Could not retrieve favorite notices.')); - } - - if($this->page > 1 && $this->notice->N == 0){ - // TRANS: Server error when page not found (404) - $this->serverError(_('No such page.'),$code=404); - } - - return true; - } - - /** - * Handle a request - * - * Just show the page. All args already handled. - * - * @param array $args $_REQUEST data - * - * @return void - */ - function handle($args) - { - parent::handle($args); - $this->showPage(); - } - - /** - * Feeds for the section - * - * @return array Feed objects to show - */ - function getFeeds() - { - return array(new Feed(Feed::JSON, - common_local_url('ApiTimelineFavorites', - array( - 'id' => $this->user->nickname, - 'format' => 'as')), - // TRANS: Feed link text. %s is a username. - sprintf(_('Feed for favorites of %s (Activity Streams JSON)'), - $this->user->nickname)), - new Feed(Feed::RSS1, - common_local_url('favoritesrss', - array('nickname' => $this->user->nickname)), - // TRANS: Feed link text. %s is a username. - sprintf(_('Feed for favorites of %s (RSS 1.0)'), - $this->user->nickname)), - new Feed(Feed::RSS2, - common_local_url('ApiTimelineFavorites', - array( - 'id' => $this->user->nickname, - 'format' => 'rss')), - // TRANS: Feed link text. %s is a username. - sprintf(_('Feed for favorites of %s (RSS 2.0)'), - $this->user->nickname)), - new Feed(Feed::ATOM, - common_local_url('ApiTimelineFavorites', - array( - 'id' => $this->user->nickname, - 'format' => 'atom')), - // TRANS: Feed link text. %s is a username. - sprintf(_('Feed for favorites of %s (Atom)'), - $this->user->nickname))); - } - - function showEmptyListMessage() - { - if (common_logged_in()) { - $current_user = common_current_user(); - if ($this->user->id === $current_user->id) { - // TRANS: Text displayed instead of favourite notices for the current logged in user that has no favourites. - $message = _('You haven\'t chosen any favorite notices yet. Click the fave button on notices you like to bookmark them for later or shed a spotlight on them.'); - } else { - // TRANS: Text displayed instead of favourite notices for a user that has no favourites while logged in. - // TRANS: %s is a username. - $message = sprintf(_('%s hasn\'t added any favorite notices yet. Post something interesting they would add to their favorites :)'), $this->user->nickname); - } - } - else { - // TRANS: Text displayed instead of favourite notices for a user that has no favourites while not logged in. - // TRANS: %s is a username, %%%%action.register%%%% is a link to the user registration page. - // TRANS: (link text)[link] is a Mark Down link. - $message = sprintf(_('%s hasn\'t added any favorite notices yet. Why not [register an account](%%%%action.register%%%%) and then post something interesting they would add to their favorites :)'), $this->user->nickname); - } - - $this->elementStart('div', 'guide'); - $this->raw(common_markup_to_html($message)); - $this->elementEnd('div'); - } - - /** - * Show the content - * - * A list of notices that this user has marked as a favorite - * - * @return void - */ - function showContent() - { - $nl = new FavoritesNoticeList($this->notice, $this); - - $cnt = $nl->show(); - if (0 == $cnt) { - $this->showEmptyListMessage(); - } - - $this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE, - $this->page, 'showfavorites', - array('nickname' => $this->user->nickname)); - } - - function showPageNotice() { - // TRANS: Page notice for show favourites page. - $this->element('p', 'instructions', _('This is a way to share what you like.')); - } -} - -class FavoritesNoticeList extends NoticeList -{ - function newListItem($notice) - { - return new FavoritesNoticeListItem($notice, $this->out); - } -} - -// All handled by superclass -class FavoritesNoticeListItem extends DoFollowListItem -{ -} diff --git a/classes/Fave.php b/classes/Fave.php deleted file mode 100644 index 2076143296..0000000000 --- a/classes/Fave.php +++ /dev/null @@ -1,204 +0,0 @@ - array( - 'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'notice that is the favorite'), - 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'user who likes this notice'), - 'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universally unique identifier, usually a tag URI'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('notice_id', 'user_id'), - 'unique keys' => array( - 'fave_uri_key' => array('uri'), - ), - 'foreign keys' => array( - 'fave_notice_id_fkey' => array('notice', array('notice_id' => 'id')), - 'fave_user_id_fkey' => array('profile', array('user_id' => 'id')), // note: formerly referenced notice.id, but we can now record remote users' favorites - ), - 'indexes' => array( - 'fave_notice_id_idx' => array('notice_id'), - 'fave_user_id_idx' => array('user_id', 'modified'), - 'fave_modified_idx' => array('modified'), - ), - ); - } - - /** - * Save a favorite record. - * @fixme post-author notification should be moved here - * - * @param Profile $profile the local or remote user who likes - * @param Notice $notice the notice that is liked - * @return mixed false on failure, or Fave record on success - */ - static function addNew(Profile $profile, Notice $notice) { - - $fave = null; - - if (Event::handle('StartFavorNotice', array($profile, $notice, &$fave))) { - - $fave = new Fave(); - - $fave->user_id = $profile->id; - $fave->notice_id = $notice->id; - $fave->modified = common_sql_now(); - $fave->uri = self::newURI($fave->user_id, - $fave->notice_id, - $fave->modified); - if (!$fave->insert()) { - common_log_db_error($fave, 'INSERT', __FILE__); - return false; - } - self::blow('fave:list-ids:notice_id:%d', $fave->notice_id); - self::blow('popular'); - - Event::handle('EndFavorNotice', array($profile, $notice)); - } - - return $fave; - } - - function delete($useWhere=false) - { - $profile = Profile::getKV('id', $this->user_id); - $notice = Notice::getKV('id', $this->notice_id); - - $result = null; - - if (Event::handle('StartDisfavorNotice', array($profile, $notice, &$result))) { - - $result = parent::delete($useWhere); - self::blow('fave:list-ids:notice_id:%d', $this->notice_id); - self::blow('popular'); - - if ($result) { - Event::handle('EndDisfavorNotice', array($profile, $notice)); - } - } - - return $result; - } - - function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $own=false, $since_id=0, $max_id=0) - { - $stream = new FaveNoticeStream($user_id, $own); - - return $stream->getNotices($offset, $limit, $since_id, $max_id); - } - - function idStream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $own=false, $since_id=0, $max_id=0) - { - $stream = new FaveNoticeStream($user_id, $own); - - return $stream->getNoticeIds($offset, $limit, $since_id, $max_id); - } - - function asActivity() - { - $notice = Notice::getKV('id', $this->notice_id); - - if (!$notice) { - throw new Exception("Fave for non-existent notice: " . $this->notice_id); - } - - $profile = Profile::getKV('id', $this->user_id); - - if (!$profile) { - throw new Exception("Fave by non-existent profile: " . $this->user_id); - } - - $act = new Activity(); - - $act->verb = ActivityVerb::FAVORITE; - - // FIXME: rationalize this with URL below - - $act->id = $this->getURI(); - - $act->time = strtotime($this->modified); - // TRANS: Activity title when marking a notice as favorite. - $act->title = _("Favor"); - // TRANS: Ntofication given when a user marks a notice as favorite. - // TRANS: %1$s is a user nickname or full name, %2$s is a notice URI. - $act->content = sprintf(_('%1$s marked notice %2$s as a favorite.'), - $profile->getBestName(), - $notice->getUrl()); - - $act->actor = ActivityObject::fromProfile($profile); - $act->objects[] = ActivityObject::fromNotice($notice); - - $url = common_local_url('AtomPubShowFavorite', - array('profile' => $this->user_id, - 'notice' => $this->notice_id)); - - $act->selfLink = $url; - $act->editLink = $url; - - return $act; - } - - /** - * Fetch a stream of favorites by profile - * - * @param integer $profileId Profile that faved - * @param integer $offset Offset from last - * @param integer $limit Number to get - * - * @return mixed stream of faves, use fetch() to iterate - * - * @todo Cache results - * @todo integrate with Fave::stream() - */ - - static function byProfile($profileId, $offset, $limit) - { - $fav = new Fave(); - - $fav->user_id = $profileId; - - $fav->orderBy('modified DESC'); - - $fav->limit($offset, $limit); - - $fav->find(); - - return $fav; - } - - function getURI() - { - if (!empty($this->uri)) { - return $this->uri; - } else { - return self::newURI($this->user_id, $this->notice_id, $this->modified); - } - } - - static function newURI($profile_id, $notice_id, $modified) - { - return TagURI::mint('favor:%d:%d:%s', - $profile_id, - $notice_id, - common_date_iso8601($modified)); - } -} diff --git a/lib/default.php b/lib/default.php index f001049363..59f3cd9985 100644 --- a/lib/default.php +++ b/lib/default.php @@ -294,6 +294,7 @@ $default = array('core' => array( 'AuthCrypt' => array(), 'Cronish' => array(), + 'Favorite' => array(), 'LRDD' => array(), 'StrictTransportSecurity' => array(), ), diff --git a/lib/disfavorform.php b/lib/disfavorform.php deleted file mode 100644 index 51903b6cb2..0000000000 --- a/lib/disfavorform.php +++ /dev/null @@ -1,139 +0,0 @@ -. - * - * @category Form - * @package GNUsocial - * @author Evan Prodromou - * @author Sarven Capadisli - * @author Mikael Nordfeldth - * @copyright 2009 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://www.gnu.org/software/social/ - */ - -if (!defined('GNUSOCIAL')) { exit(1); } - -/** - * Form for disfavoring a notice - * - * @category Form - * @package GNUsocial - * @author Evan Prodromou - * @author Sarven Capadisli - * @author Mikael Nordfeldth - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://www.gnu.org/software/social/ - * - * @see FavorForm - */ -class DisfavorForm extends Form -{ - /** - * Notice to disfavor - */ - var $notice = null; - - /** - * Constructor - * - * @param HTMLOutputter $out output channel - * @param Notice $notice notice to disfavor - */ - function __construct($out=null, $notice=null) - { - parent::__construct($out); - - $this->notice = $notice; - } - - /** - * ID of the form - * - * @return int ID of the form - */ - function id() - { - return 'disfavor-' . $this->notice->id; - } - - /** - * Action of the form - * - * @return string URL of the action - */ - function action() - { - return common_local_url('disfavor'); - } - - /** - * Legend of the Form - * - * @return void - */ - function formLegend() - { - // TRANS: Form legend for removing the favourite status for a favourite notice. - $this->out->element('legend', null, _('Disfavor this notice')); - } - - /** - * Data elements - * - * @return void - */ - - function formData() - { - if (Event::handle('StartDisFavorNoticeForm', array($this, $this->notice))) { - $this->out->hidden('notice-n'.$this->notice->id, - $this->notice->id, - 'notice'); - Event::handle('EndDisFavorNoticeForm', array($this, $this->notice)); - } - } - - /** - * Action elements - * - * @return void - */ - function formActions() - { - $this->out->submit('disfavor-submit-' . $this->notice->id, - // TRANS: Button text for removing the favourite status for a favourite notice. - _m('BUTTON','Disfavor favorite'), - 'submit', - null, - // TRANS: Button title for removing the favourite status for a favourite notice. - _('Remove this notice from your list of favorite notices.')); - } - - /** - * Class of the form. - * - * @return string the form's class - */ - function formClass() - { - return 'form_disfavor ajax'; - } -} diff --git a/lib/favenoticestream.php b/lib/favenoticestream.php deleted file mode 100644 index 6527e5441e..0000000000 --- a/lib/favenoticestream.php +++ /dev/null @@ -1,144 +0,0 @@ -. - * - * @category Stream - * @package StatusNet - * @author Evan Prodromou - * @copyright 2011 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET')) { - // This check helps protect against security problems; - // your code file can't be executed directly from the web. - exit(1); -} - -/** - * Notice stream for favorites - * - * @category Stream - * @package StatusNet - * @author Evan Prodromou - * @copyright 2011 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ -class FaveNoticeStream extends ScopingNoticeStream -{ - function __construct($user_id, $own, $profile = -1) - { - $stream = new RawFaveNoticeStream($user_id, $own); - if ($own) { - $key = 'fave:ids_by_user_own:'.$user_id; - } else { - $key = 'fave:ids_by_user:'.$user_id; - } - if (is_int($profile) && $profile == -1) { - $profile = Profile::current(); - } - parent::__construct(new CachingNoticeStream($stream, $key), - $profile); - } -} - -/** - * Raw notice stream for favorites - * - * @category Stream - * @package StatusNet - * @author Evan Prodromou - * @copyright 2011 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ -class RawFaveNoticeStream extends NoticeStream -{ - protected $user_id; - protected $own; - - function __construct($user_id, $own) - { - $this->user_id = $user_id; - $this->own = $own; - } - - /** - * Note that the sorting for this is by order of *fave* not order of *notice*. - * - * @fixme add since_id, max_id support? - * - * @param $user_id - * @param $own - * @param $offset - * @param $limit - * @param $since_id - * @param $max_id - * @return - */ - function getNoticeIds($offset, $limit, $since_id, $max_id) - { - $fav = new Fave(); - $qry = null; - - if ($this->own) { - $qry = 'SELECT fave.* FROM fave '; - $qry .= 'WHERE fave.user_id = ' . $this->user_id . ' '; - } else { - $qry = 'SELECT fave.* FROM fave '; - $qry .= 'INNER JOIN notice ON fave.notice_id = notice.id '; - $qry .= 'WHERE fave.user_id = ' . $this->user_id . ' '; - $qry .= 'AND notice.is_local != ' . Notice::GATEWAY . ' '; - } - - if ($since_id != 0) { - $qry .= 'AND notice_id > ' . $since_id . ' '; - } - - if ($max_id != 0) { - $qry .= 'AND notice_id <= ' . $max_id . ' '; - } - - // NOTE: we sort by fave time, not by notice time! - - $qry .= 'ORDER BY modified DESC '; - - if (!is_null($offset)) { - $qry .= "LIMIT $limit OFFSET $offset"; - } - - $fav->query($qry); - - $ids = array(); - - while ($fav->fetch()) { - $ids[] = $fav->notice_id; - } - - $fav->free(); - unset($fav); - - return $ids; - } -} - diff --git a/lib/favorform.php b/lib/favorform.php deleted file mode 100644 index cd956f67ff..0000000000 --- a/lib/favorform.php +++ /dev/null @@ -1,138 +0,0 @@ -. - * - * @category Form - * @package GNUsocial - * @author Evan Prodromou - * @author Sarven Capadisli - * @author Mikael Nordfeldth - * @copyright 2009 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://www.gnu.org/software/social/ - */ - -if (!defined('GNUSOCIAL')) { exit(1); } - -/** - * Form for favoring a notice - * - * @category Form - * @package GNUsocial - * @author Evan Prodromou - * @author Sarven Capadisli - * @author Mikael Nordfeldth - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://www.gnu.org/software/social/ - * - * @see DisfavorForm - */ -class FavorForm extends Form -{ - /** - * Notice to favor - */ - var $notice = null; - - /** - * Constructor - * - * @param HTMLOutputter $out output channel - * @param Notice $notice notice to favor - */ - function __construct($out=null, $notice=null) - { - parent::__construct($out); - - $this->notice = $notice; - } - - /** - * ID of the form - * - * @return int ID of the form - */ - function id() - { - return 'favor-' . $this->notice->id; - } - - /** - * Action of the form - * - * @return string URL of the action - */ - function action() - { - return common_local_url('favor'); - } - - /** - * Legend of the Form - * - * @return void - */ - function formLegend() - { - // TRANS: Form legend for adding the favourite status to a notice. - $this->out->element('legend', null, _('Favor this notice')); - } - - /** - * Data elements - * - * @return void - */ - function formData() - { - if (Event::handle('StartFavorNoticeForm', array($this, $this->notice))) { - $this->out->hidden('notice-n'.$this->notice->id, - $this->notice->id, - 'notice'); - Event::handle('EndFavorNoticeForm', array($this, $this->notice)); - } - } - - /** - * Action elements - * - * @return void - */ - function formActions() - { - $this->out->submit('favor-submit-' . $this->notice->id, - // TRANS: Button text for adding the favourite status to a notice. - _m('BUTTON','Favor'), - 'submit', - null, - // TRANS: Button title for adding the favourite status to a notice. - _('Add this notice to your list of favorite notices.')); - } - - /** - * Class of the form. - * - * @return string the form's class - */ - function formClass() - { - return 'form_favor ajax'; - } -} diff --git a/lib/popularnoticesection.php b/lib/popularnoticesection.php deleted file mode 100644 index 2000d302d4..0000000000 --- a/lib/popularnoticesection.php +++ /dev/null @@ -1,83 +0,0 @@ -. - * - * @category Widget - * @package StatusNet - * @author Evan Prodromou - * @copyright 2009,2011 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -/** - * Base class for sections showing lists of notices - * - * These are the widgets that show interesting data about a person - * group, or site. - * - * @category Widget - * @package StatusNet - * @author Evan Prodromou - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ -class PopularNoticeSection extends NoticeSection -{ - protected $viewer; - - function __construct($out, $viewer) - { - parent::__construct($out); - $this->viewer = $viewer; - } - - function getNotices() - { - $stream = new PopularNoticeStream($this->viewer); - return $stream->getNotices(0, NOTICES_PER_SECTION + 1); - } - - function title() - { - // TRANS: Title for favourited notices section. - return _('Popular notices'); - } - - function divId() - { - return 'popular_notices'; - } - - function moreUrl() - { - if (common_config('singleuser', 'enabled')) { - $user = User::singleUser(); - common_local_url('showfavorites', array('nickname' => - $user->nickname)); - } else { - return common_local_url('favorited'); - } - } -} diff --git a/lib/popularnoticestream.php b/lib/popularnoticestream.php deleted file mode 100644 index eeba541238..0000000000 --- a/lib/popularnoticestream.php +++ /dev/null @@ -1,92 +0,0 @@ -. - * - * @category Popular - * @package StatusNet - * @author Evan Prodromou - * @copyright 2011 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET')) { - // This check helps protect against security problems; - // your code file can't be executed directly from the web. - exit(1); -} - -/** - * Stream of notices sorted by popularity - * - * @category Popular - * @package StatusNet - * @author Evan Prodromou - * @copyright 2011 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ - -class PopularNoticeStream extends ScopingNoticeStream -{ - function __construct($profile=null) - { - parent::__construct(new CachingNoticeStream(new RawPopularNoticeStream(), - 'popular', - false), - $profile); - } -} - -class RawPopularNoticeStream extends NoticeStream -{ - function getNoticeIds($offset, $limit, $since_id, $max_id) - { - $weightexpr = common_sql_weight('modified', common_config('popular', 'dropoff')); - $cutoff = sprintf("modified > '%s'", - common_sql_date(time() - common_config('popular', 'cutoff'))); - - $fave = new Fave(); - $fave->selectAdd(); - $fave->selectAdd('notice_id'); - $fave->selectAdd("$weightexpr as weight"); - $fave->whereAdd($cutoff); - $fave->orderBy('weight DESC'); - $fave->groupBy('notice_id'); - - if (!is_null($offset)) { - $fave->limit($offset, $limit); - } - - // FIXME: $since_id, $max_id are ignored - - $ids = array(); - - if ($fave->find()) { - while ($fave->fetch()) { - $ids[] = $fave->notice_id; - } - } - - return $ids; - } -} - diff --git a/lib/router.php b/lib/router.php index 0a835ce4d8..54683e986e 100644 --- a/lib/router.php +++ b/lib/router.php @@ -143,7 +143,7 @@ class Router $main = array('login', 'logout', 'register', 'subscribe', 'unsubscribe', 'cancelsubscription', 'approvesub', 'confirmaddress', 'recoverpassword', - 'invite', 'favor', 'disfavor', 'sup', + 'invite', 'sup', 'block', 'unblock', 'subedit', 'groupblock', 'groupunblock', 'sandbox', 'unsandbox', @@ -454,11 +454,6 @@ class Router 'format' => '(xml|json)')); // START qvitter API additions - - $m->connect('api/statuses/favs/:id.:format', - array('action' => 'ApiStatusesFavs', - 'id' => '[0-9]+', - 'format' => '(xml|json)')); $m->connect('api/attachment/:id.:format', array('action' => 'ApiAttachment', @@ -595,39 +590,6 @@ class Router $m->connect('api/account/rate_limit_status.:format', array('action' => 'ApiAccountRateLimitStatus')); - // favorites - - $m->connect('api/favorites/create.:format', - array('action' => 'ApiFavoriteCreate', - 'format' => '(xml|json)')); - - $m->connect('api/favorites/destroy.:format', - array('action' => 'ApiFavoriteDestroy', - 'format' => '(xml|json)')); - - $m->connect('api/favorites/list.:format', - array('action' => 'ApiTimelineFavorites', - 'format' => '(xml|json|rss|atom|as)')); - - $m->connect('api/favorites/:id.:format', - array('action' => 'ApiTimelineFavorites', - 'id' => Nickname::INPUT_FMT, - 'format' => '(xml|json|rss|atom|as)')); - - $m->connect('api/favorites.:format', - array('action' => 'ApiTimelineFavorites', - 'format' => '(xml|json|rss|atom|as)')); - - $m->connect('api/favorites/create/:id.:format', - array('action' => 'ApiFavoriteCreate', - 'id' => '[0-9]+', - 'format' => '(xml|json)')); - - $m->connect('api/favorites/destroy/:id.:format', - array('action' => 'ApiFavoriteDestroy', - 'id' => '[0-9]+', - 'format' => '(xml|json)')); - // blocks $m->connect('api/blocks/create/:id.:format', @@ -921,16 +883,12 @@ class Router 'nickname' => $nickname)); } - foreach (array('all', 'replies', 'favorites') as $a) { + foreach (array('all', 'replies') as $a) { $m->connect($a.'/rss', array('action' => $a.'rss', 'nickname' => $nickname)); } - $m->connect('favorites', - array('action' => 'showfavorites', - 'nickname' => $nickname)); - $m->connect('avatar', array('action' => 'avatarbynickname', 'nickname' => $nickname)); @@ -1011,11 +969,8 @@ class Router $m->connect('', array('action' => 'public')); $m->connect('rss', array('action' => 'publicrss')); $m->connect('featuredrss', array('action' => 'featuredrss')); - $m->connect('favoritedrss', array('action' => 'favoritedrss')); $m->connect('featured/', array('action' => 'featured')); $m->connect('featured', array('action' => 'featured')); - $m->connect('favorited/', array('action' => 'favorited')); - $m->connect('favorited', array('action' => 'favorited')); $m->connect('rsd.xml', array('action' => 'rsd')); foreach (array('subscriptions', 'subscribers', @@ -1096,16 +1051,12 @@ class Router array('nickname' => Nickname::DISPLAY_FMT)); } - foreach (array('all', 'replies', 'favorites') as $a) { + foreach (array('all', 'replies') as $a) { $m->connect(':nickname/'.$a.'/rss', array('action' => $a.'rss'), array('nickname' => Nickname::DISPLAY_FMT)); } - $m->connect(':nickname/favorites', - array('action' => 'showfavorites'), - array('nickname' => Nickname::DISPLAY_FMT)); - $m->connect(':nickname/avatar', array('action' => 'avatarbynickname'), array('nickname' => Nickname::DISPLAY_FMT)); @@ -1155,15 +1106,6 @@ class Router array('action' => 'AtomPubSubscriptionFeed'), array('subscriber' => '[0-9]+')); - $m->connect('api/statusnet/app/favorites/:profile/:notice.atom', - array('action' => 'AtomPubShowFavorite'), - array('profile' => '[0-9]+', - 'notice' => '[0-9]+')); - - $m->connect('api/statusnet/app/favorites/:profile.atom', - array('action' => 'AtomPubFavoriteFeed'), - array('profile' => '[0-9]+')); - $m->connect('api/statusnet/app/memberships/:profile/:group.atom', array('action' => 'AtomPubShowMembership'), array('profile' => '[0-9]+', diff --git a/plugins/Favorite/FavoritePlugin.php b/plugins/Favorite/FavoritePlugin.php new file mode 100644 index 0000000000..9fbaf258c4 --- /dev/null +++ b/plugins/Favorite/FavoritePlugin.php @@ -0,0 +1,111 @@ +. + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +/** + * @package UI + * @maintainer Mikael Nordfeldth + */ +class FavoritePlugin extends Plugin +{ + public function onRouterInitialized(URLMapper $m) + { + // Web UI actions + $m->connect('main/favor', array('action' => 'favor')); + $m->connect('main/disfavor', array('action' => 'disfavor')); + + if (common_config('singleuser', 'enabled')) { + $nickname = User::singleUserNickname(); + + $m->connect('favorites', + array('action' => 'showfavorites', + 'nickname' => $nickname)); + $m->connect('favoritesrss', + array('action' => 'favoritesrss', + 'nickname' => $nickname)); + } else { + $m->connect('favoritedrss', array('action' => 'favoritedrss')); + $m->connect('favorited/', array('action' => 'favorited')); + $m->connect('favorited', array('action' => 'favorited')); + + $m->connect(':nickname/favorites', + array('action' => 'showfavorites'), + array('nickname' => Nickname::DISPLAY_FMT)); + $m->connect(':nickname/favorites/rss', + array('action' => 'favoritesrss'), + array('nickname' => Nickname::DISPLAY_FMT)); + } + + // Favorites for API + $m->connect('api/favorites/create.:format', + array('action' => 'ApiFavoriteCreate', + 'format' => '(xml|json)')); + $m->connect('api/favorites/destroy.:format', + array('action' => 'ApiFavoriteDestroy', + 'format' => '(xml|json)')); + $m->connect('api/favorites/list.:format', + array('action' => 'ApiTimelineFavorites', + 'format' => '(xml|json|rss|atom|as)')); + $m->connect('api/favorites/:id.:format', + array('action' => 'ApiTimelineFavorites', + 'id' => Nickname::INPUT_FMT, + 'format' => '(xml|json|rss|atom|as)')); + $m->connect('api/favorites.:format', + array('action' => 'ApiTimelineFavorites', + 'format' => '(xml|json|rss|atom|as)')); + $m->connect('api/favorites/create/:id.:format', + array('action' => 'ApiFavoriteCreate', + 'id' => '[0-9]+', + 'format' => '(xml|json)')); + $m->connect('api/favorites/destroy/:id.:format', + array('action' => 'ApiFavoriteDestroy', + 'id' => '[0-9]+', + 'format' => '(xml|json)')); + + // AtomPub API + $m->connect('api/statusnet/app/favorites/:profile/:notice.atom', + array('action' => 'AtomPubShowFavorite'), + array('profile' => '[0-9]+', + 'notice' => '[0-9]+')); + + $m->connect('api/statusnet/app/favorites/:profile.atom', + array('action' => 'AtomPubFavoriteFeed'), + array('profile' => '[0-9]+')); + + // Required for qvitter API + $m->connect('api/statuses/favs/:id.:format', + array('action' => 'ApiStatusesFavs', + 'id' => '[0-9]+', + 'format' => '(xml|json)')); + } + + public function onPluginVersion(array &$versions) + { + $versions[] = array('name' => 'Favorite', + 'version' => GNUSOCIAL_VERSION, + 'author' => 'Mikael Nordfeldth', + 'homepage' => 'http://gnu.io/', + 'rawdescription' => + // TRANS: Plugin description. + _m('Favorites (likes) using ActivityStreams.')); + + return true; + } +} diff --git a/plugins/Favorite/actions/apifavoritecreate.php b/plugins/Favorite/actions/apifavoritecreate.php new file mode 100644 index 0000000000..607fb76edc --- /dev/null +++ b/plugins/Favorite/actions/apifavoritecreate.php @@ -0,0 +1,173 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Zach Copley + * @copyright 2009 StatusNet, Inc. + * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Favorites the status specified in the ID parameter as the authenticating user. + * Returns the favorite status when successful. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ +class ApiFavoriteCreateAction extends ApiAuthAction +{ + var $notice = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + */ + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + $this->notice = Notice::getKV($this->arg('id')); + if ($this->notice->repeat_of != '' ) { + common_log(LOG_DEBUG, 'Trying to Fave '.$this->notice->id.', repeat of '.$this->notice->repeat_of); + common_log(LOG_DEBUG, 'Will Fave '.$this->notice->repeat_of.' instead'); + $real_notice_id = $this->notice->repeat_of; + $this->notice = Notice::getKV($real_notice_id); + } + + return true; + } + + /** + * Handle the request + * + * Check the format and show the user info + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + // TRANS: Client error. POST is a HTTP command. It should not be translated. + _('This method requires a POST.'), + 400, + $this->format + ); + return; + } + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError( + // TRANS: Client error displayed when coming across a non-supported API method. + _('API method not found.'), + 404, + $this->format + ); + return; + } + + if (empty($this->notice)) { + $this->clientError( + // TRANS: Client error displayed when requesting a status with a non-existing ID. + _('No status found with that ID.'), + 404, + $this->format + ); + return; + } + + // Note: Twitter lets you fave things repeatedly via API. + + if ($this->user->hasFave($this->notice)) { + $this->clientError( + // TRANS: Client error displayed when trying to mark a notice favourite that already is a favourite. + _('This status is already a favorite.'), + 403, + $this->format + ); + return; + } + + $fave = Fave::addNew($this->user->getProfile(), $this->notice); + + if (empty($fave)) { + $this->clientError( + // TRANS: Client error displayed when marking a notice as favourite fails. + _('Could not create favorite.'), + 403, + $this->format + ); + return; + } + + $this->notify($fave, $this->notice, $this->user); + $this->user->blowFavesCache(); + + if ($this->format == 'xml') { + $this->showSingleXmlStatus($this->notice); + } elseif ($this->format == 'json') { + $this->show_single_json_status($this->notice); + } + } + + /** + * Notify the author of the favorite that the user likes their notice + * + * @param Favorite $fave the favorite in question + * @param Notice $notice the notice that's been faved + * @param User $user the user doing the favoriting + * + * @return void + */ + function notify($fave, $notice, $user) + { + $other = User::getKV('id', $notice->profile_id); + if ($other && $other->id != $user->id) { + if ($other->email && $other->emailnotifyfav) { + mail_notify_fave($other, $user, $notice); + } + // XXX: notify by IM + // XXX: notify by SMS + } + } +} diff --git a/plugins/Favorite/actions/apifavoritedestroy.php b/plugins/Favorite/actions/apifavoritedestroy.php new file mode 100644 index 0000000000..02f81cf4df --- /dev/null +++ b/plugins/Favorite/actions/apifavoritedestroy.php @@ -0,0 +1,154 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Zach Copley + * @copyright 2009 StatusNet, Inc. + * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Un-favorites the status specified in the ID parameter as the authenticating user. + * Returns the un-favorited status in the requested format when successful. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ +class ApiFavoriteDestroyAction extends ApiAuthAction +{ + var $notice = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + */ + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + $this->notice = Notice::getKV($this->arg('id')); + if ($this->notice->repeat_of != '' ) { + common_log(LOG_DEBUG, 'Trying to unFave '.$this->notice->id); + common_log(LOG_DEBUG, 'Will unFave '.$this->notice->repeat_of.' instead'); + $real_notice_id = $this->notice->repeat_of; + $this->notice = Notice::getKV($real_notice_id); + } + + return true; + } + + /** + * Handle the request + * + * Check the format and show the user info + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + // TRANS: Client error. POST is a HTTP command. It should not be translated. + _('This method requires a POST.'), + 400, + $this->format + ); + return; + } + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError( + // TRANS: Client error displayed when coming across a non-supported API method. + _('API method not found.'), + 404, + $this->format + ); + return; + } + + if (empty($this->notice)) { + $this->clientError( + // TRANS: Client error displayed when trying to remove a favourite with an invalid ID. + _('No status found with that ID.'), + 404, + $this->format + ); + return; + } + + $fave = new Fave(); + $fave->user_id = $this->user->id; + $fave->notice_id = $this->notice->id; + + if (!$fave->find(true)) { + $this->clientError( + // TRANS: Client error displayed when trying to remove a favourite that was not a favourite. + _('That status is not a favorite.'), + 403, + $this->favorite + ); + return; + } + + $result = $fave->delete(); + + if (!$result) { + common_log_db_error($fave, 'DELETE', __FILE__); + $this->clientError( + // TRANS: Client error displayed when removing a favourite has failed. + _('Could not delete favorite.'), + 404, + $this->format + ); + return; + } + + $this->user->blowFavesCache(); + + if ($this->format == 'xml') { + $this->showSingleXmlStatus($this->notice); + } elseif ($this->format == 'json') { + $this->show_single_json_status($this->notice); + } + } +} diff --git a/plugins/Favorite/actions/apistatusesfavs.php b/plugins/Favorite/actions/apistatusesfavs.php new file mode 100644 index 0000000000..01a5d3014e --- /dev/null +++ b/plugins/Favorite/actions/apistatusesfavs.php @@ -0,0 +1,136 @@ +. + * + * @category API + * @package GNUsocial + * @author Hannes Mannerheim + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://www.gnu.org/software/social/ + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +/** + * Show up to 100 favs of a notice + * + */ +class ApiStatusesFavsAction extends ApiAuthAction +{ + const MAXCOUNT = 100; + + var $original = null; // Notice object for which to retrieve favs + var $cnt = self::MAXCOUNT; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + */ + protected function prepare(array $args=array()) + { + parent::prepare($args); + + if ($this->format !== 'json') { + $this->clientError('This method currently only serves JSON.', 415); + } + + $id = $this->trimmed('id'); + + $this->original = Notice::getKV('id', $id); + + if (!($this->original instanceof Notice)) { + // TRANS: Client error displayed trying to display redents of a non-exiting notice. + $this->clientError(_('No such notice.'), 400); + } + + $cnt = $this->trimmed('count'); + + if (empty($cnt) || !is_integer($cnt)) { + $cnt = 100; + } else { + $this->cnt = min((int)$cnt, self::MAXCOUNT); + } + + return true; + } + + /** + * Handle the request + * + * Get favs and return them as json object + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + protected function handle() + { + parent::handle(); + + $fave = new Fave(); + $fave->selectAdd(); + $fave->selectAdd('user_id'); + $fave->notice_id = $this->original->id; + $fave->orderBy('modified'); + if (!is_null($this->cnt)) { + $fave->limit(0, $this->cnt); + } + + $ids = $fave->fetchAll('user_id'); + + // get nickname and profile image + $ids_with_profile_data = array(); + $i=0; + foreach($ids as $id) { + $profile = Profile::getKV('id', $id); + $ids_with_profile_data[$i]['user_id'] = $id; + $ids_with_profile_data[$i]['nickname'] = $profile->nickname; + $ids_with_profile_data[$i]['fullname'] = $profile->fullname; + $ids_with_profile_data[$i]['profileurl'] = $profile->profileurl; + $profile = new Profile(); + $profile->id = $id; + $avatarurl = $profile->avatarUrl(24); + $ids_with_profile_data[$i]['avatarurl'] = $avatarurl; + $i++; + } + + $this->initDocument('json'); + $this->showJsonObjects($ids_with_profile_data); + $this->endDocument('json'); + } + + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + + function isReadOnly($args) + { + return true; + } +} diff --git a/plugins/Favorite/actions/apitimelinefavorites.php b/plugins/Favorite/actions/apitimelinefavorites.php new file mode 100644 index 0000000000..13dc842447 --- /dev/null +++ b/plugins/Favorite/actions/apitimelinefavorites.php @@ -0,0 +1,263 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Zach Copley + * @copyright 2009-2010 StatusNet, Inc. + * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Returns the 20 most recent favorite notices for the authenticating user or user + * specified by the ID parameter in the requested format. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ +class ApiTimelineFavoritesAction extends ApiBareAuthAction +{ + var $notices = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + */ + protected function prepare(array $args=array()) + { + parent::prepare($args); + + $this->target = $this->getTargetProfile($this->arg('id')); + + if (!($this->target instanceof Profile)) { + // TRANS: Client error displayed when requesting most recent favourite notices by a user for a non-existing user. + $this->clientError(_('No such user.'), 404); + } + + $this->notices = $this->getNotices(); + + return true; + } + + /** + * Handle the request + * + * Just show the notices + * + * @return void + */ + protected function handle() + { + parent::handle(); + $this->showTimeline(); + } + + /** + * Show the timeline of notices + * + * @return void + */ + function showTimeline() + { + $sitename = common_config('site', 'name'); + $title = sprintf( + // TRANS: Title for timeline of most recent favourite notices by a user. + // TRANS: %1$s is the StatusNet sitename, %2$s is a user nickname. + _('%1$s / Favorites from %2$s'), + $sitename, + $this->target->nickname + ); + + $taguribase = TagURI::base(); + $id = "tag:$taguribase:Favorites:" . $this->target->id; + + $subtitle = sprintf( + // TRANS: Subtitle for timeline of most recent favourite notices by a user. + // TRANS: %1$s is the StatusNet sitename, %2$s is a user's full name, + // TRANS: %3$s is a user nickname. + _('%1$s updates favorited by %2$s / %3$s.'), + $sitename, + $this->target->getBestName(), + $this->target->nickname + ); + + $logo = $this->target->avatarUrl(AVATAR_PROFILE_SIZE); + $link = common_local_url('showfavorites', + array('nickname' => $this->target->nickname)); + $self = $this->getSelfUri(); + + switch($this->format) { + case 'xml': + $this->showXmlTimeline($this->notices); + break; + case 'rss': + $this->showRssTimeline( + $this->notices, + $title, + $link, + $subtitle, + null, + $logo, + $self + ); + break; + case 'atom': + header('Content-Type: application/atom+xml; charset=utf-8'); + + $atom = new AtomNoticeFeed($this->auth_user); + + $atom->setId($id); + $atom->setTitle($title); + $atom->setSubtitle($subtitle); + $atom->setLogo($logo); + $atom->setUpdated('now'); + + $atom->addLink($link); + $atom->setSelfLink($self); + + $atom->addEntryFromNotices($this->notices); + + $this->raw($atom->getString()); + break; + case 'json': + $this->showJsonTimeline($this->notices); + break; + case 'as': + header('Content-Type: ' . ActivityStreamJSONDocument::CONTENT_TYPE); + $doc = new ActivityStreamJSONDocument($this->auth_user); + $doc->setTitle($title); + $doc->addLink($link,'alternate', 'text/html'); + $doc->addItemsFromNotices($this->notices); + $this->raw($doc->asString()); + break; + default: + // TRANS: Client error displayed when coming across a non-supported API method. + $this->clientError(_('API method not found.'), 404); + } + } + + /** + * Get notices + * + * @return array notices + */ + function getNotices() + { + $notices = array(); + + common_debug("since id = " . $this->since_id . " max id = " . $this->max_id); + + if (!empty($this->auth_user) && $this->auth_user->id == $this->target->id) { + $notice = $this->target->favoriteNotices( + true, + ($this->page-1) * $this->count, + $this->count, + $this->since_id, + $this->max_id + ); + } else { + $notice = $this->target->favoriteNotices( + false, + ($this->page-1) * $this->count, + $this->count, + $this->since_id, + $this->max_id + ); + } + + while ($notice->fetch()) { + $notices[] = clone($notice); + } + + return $notices; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest notice in the stream + */ + function lastModified() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + return strtotime($this->notices[0]->created); + } + + return null; + } + + /** + * An entity tag for this stream + * + * Returns an Etag based on the action name, language, user ID, and + * timestamps of the first and last notice in the timeline + * + * @return string etag + */ + function etag() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + + $last = count($this->notices) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_user_cache_hash($this->auth_user), + common_language(), + $this->target->id, + strtotime($this->notices[0]->created), + strtotime($this->notices[$last]->created)) + ) + . '"'; + } + + return null; + } +} diff --git a/plugins/Favorite/actions/atompubfavoritefeed.php b/plugins/Favorite/actions/atompubfavoritefeed.php new file mode 100644 index 0000000000..837a9da3e9 --- /dev/null +++ b/plugins/Favorite/actions/atompubfavoritefeed.php @@ -0,0 +1,372 @@ +. + * + * @category AtomPub + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Feed of ActivityStreams 'favorite' actions + * + * @category AtomPub + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class AtompubfavoritefeedAction extends ApiAuthAction +{ + private $_profile = null; + private $_faves = null; + + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + function prepare($argarray) + { + parent::prepare($argarray); + + $this->_profile = Profile::getKV('id', $this->trimmed('profile')); + + if (empty($this->_profile)) { + // TRANS: Client exception thrown when requesting a favorite feed for a non-existing profile. + throw new ClientException(_('No such profile.'), 404); + } + + $offset = ($this->page-1) * $this->count; + $limit = $this->count + 1; + + $this->_faves = Fave::byProfile($this->_profile->id, + $offset, + $limit); + + return true; + } + + /** + * Handler method + * + * @param array $argarray is ignored since it's now passed in in prepare() + * + * @return void + */ + function handle($argarray=null) + { + parent::handle($argarray); + + switch ($_SERVER['REQUEST_METHOD']) { + case 'HEAD': + case 'GET': + $this->showFeed(); + break; + case 'POST': + $this->addFavorite(); + break; + default: + // TRANS: Client exception thrown when using an unsupported HTTP method. + throw new ClientException(_('HTTP method not supported.'), 405); + return; + } + + return; + } + + /** + * Show a feed of favorite activity streams objects + * + * @return void + */ + function showFeed() + { + header('Content-Type: application/atom+xml; charset=utf-8'); + + $url = common_local_url('AtomPubFavoriteFeed', + array('profile' => $this->_profile->id)); + + $feed = new Atom10Feed(true); + + $feed->addNamespace('activity', + 'http://activitystrea.ms/spec/1.0/'); + + $feed->addNamespace('poco', + 'http://portablecontacts.net/spec/1.0'); + + $feed->addNamespace('media', + 'http://purl.org/syndication/atommedia'); + + $feed->id = $url; + + $feed->setUpdated('now'); + + $feed->addAuthor($this->_profile->getBestName(), + $this->_profile->getURI()); + + // TRANS: Title for Atom favorites feed. + // TRANS: %s is a user nickname. + $feed->setTitle(sprintf(_("%s favorites"), + $this->_profile->getBestName())); + + // TRANS: Subtitle for Atom favorites feed. + // TRANS: %1$s is a user nickname, %2$s is the StatusNet sitename. + $feed->setSubtitle(sprintf(_('Notices %1$s has favorited on %2$s'), + $this->_profile->getBestName(), + common_config('site', 'name'))); + + $feed->addLink(common_local_url('showfavorites', + array('nickname' => + $this->_profile->nickname))); + + $feed->addLink($url, + array('rel' => 'self', + 'type' => 'application/atom+xml')); + + // If there's more... + + if ($this->page > 1) { + $feed->addLink($url, + array('rel' => 'first', + 'type' => 'application/atom+xml')); + + $feed->addLink(common_local_url('AtomPubFavoriteFeed', + array('profile' => + $this->_profile->id), + array('page' => + $this->page - 1)), + array('rel' => 'prev', + 'type' => 'application/atom+xml')); + } + + if ($this->_faves->N > $this->count) { + + $feed->addLink(common_local_url('AtomPubFavoriteFeed', + array('profile' => + $this->_profile->id), + array('page' => + $this->page + 1)), + array('rel' => 'next', + 'type' => 'application/atom+xml')); + } + + $i = 0; + + while ($this->_faves->fetch()) { + + // We get one more than needed; skip that one + + $i++; + + if ($i > $this->count) { + break; + } + + $act = $this->_faves->asActivity(); + $feed->addEntryRaw($act->asString(false, false, false)); + } + + $this->raw($feed->getString()); + } + + /** + * add a new favorite + * + * @return void + */ + function addFavorite() + { + // XXX: Refactor this; all the same for atompub + + if (empty($this->auth_user) || + $this->auth_user->id != $this->_profile->id) { + // TRANS: Client exception thrown when trying to set a favorite for another user. + throw new ClientException(_("Cannot add someone else's". + " subscription."), 403); + } + + $xml = file_get_contents('php://input'); + + $dom = DOMDocument::loadXML($xml); + + if ($dom->documentElement->namespaceURI != Activity::ATOM || + $dom->documentElement->localName != 'entry') { + // TRANS: Client error displayed when not using an Atom entry. + throw new ClientException(_('Atom post must be an Atom entry.')); + return; + } + + $activity = new Activity($dom->documentElement); + + $fave = null; + + if (Event::handle('StartAtomPubNewActivity', array(&$activity))) { + + if ($activity->verb != ActivityVerb::FAVORITE) { + // TRANS: Client exception thrown when trying use an incorrect activity verb for the Atom pub method. + throw new ClientException(_('Can only handle favorite activities.')); + return; + } + + $note = $activity->objects[0]; + + if (!in_array($note->type, array(ActivityObject::NOTE, + ActivityObject::BLOGENTRY, + ActivityObject::STATUS))) { + // TRANS: Client exception thrown when trying favorite an object that is not a notice. + throw new ClientException(_('Can only fave notices.')); + return; + } + + $notice = Notice::getKV('uri', $note->id); + + if (empty($notice)) { + // XXX: import from listed URL or something + // TRANS: Client exception thrown when trying favorite a notice without content. + throw new ClientException(_('Unknown notice.')); + } + + $old = Fave::pkeyGet(array('user_id' => $this->auth_user->id, + 'notice_id' => $notice->id)); + + if (!empty($old)) { + // TRANS: Client exception thrown when trying favorite an already favorited notice. + throw new ClientException(_('Already a favorite.')); + } + + $profile = $this->auth_user->getProfile(); + + $fave = Fave::addNew($profile, $notice); + + if (!empty($fave)) { + $this->_profile->blowFavesCache(); + $this->notify($fave, $notice, $this->auth_user); + } + + Event::handle('EndAtomPubNewActivity', array($activity, $fave)); + } + + if (!empty($fave)) { + $act = $fave->asActivity(); + + header('Content-Type: application/atom+xml; charset=utf-8'); + header('Content-Location: ' . $act->selfLink); + + $this->startXML(); + $this->raw($act->asString(true, true, true)); + $this->endXML(); + } + } + + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + function isReadOnly($args) + { + if ($_SERVER['REQUEST_METHOD'] == 'GET' || + $_SERVER['REQUEST_METHOD'] == 'HEAD') { + return true; + } else { + return false; + } + } + + /** + * Return last modified, if applicable. + * + * MAY override + * + * @return string last modified http header + */ + function lastModified() + { + // For comparison with If-Last-Modified + // If not applicable, return null + return null; + } + + /** + * Return etag, if applicable. + * + * MAY override + * + * @return string etag http header + */ + function etag() + { + return null; + } + + /** + * Does this require authentication? + * + * @return boolean true if delete, else false + */ + function requiresAuth() + { + if ($_SERVER['REQUEST_METHOD'] == 'GET' || + $_SERVER['REQUEST_METHOD'] == 'HEAD') { + return false; + } else { + return true; + } + } + + /** + * Notify the author of the favorite that the user likes their notice + * + * @param Favorite $fave the favorite in question + * @param Notice $notice the notice that's been faved + * @param User $user the user doing the favoriting + * + * @return void + */ + function notify($fave, $notice, $user) + { + $other = User::getKV('id', $notice->profile_id); + if ($other && $other->id != $user->id) { + if ($other->email && $other->emailnotifyfav) { + mail_notify_fave($other, $user, $notice); + } + // XXX: notify by IM + // XXX: notify by SMS + } + } +} diff --git a/plugins/Favorite/actions/atompubshowfavorite.php b/plugins/Favorite/actions/atompubshowfavorite.php new file mode 100644 index 0000000000..436e88e693 --- /dev/null +++ b/plugins/Favorite/actions/atompubshowfavorite.php @@ -0,0 +1,222 @@ +. + * + * @category AtomPub + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Show a single favorite in Atom Activity Streams format. + * + * Can also be used to delete a favorite. + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class AtompubshowfavoriteAction extends ApiAuthAction +{ + private $_profile = null; + private $_notice = null; + private $_fave = null; + + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + function prepare($argarray) + { + parent::prepare($argarray); + + $profileId = $this->trimmed('profile'); + $noticeId = $this->trimmed('notice'); + + $this->_profile = Profile::getKV('id', $profileId); + + if (empty($this->_profile)) { + // TRANS: Client exception. + throw new ClientException(_('No such profile.'), 404); + } + + $this->_notice = Notice::getKV('id', $noticeId); + + if (empty($this->_notice)) { + // TRANS: Client exception thrown when referencing a non-existing notice. + throw new ClientException(_('No such notice.'), 404); + } + + $this->_fave = Fave::pkeyGet(array('user_id' => $profileId, + 'notice_id' => $noticeId)); + + if (empty($this->_fave)) { + // TRANS: Client exception thrown when referencing a non-existing favorite. + throw new ClientException(_('No such favorite.'), 404); + } + + return true; + } + + /** + * Handler method + * + * @param array $argarray is ignored since it's now passed in in prepare() + * + * @return void + */ + function handle($argarray=null) + { + parent::handle($argarray); + + switch ($_SERVER['REQUEST_METHOD']) { + case GET: + case HEAD: + $this->showFave(); + break; + case DELETE: + $this->deleteFave(); + break; + default: + // TRANS: Client exception thrown using an unsupported HTTP method. + throw new ClientException(_('HTTP method not supported.'), + 405); + } + return true; + } + + /** + * Show a single favorite, in ActivityStreams format + * + * @return void + */ + function showFave() + { + $activity = $this->_fave->asActivity(); + + header('Content-Type: application/atom+xml; charset=utf-8'); + + $this->startXML(); + $this->raw($activity->asString(true, true, true)); + $this->endXML(); + + return; + } + + /** + * Delete the favorite + * + * @return void + */ + function deleteFave() + { + if (empty($this->auth_user) || + $this->auth_user->id != $this->_profile->id) { + // TRANS: Client exception thrown when trying to remove a favorite notice of another user. + throw new ClientException(_("Cannot delete someone else's". + " favorite."), 403); + } + + $this->_fave->delete(); + + return; + } + + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + function isReadOnly($args) + { + if ($_SERVER['REQUEST_METHOD'] == 'GET' || + $_SERVER['REQUEST_METHOD'] == 'HEAD') { + return true; + } else { + return false; + } + } + + /** + * Return last modified, if applicable. + * + * MAY override + * + * @return string last modified http header + */ + function lastModified() + { + return max(strtotime($this->_profile->modified), + strtotime($this->_notice->modified), + strtotime($this->_fave->modified)); + } + + /** + * Return etag, if applicable. + * + * MAY override + * + * @return string etag http header + */ + function etag() + { + $mtime = strtotime($this->_fave->modified); + + return 'W/"' . implode(':', array('AtomPubShowFavorite', + $this->_profile->id, + $this->_notice->id, + $mtime)) . '"'; + } + + /** + * Does this require authentication? + * + * @return boolean true if delete, else false + */ + function requiresAuth() + { + if ($_SERVER['REQUEST_METHOD'] == 'GET' || + $_SERVER['REQUEST_METHOD'] == 'HEAD') { + return false; + } else { + return true; + } + } +} diff --git a/plugins/Favorite/actions/disfavor.php b/plugins/Favorite/actions/disfavor.php new file mode 100644 index 0000000000..ef9ee1ee2b --- /dev/null +++ b/plugins/Favorite/actions/disfavor.php @@ -0,0 +1,91 @@ + + * @author Robin Millette + * @author Mikael Nordfeldth + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://www.gnu.org/software/social/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2008, 2009, StatusNet, Inc. + * + * 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 . + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +/** + * DisfavorAction class. + * + * @category Action + * @package GNUsocial + * @author Evan Prodromou + * @author Robin Millette + * @author Mikael Nordfeldth + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://www.gnu.org/software/social/ + */ +class DisfavorAction extends FormAction +{ + public function showForm($msg=null, $success=false) + { + if ($success) { + common_redirect(common_local_url('showfavorites', + array('nickname' => $this->scoped->nickname)), 303); + } + parent::showForm($msg, $success); + } + + protected function handlePost() + { + $id = $this->trimmed('notice'); + $notice = Notice::getKV($id); + if (!$notice instanceof Notice) { + $this->serverError(_('Notice not found')); + } + + $fave = new Fave(); + $fave->user_id = $this->scoped->id; + $fave->notice_id = $notice->id; + if (!$fave->find(true)) { + throw new NoResultException($fave); + } + $result = $fave->delete(); + if (!$result) { + common_log_db_error($fave, 'DELETE', __FILE__); + // TRANS: Server error displayed when removing a favorite from the database fails. + $this->serverError(_('Could not delete favorite.')); + } + $this->scoped->blowFavesCache(); + if (StatusNet::isAjax()) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + // TRANS: Title for page on which favorites can be added. + $this->element('title', null, _('Add to favorites')); + $this->elementEnd('head'); + $this->elementStart('body'); + $favor = new FavorForm($this, $notice); + $favor->show(); + $this->elementEnd('body'); + $this->endHTML(); + exit; + } + } +} diff --git a/plugins/Favorite/actions/favor.php b/plugins/Favorite/actions/favor.php new file mode 100644 index 0000000000..8c19f9da5c --- /dev/null +++ b/plugins/Favorite/actions/favor.php @@ -0,0 +1,106 @@ + + * @author Robin Millette + * @author Mikael Nordfeldth + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://www.gnu.org/software/social/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2008, 2009, StatusNet, Inc. + * + * 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 . + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +require_once INSTALLDIR.'/lib/mail.php'; + +/** + * FavorAction class. + * + * @category Action + * @package GNUsocial + * @author Evan Prodromou + * @author Robin Millette + * @author Mikael Nordfeldth + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://www.gnu.org/software/social/ + */ +class FavorAction extends FormAction +{ + protected $needPost = true; + + protected function handlePost() + { + $id = $this->trimmed('notice'); + $notice = Notice::getKV($id); + if (!($notice instanceof Notice)) { + $this->serverError(_('Notice not found')); + } + if ($this->scoped->hasFave($notice)) { + // TRANS: Client error displayed when trying to mark a notice as favorite that already is a favorite. + $this->clientError(_('This notice is already a favorite!')); + } + $fave = Fave::addNew($this->scoped, $notice); + if (!$fave) { + // TRANS: Server error displayed when trying to mark a notice as favorite fails in the database. + $this->serverError(_('Could not create favorite.')); + } + $this->notify($notice, $this->scoped->getUser()); + $this->scoped->blowFavesCache(); + if (StatusNet::isAjax()) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + // TRANS: Page title for page on which favorite notices can be unfavourited. + $this->element('title', null, _('Disfavor favorite.')); + $this->elementEnd('head'); + $this->elementStart('body'); + $disfavor = new DisFavorForm($this, $notice); + $disfavor->show(); + $this->elementEnd('body'); + $this->endHTML(); + exit; + } + common_redirect(common_local_url('showfavorites', + array('nickname' => $this->scoped->nickname)), + 303); + } + + /** + * Notifies a user when their notice is favorited. + * + * @param class $notice favorited notice + * @param class $user user declaring a favorite + * + * @return void + */ + function notify($notice, $user) + { + $other = User::getKV('id', $notice->profile_id); + if ($other && $other->id != $user->id) { + if ($other->email && $other->emailnotifyfav) { + mail_notify_fave($other, $user, $notice); + } + // XXX: notify by IM + // XXX: notify by SMS + } + } +} diff --git a/plugins/Favorite/actions/favorited.php b/plugins/Favorite/actions/favorited.php new file mode 100644 index 0000000000..ff4a99cd60 --- /dev/null +++ b/plugins/Favorite/actions/favorited.php @@ -0,0 +1,189 @@ +. + * + * @category Public + * @package StatusNet + * @author Zach Copley + * @author Evan Prodromou + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/publicgroupnav.php'; +require_once INSTALLDIR.'/lib/noticelist.php'; + +/** + * List of popular notices + * + * We provide a list of the most popular notices. Popularity + * is measured by + * + * @category Personal + * @package StatusNet + * @author Zach Copley + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ +class FavoritedAction extends Action +{ + var $page = null; + + /** + * Title of the page + * + * @return string Title of the page + */ + + function title() + { + if ($this->page == 1) { + // TRANS: Page title for first page of favorited notices. + return _('Popular notices'); + } else { + // TRANS: Page title for all but first page of favorited notices. + // TRANS: %d is the page number being displayed. + return sprintf(_('Popular notices, page %d'), $this->page); + } + } + + /** + * Instructions for use + * + * @return instructions for use + */ + function getInstructions() + { + // TRANS: Description on page displaying favorited notices. + return _('The most popular notices on the site right now.'); + } + + /** + * Is this page read-only? + * + * @return boolean true + */ + function isReadOnly($args) + { + return true; + } + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + * @todo move queries from showContent() to here + */ + function prepare($args) + { + parent::prepare($args); + $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; + + common_set_returnto($this->selfUrl()); + + return true; + } + + /** + * Handle request + * + * Shows a page with list of favorite notices + * + * @param array $args $_REQUEST args; handled in prepare() + * + * @return void + */ + function handle($args) + { + parent::handle($args); + + $this->showPage(); + } + + /** + * Show the page notice + * + * Shows instructions for the page + * + * @return void + */ + function showPageNotice() + { + $instr = $this->getInstructions(); + $output = common_markup_to_html($instr); + + $this->elementStart('div', 'instructions'); + $this->raw($output); + $this->elementEnd('div'); + } + + function showEmptyList() + { + // TRANS: Text displayed instead of a list when a site does not yet have any favourited notices. + $message = _('Favorite notices appear on this page but no one has favorited one yet.') . ' '; + + if (common_logged_in()) { + // TRANS: Additional text displayed instead of a list when a site does not yet have any favourited notices for logged in users. + $message .= _('Be the first to add a notice to your favorites by clicking the fave button next to any notice you like.'); + } + else { + // TRANS: Additional text displayed instead of a list when a site does not yet have any favourited notices for not logged in users. + // TRANS: %%action.register%% is a registration link. "[link text](link)" is Mark Down. Do not change the formatting. + $message .= _('Why not [register an account](%%action.register%%) and be the first to add a notice to your favorites!'); + } + + $this->elementStart('div', 'guide'); + $this->raw(common_markup_to_html($message)); + $this->elementEnd('div'); + } + + /** + * Content area + * + * Shows the list of popular notices + * + * @return void + */ + function showContent() + { + $stream = new PopularNoticeStream(Profile::current()); + $notice = $stream->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE+1); + + $nl = new NoticeList($notice, $this); + + $cnt = $nl->show(); + + if ($cnt == 0) { + $this->showEmptyList(); + } + + $this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE, + $this->page, 'favorited'); + } +} diff --git a/plugins/Favorite/actions/favoritesrss.php b/plugins/Favorite/actions/favoritesrss.php new file mode 100644 index 0000000000..de901b0b46 --- /dev/null +++ b/plugins/Favorite/actions/favoritesrss.php @@ -0,0 +1,131 @@ + + * @author Robin Millette + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2008, 2009, StatusNet, Inc. + * + * 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 . + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/rssaction.php'; + +/** + * RSS feed for user favorites action class. + * + * Formatting of RSS handled by Rss10Action + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @author Robin Millette + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ +class FavoritesrssAction extends Rss10Action +{ + /** The user whose favorites to display */ + + var $user = null; + + /** + * Find the user to display by supplied nickname + * + * @param array $args Arguments from $_REQUEST + * + * @return boolean success + */ + function prepare($args) + { + parent::prepare($args); + + $nickname = $this->trimmed('nickname'); + $this->user = User::getKV('nickname', $nickname); + + if (!$this->user) { + // TRANS: Client error displayed when trying to get the RSS feed with favorites of a user that does not exist. + $this->clientError(_('No such user.')); + } else { + $this->notices = $this->getNotices($this->limit); + return true; + } + } + + /** + * Get notices + * + * @param integer $limit max number of notices to return + * + * @return array notices + */ + function getNotices($limit=0) + { + $user = $this->user; + $notice = $user->favoriteNotices(false, 0, $limit); + $notices = array(); + while ($notice->fetch()) { + $notices[] = clone($notice); + } + return $notices; + } + + /** + * Get channel. + * + * @return array associative array on channel information + */ + function getChannel() + { + $user = $this->user; + $c = array('url' => common_local_url('favoritesrss', + array('nickname' => + $user->nickname)), + // TRANS: Title of RSS feed with favourite notices of a user. + // TRANS: %s is a user's nickname. + 'title' => sprintf(_("%s's favorite notices"), $user->nickname), + 'link' => common_local_url('showfavorites', + array('nickname' => + $user->nickname)), + // TRANS: Desciption of RSS feed with favourite notices of a user. + // TRANS: %1$s is a user's nickname, %2$s is the name of the StatusNet site. + 'description' => sprintf(_('Updates favored by %1$s on %2$s!'), + $user->nickname, common_config('site', 'name'))); + return $c; + } + + /** + * Get image. + * + * @return void + */ + function getImage() + { + return null; + } + +} diff --git a/plugins/Favorite/actions/showfavorites.php b/plugins/Favorite/actions/showfavorites.php new file mode 100644 index 0000000000..0e028416da --- /dev/null +++ b/plugins/Favorite/actions/showfavorites.php @@ -0,0 +1,261 @@ +. + * + * @category Personal + * @package StatusNet + * @author Evan Prodromou + * @copyright 2008-2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/personalgroupnav.php'; +require_once INSTALLDIR.'/lib/noticelist.php'; +require_once INSTALLDIR.'/lib/feedlist.php'; + +/** + * List of replies + * + * @category Personal + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ +class ShowfavoritesAction extends Action +{ + /** User we're getting the faves of */ + var $user = null; + /** Page of the faves we're on */ + var $page = null; + + /** + * Is this a read-only page? + * + * @return boolean true + */ + function isReadOnly($args) + { + return true; + } + + /** + * Title of the page + * + * Includes name of user and page number. + * + * @return string title of page + */ + function title() + { + if ($this->page == 1) { + // TRANS: Title for first page of favourite notices of a user. + // TRANS: %s is the user for whom the favourite notices are displayed. + return sprintf(_('%s\'s favorite notices'), $this->user->nickname); + } else { + // TRANS: Title for all but the first page of favourite notices of a user. + // TRANS: %1$s is the user for whom the favourite notices are displayed, %2$d is the page number. + return sprintf(_('%1$s\'s favorite notices, page %2$d'), + $this->user->nickname, + $this->page); + } + } + + /** + * Prepare the object + * + * Check the input values and initialize the object. + * Shows an error page on bad input. + * + * @param array $args $_REQUEST data + * + * @return boolean success flag + */ + function prepare($args) + { + parent::prepare($args); + + $nickname = common_canonical_nickname($this->arg('nickname')); + + $this->user = User::getKV('nickname', $nickname); + + if (!$this->user) { + // TRANS: Client error displayed when trying to display favourite notices for a non-existing user. + $this->clientError(_('No such user.')); + } + + $this->page = $this->trimmed('page'); + + if (!$this->page) { + $this->page = 1; + } + + common_set_returnto($this->selfUrl()); + + $cur = common_current_user(); + + if (!empty($cur) && $cur->id == $this->user->id) { + + // Show imported/gateway notices as well as local if + // the user is looking at their own favorites + + $this->notice = $this->user->favoriteNotices(true, ($this->page-1)*NOTICES_PER_PAGE, + NOTICES_PER_PAGE + 1); + } else { + $this->notice = $this->user->favoriteNotices(false, ($this->page-1)*NOTICES_PER_PAGE, + NOTICES_PER_PAGE + 1); + } + + if (empty($this->notice)) { + // TRANS: Server error displayed when favourite notices could not be retrieved from the database. + $this->serverError(_('Could not retrieve favorite notices.')); + } + + if($this->page > 1 && $this->notice->N == 0){ + // TRANS: Server error when page not found (404) + $this->serverError(_('No such page.'),$code=404); + } + + return true; + } + + /** + * Handle a request + * + * Just show the page. All args already handled. + * + * @param array $args $_REQUEST data + * + * @return void + */ + function handle($args) + { + parent::handle($args); + $this->showPage(); + } + + /** + * Feeds for the section + * + * @return array Feed objects to show + */ + function getFeeds() + { + return array(new Feed(Feed::JSON, + common_local_url('ApiTimelineFavorites', + array( + 'id' => $this->user->nickname, + 'format' => 'as')), + // TRANS: Feed link text. %s is a username. + sprintf(_('Feed for favorites of %s (Activity Streams JSON)'), + $this->user->nickname)), + new Feed(Feed::RSS1, + common_local_url('favoritesrss', + array('nickname' => $this->user->nickname)), + // TRANS: Feed link text. %s is a username. + sprintf(_('Feed for favorites of %s (RSS 1.0)'), + $this->user->nickname)), + new Feed(Feed::RSS2, + common_local_url('ApiTimelineFavorites', + array( + 'id' => $this->user->nickname, + 'format' => 'rss')), + // TRANS: Feed link text. %s is a username. + sprintf(_('Feed for favorites of %s (RSS 2.0)'), + $this->user->nickname)), + new Feed(Feed::ATOM, + common_local_url('ApiTimelineFavorites', + array( + 'id' => $this->user->nickname, + 'format' => 'atom')), + // TRANS: Feed link text. %s is a username. + sprintf(_('Feed for favorites of %s (Atom)'), + $this->user->nickname))); + } + + function showEmptyListMessage() + { + if (common_logged_in()) { + $current_user = common_current_user(); + if ($this->user->id === $current_user->id) { + // TRANS: Text displayed instead of favourite notices for the current logged in user that has no favourites. + $message = _('You haven\'t chosen any favorite notices yet. Click the fave button on notices you like to bookmark them for later or shed a spotlight on them.'); + } else { + // TRANS: Text displayed instead of favourite notices for a user that has no favourites while logged in. + // TRANS: %s is a username. + $message = sprintf(_('%s hasn\'t added any favorite notices yet. Post something interesting they would add to their favorites :)'), $this->user->nickname); + } + } + else { + // TRANS: Text displayed instead of favourite notices for a user that has no favourites while not logged in. + // TRANS: %s is a username, %%%%action.register%%%% is a link to the user registration page. + // TRANS: (link text)[link] is a Mark Down link. + $message = sprintf(_('%s hasn\'t added any favorite notices yet. Why not [register an account](%%%%action.register%%%%) and then post something interesting they would add to their favorites :)'), $this->user->nickname); + } + + $this->elementStart('div', 'guide'); + $this->raw(common_markup_to_html($message)); + $this->elementEnd('div'); + } + + /** + * Show the content + * + * A list of notices that this user has marked as a favorite + * + * @return void + */ + function showContent() + { + $nl = new FavoritesNoticeList($this->notice, $this); + + $cnt = $nl->show(); + if (0 == $cnt) { + $this->showEmptyListMessage(); + } + + $this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE, + $this->page, 'showfavorites', + array('nickname' => $this->user->nickname)); + } + + function showPageNotice() { + // TRANS: Page notice for show favourites page. + $this->element('p', 'instructions', _('This is a way to share what you like.')); + } +} + +class FavoritesNoticeList extends NoticeList +{ + function newListItem($notice) + { + return new FavoritesNoticeListItem($notice, $this->out); + } +} + +// All handled by superclass +class FavoritesNoticeListItem extends DoFollowListItem +{ +} diff --git a/plugins/Favorite/classes/Fave.php b/plugins/Favorite/classes/Fave.php new file mode 100644 index 0000000000..2076143296 --- /dev/null +++ b/plugins/Favorite/classes/Fave.php @@ -0,0 +1,204 @@ + array( + 'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'notice that is the favorite'), + 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'user who likes this notice'), + 'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universally unique identifier, usually a tag URI'), + 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), + ), + 'primary key' => array('notice_id', 'user_id'), + 'unique keys' => array( + 'fave_uri_key' => array('uri'), + ), + 'foreign keys' => array( + 'fave_notice_id_fkey' => array('notice', array('notice_id' => 'id')), + 'fave_user_id_fkey' => array('profile', array('user_id' => 'id')), // note: formerly referenced notice.id, but we can now record remote users' favorites + ), + 'indexes' => array( + 'fave_notice_id_idx' => array('notice_id'), + 'fave_user_id_idx' => array('user_id', 'modified'), + 'fave_modified_idx' => array('modified'), + ), + ); + } + + /** + * Save a favorite record. + * @fixme post-author notification should be moved here + * + * @param Profile $profile the local or remote user who likes + * @param Notice $notice the notice that is liked + * @return mixed false on failure, or Fave record on success + */ + static function addNew(Profile $profile, Notice $notice) { + + $fave = null; + + if (Event::handle('StartFavorNotice', array($profile, $notice, &$fave))) { + + $fave = new Fave(); + + $fave->user_id = $profile->id; + $fave->notice_id = $notice->id; + $fave->modified = common_sql_now(); + $fave->uri = self::newURI($fave->user_id, + $fave->notice_id, + $fave->modified); + if (!$fave->insert()) { + common_log_db_error($fave, 'INSERT', __FILE__); + return false; + } + self::blow('fave:list-ids:notice_id:%d', $fave->notice_id); + self::blow('popular'); + + Event::handle('EndFavorNotice', array($profile, $notice)); + } + + return $fave; + } + + function delete($useWhere=false) + { + $profile = Profile::getKV('id', $this->user_id); + $notice = Notice::getKV('id', $this->notice_id); + + $result = null; + + if (Event::handle('StartDisfavorNotice', array($profile, $notice, &$result))) { + + $result = parent::delete($useWhere); + self::blow('fave:list-ids:notice_id:%d', $this->notice_id); + self::blow('popular'); + + if ($result) { + Event::handle('EndDisfavorNotice', array($profile, $notice)); + } + } + + return $result; + } + + function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $own=false, $since_id=0, $max_id=0) + { + $stream = new FaveNoticeStream($user_id, $own); + + return $stream->getNotices($offset, $limit, $since_id, $max_id); + } + + function idStream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $own=false, $since_id=0, $max_id=0) + { + $stream = new FaveNoticeStream($user_id, $own); + + return $stream->getNoticeIds($offset, $limit, $since_id, $max_id); + } + + function asActivity() + { + $notice = Notice::getKV('id', $this->notice_id); + + if (!$notice) { + throw new Exception("Fave for non-existent notice: " . $this->notice_id); + } + + $profile = Profile::getKV('id', $this->user_id); + + if (!$profile) { + throw new Exception("Fave by non-existent profile: " . $this->user_id); + } + + $act = new Activity(); + + $act->verb = ActivityVerb::FAVORITE; + + // FIXME: rationalize this with URL below + + $act->id = $this->getURI(); + + $act->time = strtotime($this->modified); + // TRANS: Activity title when marking a notice as favorite. + $act->title = _("Favor"); + // TRANS: Ntofication given when a user marks a notice as favorite. + // TRANS: %1$s is a user nickname or full name, %2$s is a notice URI. + $act->content = sprintf(_('%1$s marked notice %2$s as a favorite.'), + $profile->getBestName(), + $notice->getUrl()); + + $act->actor = ActivityObject::fromProfile($profile); + $act->objects[] = ActivityObject::fromNotice($notice); + + $url = common_local_url('AtomPubShowFavorite', + array('profile' => $this->user_id, + 'notice' => $this->notice_id)); + + $act->selfLink = $url; + $act->editLink = $url; + + return $act; + } + + /** + * Fetch a stream of favorites by profile + * + * @param integer $profileId Profile that faved + * @param integer $offset Offset from last + * @param integer $limit Number to get + * + * @return mixed stream of faves, use fetch() to iterate + * + * @todo Cache results + * @todo integrate with Fave::stream() + */ + + static function byProfile($profileId, $offset, $limit) + { + $fav = new Fave(); + + $fav->user_id = $profileId; + + $fav->orderBy('modified DESC'); + + $fav->limit($offset, $limit); + + $fav->find(); + + return $fav; + } + + function getURI() + { + if (!empty($this->uri)) { + return $this->uri; + } else { + return self::newURI($this->user_id, $this->notice_id, $this->modified); + } + } + + static function newURI($profile_id, $notice_id, $modified) + { + return TagURI::mint('favor:%d:%d:%s', + $profile_id, + $notice_id, + common_date_iso8601($modified)); + } +} diff --git a/plugins/Favorite/forms/disfavor.php b/plugins/Favorite/forms/disfavor.php new file mode 100644 index 0000000000..51903b6cb2 --- /dev/null +++ b/plugins/Favorite/forms/disfavor.php @@ -0,0 +1,139 @@ +. + * + * @category Form + * @package GNUsocial + * @author Evan Prodromou + * @author Sarven Capadisli + * @author Mikael Nordfeldth + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://www.gnu.org/software/social/ + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +/** + * Form for disfavoring a notice + * + * @category Form + * @package GNUsocial + * @author Evan Prodromou + * @author Sarven Capadisli + * @author Mikael Nordfeldth + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://www.gnu.org/software/social/ + * + * @see FavorForm + */ +class DisfavorForm extends Form +{ + /** + * Notice to disfavor + */ + var $notice = null; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param Notice $notice notice to disfavor + */ + function __construct($out=null, $notice=null) + { + parent::__construct($out); + + $this->notice = $notice; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + function id() + { + return 'disfavor-' . $this->notice->id; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + function action() + { + return common_local_url('disfavor'); + } + + /** + * Legend of the Form + * + * @return void + */ + function formLegend() + { + // TRANS: Form legend for removing the favourite status for a favourite notice. + $this->out->element('legend', null, _('Disfavor this notice')); + } + + /** + * Data elements + * + * @return void + */ + + function formData() + { + if (Event::handle('StartDisFavorNoticeForm', array($this, $this->notice))) { + $this->out->hidden('notice-n'.$this->notice->id, + $this->notice->id, + 'notice'); + Event::handle('EndDisFavorNoticeForm', array($this, $this->notice)); + } + } + + /** + * Action elements + * + * @return void + */ + function formActions() + { + $this->out->submit('disfavor-submit-' . $this->notice->id, + // TRANS: Button text for removing the favourite status for a favourite notice. + _m('BUTTON','Disfavor favorite'), + 'submit', + null, + // TRANS: Button title for removing the favourite status for a favourite notice. + _('Remove this notice from your list of favorite notices.')); + } + + /** + * Class of the form. + * + * @return string the form's class + */ + function formClass() + { + return 'form_disfavor ajax'; + } +} diff --git a/plugins/Favorite/forms/favor.php b/plugins/Favorite/forms/favor.php new file mode 100644 index 0000000000..cd956f67ff --- /dev/null +++ b/plugins/Favorite/forms/favor.php @@ -0,0 +1,138 @@ +. + * + * @category Form + * @package GNUsocial + * @author Evan Prodromou + * @author Sarven Capadisli + * @author Mikael Nordfeldth + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://www.gnu.org/software/social/ + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +/** + * Form for favoring a notice + * + * @category Form + * @package GNUsocial + * @author Evan Prodromou + * @author Sarven Capadisli + * @author Mikael Nordfeldth + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://www.gnu.org/software/social/ + * + * @see DisfavorForm + */ +class FavorForm extends Form +{ + /** + * Notice to favor + */ + var $notice = null; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param Notice $notice notice to favor + */ + function __construct($out=null, $notice=null) + { + parent::__construct($out); + + $this->notice = $notice; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + function id() + { + return 'favor-' . $this->notice->id; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + function action() + { + return common_local_url('favor'); + } + + /** + * Legend of the Form + * + * @return void + */ + function formLegend() + { + // TRANS: Form legend for adding the favourite status to a notice. + $this->out->element('legend', null, _('Favor this notice')); + } + + /** + * Data elements + * + * @return void + */ + function formData() + { + if (Event::handle('StartFavorNoticeForm', array($this, $this->notice))) { + $this->out->hidden('notice-n'.$this->notice->id, + $this->notice->id, + 'notice'); + Event::handle('EndFavorNoticeForm', array($this, $this->notice)); + } + } + + /** + * Action elements + * + * @return void + */ + function formActions() + { + $this->out->submit('favor-submit-' . $this->notice->id, + // TRANS: Button text for adding the favourite status to a notice. + _m('BUTTON','Favor'), + 'submit', + null, + // TRANS: Button title for adding the favourite status to a notice. + _('Add this notice to your list of favorite notices.')); + } + + /** + * Class of the form. + * + * @return string the form's class + */ + function formClass() + { + return 'form_favor ajax'; + } +} diff --git a/plugins/Favorite/lib/favenoticestream.php b/plugins/Favorite/lib/favenoticestream.php new file mode 100644 index 0000000000..6527e5441e --- /dev/null +++ b/plugins/Favorite/lib/favenoticestream.php @@ -0,0 +1,144 @@ +. + * + * @category Stream + * @package StatusNet + * @author Evan Prodromou + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Notice stream for favorites + * + * @category Stream + * @package StatusNet + * @author Evan Prodromou + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class FaveNoticeStream extends ScopingNoticeStream +{ + function __construct($user_id, $own, $profile = -1) + { + $stream = new RawFaveNoticeStream($user_id, $own); + if ($own) { + $key = 'fave:ids_by_user_own:'.$user_id; + } else { + $key = 'fave:ids_by_user:'.$user_id; + } + if (is_int($profile) && $profile == -1) { + $profile = Profile::current(); + } + parent::__construct(new CachingNoticeStream($stream, $key), + $profile); + } +} + +/** + * Raw notice stream for favorites + * + * @category Stream + * @package StatusNet + * @author Evan Prodromou + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class RawFaveNoticeStream extends NoticeStream +{ + protected $user_id; + protected $own; + + function __construct($user_id, $own) + { + $this->user_id = $user_id; + $this->own = $own; + } + + /** + * Note that the sorting for this is by order of *fave* not order of *notice*. + * + * @fixme add since_id, max_id support? + * + * @param $user_id + * @param $own + * @param $offset + * @param $limit + * @param $since_id + * @param $max_id + * @return + */ + function getNoticeIds($offset, $limit, $since_id, $max_id) + { + $fav = new Fave(); + $qry = null; + + if ($this->own) { + $qry = 'SELECT fave.* FROM fave '; + $qry .= 'WHERE fave.user_id = ' . $this->user_id . ' '; + } else { + $qry = 'SELECT fave.* FROM fave '; + $qry .= 'INNER JOIN notice ON fave.notice_id = notice.id '; + $qry .= 'WHERE fave.user_id = ' . $this->user_id . ' '; + $qry .= 'AND notice.is_local != ' . Notice::GATEWAY . ' '; + } + + if ($since_id != 0) { + $qry .= 'AND notice_id > ' . $since_id . ' '; + } + + if ($max_id != 0) { + $qry .= 'AND notice_id <= ' . $max_id . ' '; + } + + // NOTE: we sort by fave time, not by notice time! + + $qry .= 'ORDER BY modified DESC '; + + if (!is_null($offset)) { + $qry .= "LIMIT $limit OFFSET $offset"; + } + + $fav->query($qry); + + $ids = array(); + + while ($fav->fetch()) { + $ids[] = $fav->notice_id; + } + + $fav->free(); + unset($fav); + + return $ids; + } +} + diff --git a/plugins/Favorite/lib/popularnoticesection.php b/plugins/Favorite/lib/popularnoticesection.php new file mode 100644 index 0000000000..2000d302d4 --- /dev/null +++ b/plugins/Favorite/lib/popularnoticesection.php @@ -0,0 +1,83 @@ +. + * + * @category Widget + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009,2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Base class for sections showing lists of notices + * + * These are the widgets that show interesting data about a person + * group, or site. + * + * @category Widget + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ +class PopularNoticeSection extends NoticeSection +{ + protected $viewer; + + function __construct($out, $viewer) + { + parent::__construct($out); + $this->viewer = $viewer; + } + + function getNotices() + { + $stream = new PopularNoticeStream($this->viewer); + return $stream->getNotices(0, NOTICES_PER_SECTION + 1); + } + + function title() + { + // TRANS: Title for favourited notices section. + return _('Popular notices'); + } + + function divId() + { + return 'popular_notices'; + } + + function moreUrl() + { + if (common_config('singleuser', 'enabled')) { + $user = User::singleUser(); + common_local_url('showfavorites', array('nickname' => + $user->nickname)); + } else { + return common_local_url('favorited'); + } + } +} diff --git a/plugins/Favorite/lib/popularnoticestream.php b/plugins/Favorite/lib/popularnoticestream.php new file mode 100644 index 0000000000..eeba541238 --- /dev/null +++ b/plugins/Favorite/lib/popularnoticestream.php @@ -0,0 +1,92 @@ +. + * + * @category Popular + * @package StatusNet + * @author Evan Prodromou + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Stream of notices sorted by popularity + * + * @category Popular + * @package StatusNet + * @author Evan Prodromou + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class PopularNoticeStream extends ScopingNoticeStream +{ + function __construct($profile=null) + { + parent::__construct(new CachingNoticeStream(new RawPopularNoticeStream(), + 'popular', + false), + $profile); + } +} + +class RawPopularNoticeStream extends NoticeStream +{ + function getNoticeIds($offset, $limit, $since_id, $max_id) + { + $weightexpr = common_sql_weight('modified', common_config('popular', 'dropoff')); + $cutoff = sprintf("modified > '%s'", + common_sql_date(time() - common_config('popular', 'cutoff'))); + + $fave = new Fave(); + $fave->selectAdd(); + $fave->selectAdd('notice_id'); + $fave->selectAdd("$weightexpr as weight"); + $fave->whereAdd($cutoff); + $fave->orderBy('weight DESC'); + $fave->groupBy('notice_id'); + + if (!is_null($offset)) { + $fave->limit($offset, $limit); + } + + // FIXME: $since_id, $max_id are ignored + + $ids = array(); + + if ($fave->find()) { + while ($fave->fetch()) { + $ids[] = $fave->notice_id; + } + } + + return $ids; + } +} +