From d684a07fd93fe46c6195b0aeb56fcf01812ef60d Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 27 Sep 2010 15:01:03 -0700 Subject: [PATCH 01/30] Move hasFave() to Profile --- classes/Profile.php | 35 +++++++++++++++++++++++++++++++++++ classes/User.php | 33 ++------------------------------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/classes/Profile.php b/classes/Profile.php index 8f86795504..1a54489ed8 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -474,6 +474,41 @@ class Profile extends Memcached_DataObject return $cnt; } + function hasFave($notice) + { + $cache = common_memcache(); + + // XXX: Kind of a hack. + + if (!empty($cache)) { + // This is the stream of favorite notices, in rev chron + // order. This forces it into cache. + + $ids = Fave::stream($this->id, 0, NOTICE_CACHE_WINDOW); + + // If it's in the list, then it's a fave + + if (in_array($notice->id, $ids)) { + return true; + } + + // If we're not past the end of the cache window, + // then the cache has all available faves, so this one + // is not a fave. + + if (count($ids) < NOTICE_CACHE_WINDOW) { + return false; + } + + // Otherwise, cache doesn't have all faves; + // fall through to the default + } + + $fave = Fave::pkeyGet(array('user_id' => $this->id, + 'notice_id' => $notice->id)); + return ((is_null($fave)) ? false : true); + } + function faveCount() { $c = common_memcache(); diff --git a/classes/User.php b/classes/User.php index b85192b29c..27299e62e0 100644 --- a/classes/User.php +++ b/classes/User.php @@ -412,37 +412,8 @@ class User extends Memcached_DataObject function hasFave($notice) { - $cache = common_memcache(); - - // XXX: Kind of a hack. - - if ($cache) { - // This is the stream of favorite notices, in rev chron - // order. This forces it into cache. - - $ids = Fave::stream($this->id, 0, NOTICE_CACHE_WINDOW); - - // If it's in the list, then it's a fave - - if (in_array($notice->id, $ids)) { - return true; - } - - // If we're not past the end of the cache window, - // then the cache has all available faves, so this one - // is not a fave. - - if (count($ids) < NOTICE_CACHE_WINDOW) { - return false; - } - - // Otherwise, cache doesn't have all faves; - // fall through to the default - } - - $fave = Fave::pkeyGet(array('user_id' => $this->id, - 'notice_id' => $notice->id)); - return ((is_null($fave)) ? false : true); + $profile = $this->getProfile(); + return $profile->hasFave($notice); } function mutuallySubscribed($other) From 78a711d5566e7bf2347199f034641f44b36f6e09 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 28 Sep 2010 15:46:14 -0700 Subject: [PATCH 02/30] Move blowFavesCache() to Profile --- classes/Profile.php | 14 ++++++++++++++ classes/User.php | 11 +---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/classes/Profile.php b/classes/Profile.php index 1a54489ed8..9753b278ec 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -552,6 +552,20 @@ class Profile extends Memcached_DataObject return $cnt; } + function blowFavesCache() + { + $cache = common_memcache(); + if ($cache) { + // Faves don't happen chronologically, so we need to blow + // ;last cache, too + $cache->delete(common_cache_key('fave:ids_by_user:'.$this->id)); + $cache->delete(common_cache_key('fave:ids_by_user:'.$this->id.';last')); + $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id)); + $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id.';last')); + } + $this->blowFaveCount(); + } + function blowSubscriberCount() { $c = common_memcache(); diff --git a/classes/User.php b/classes/User.php index 27299e62e0..e784fd9e9a 100644 --- a/classes/User.php +++ b/classes/User.php @@ -482,17 +482,8 @@ class User extends Memcached_DataObject function blowFavesCache() { - $cache = common_memcache(); - if ($cache) { - // Faves don't happen chronologically, so we need to blow - // ;last cache, too - $cache->delete(common_cache_key('fave:ids_by_user:'.$this->id)); - $cache->delete(common_cache_key('fave:ids_by_user:'.$this->id.';last')); - $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id)); - $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id.';last')); - } $profile = $this->getProfile(); - $profile->blowFaveCount(); + $profile->blowFavesCache(); } function getSelfTags() From ab1d8c4c56dcc4eaa799eb662123bb285cc1aea2 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 28 Sep 2010 15:56:11 -0700 Subject: [PATCH 03/30] Add Start/EndShowNoticeItem event hooks to single notice page --- actions/shownotice.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/actions/shownotice.php b/actions/shownotice.php index 86df5f9f30..c8e9cfe66a 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -314,10 +314,14 @@ class SingleNoticeItem extends NoticeListItem function show() { $this->showStart(); - $this->showNotice(); - $this->showNoticeAttachments(); - $this->showNoticeInfo(); - $this->showNoticeOptions(); + if (Event::handle('StartShowNoticeItem', array($this))) { + $this->showNotice(); + $this->showNoticeAttachments(); + $this->showNoticeInfo(); + $this->showNoticeOptions(); + Event::handle('EndShowNoticeItem', array($this)); + } + $this->showEnd(); } From f42d4b18551eee1a8a8541047465d5beedb24bc2 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 28 Sep 2010 17:09:34 -0700 Subject: [PATCH 04/30] New eventsi: Start/EndShowNoticeOptions and Start/EndShowFaveForm --- EVENTS.txt | 16 ++++++++++++++-- lib/noticelist.php | 38 ++++++++++++++++++++++---------------- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index fd2bdb9d56..7b6ad40eb9 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -258,10 +258,22 @@ EndShowExportData: just after showing the
with export data (feeds) - $action: action object being shown StartShowNoticeItem: just before showing the notice item -- $action: action object being shown +- $item: The NoticeListItem object being shown EndShowNoticeItem: just after showing the notice item -- $action: action object being shown +- $item: the NoticeListItem object being shown + +StartShowNoticeOptions: just before showing notice options like fave, repeat, etc. +- $item: the NoticeListItem object being shown + +EndShowNoticeOptions: just after showing notice options like fave, repeat, etc. +- $item: the NoticeListItem object being shown + +StartShowFaveForm: just before showing the fave form +- $item: the NoticeListItem object being shown + +EndShowFaveForm: just after showing the fave form +- $item: the NoticeListItem object being shown StartShowPageNotice: just before showing the page notice (instructions or error) - $action: action object being shown diff --git a/lib/noticelist.php b/lib/noticelist.php index 529d6a3f90..cc460005ad 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -236,14 +236,17 @@ class NoticeListItem extends Widget function showNoticeOptions() { - $user = common_current_user(); - if ($user) { - $this->out->elementStart('div', 'notice-options'); - $this->showFaveForm(); - $this->showReplyLink(); - $this->showRepeatForm(); - $this->showDeleteLink(); - $this->out->elementEnd('div'); + if (Event::handle('StartShowNoticeOptions', array($this))) { + $user = common_current_user(); + if ($user) { + $this->out->elementStart('div', 'notice-options'); + $this->showFaveForm(); + $this->showReplyLink(); + $this->showRepeatForm(); + $this->showDeleteLink(); + $this->out->elementEnd('div'); + } + Event::handle('EndShowNoticeOptions', array($this)); } } @@ -270,15 +273,18 @@ class NoticeListItem extends Widget function showFaveForm() { - $user = common_current_user(); - if ($user) { - if ($user->hasFave($this->notice)) { - $disfavor = new DisfavorForm($this->out, $this->notice); - $disfavor->show(); - } else { - $favor = new FavorForm($this->out, $this->notice); - $favor->show(); + if (Event::handle('StartShowFaveForm', array($this))) { + $user = common_current_user(); + if ($user) { + if ($user->hasFave($this->notice)) { + $disfavor = new DisfavorForm($this->out, $this->notice); + $disfavor->show(); + } else { + $favor = new FavorForm($this->out, $this->notice); + $favor->show(); + } } + Event::handle('EndShowFaveForm', array($this)); } } From daeb220b132ff5f901ab28e1ea961d08482256fe Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 28 Sep 2010 17:17:09 -0700 Subject: [PATCH 05/30] Initial plugin for allowing anonymous favoriting --- plugins/AnonymousFave/AnonymousFavePlugin.php | 198 ++++++++++++++++++ plugins/AnonymousFave/anondisfavor.php | 132 ++++++++++++ plugins/AnonymousFave/anondisfavorform.php | 74 +++++++ plugins/AnonymousFave/anonfavor.php | 125 +++++++++++ plugins/AnonymousFave/anonfavorform.php | 74 +++++++ 5 files changed, 603 insertions(+) create mode 100644 plugins/AnonymousFave/AnonymousFavePlugin.php create mode 100644 plugins/AnonymousFave/anondisfavor.php create mode 100644 plugins/AnonymousFave/anondisfavorform.php create mode 100644 plugins/AnonymousFave/anonfavor.php create mode 100644 plugins/AnonymousFave/anonfavorform.php diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php new file mode 100644 index 0000000000..3dccaf538a --- /dev/null +++ b/plugins/AnonymousFave/AnonymousFavePlugin.php @@ -0,0 +1,198 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Zach Copley + * @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); +} + +define('ANONYMOUS_FAVE_PLUGIN_VERSION', '0.1'); + +/** + * Anonymous Fave plugin to allow anonymous (not logged in) users + * to favorite notices + * + * @category Plugin + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class AnonymousFavePlugin extends Plugin { + + function onArgsInitialize() { + // We always want a session because we're tracking anon users + common_ensure_session(); + } + + function onEndShowHTML($action) + { + if (!common_logged_in()) { + // Set a place to return to when submitting forms + common_set_returnto($action->selfUrl()); + } + } + + function onEndShowScripts($action) + { + // Setup ajax calls for favoriting. Usually this is only done when + // a user is logged in. + $action->inlineScript('SN.U.NoticeFavor();'); + } + + function onAutoload($cls) + { + $dir = dirname(__FILE__); + + switch ($cls) { + case 'AnonFavorAction': + include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; + return false; + case 'AnonDisFavorAction': + include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; + return false; + case 'AnonFavorForm': + include_once $dir . '/anonfavorform.php'; + return false; + case 'AnonDisFavorForm': + include_once $dir . '/anondisfavorform.php'; + return false; + default: + return true; + } + } + + function onStartInitializeRouter($m) { + + $m->connect('main/anonfavor', array('action' => 'AnonFavor')); + $m->connect('main/anondisfavor', array('action' => 'AnonDisFavor')); + + return true; + } + + function onStartShowNoticeOptions($item) { + + if (!common_logged_in()) { + $item->out->elementStart('div', 'notice-options'); + $item->showFaveForm(); + $item->out->elementEnd('div'); + } + + return true; + } + + function onStartShowFaveForm($item) { + + if (!common_logged_in()) { + + $profile = $this->getAnonProfile(); + if (!empty($profile)) { + if ($profile->hasFave($item->notice)) { + $disfavor = new AnonDisFavorForm($item->out, $item->notice); + $disfavor->show(); + } else { + $favor = new AnonFavorForm($item->out, $item->notice); + $favor->show(); + } + } + } + + return true; + } + + function createAnonProfile() { + + // Get the anon user's IP, and turn it into a nickname + list($proxy, $ip) = common_client_ip(); + // IP + time + random number should avoid collisions + $nickname = 'anonymous-' . $ip . '-' . time() . '-' . common_good_rand(5); + + $profile = new Profile(); + $profile->nickname = $nickname; + $id = $profile->insert(); + + if (!empty($id)) { + common_log( + LOG_INFO, + "AnonymousFavePlugin - created profile for anonymous user from IP: " + . $ip + . ', nickname = ' + . $nickname + ); + } + + return $profile; + } + + function getAnonProfile() { + + $anon = $_SESSION['anon_nickname']; + + $profile = null; + + if (!empty($anon)) { + $profile = Profile::staticGet('nickname', $anon); + } else { + $profile = $this->createAnonProfile(); + $_SESSION['anon_nickname'] = $profile->nickname; + } + + if (!empty($profile)) { + return $profile; + } + } + + /** + * Provide plugin version information. + * + * This data is used when showing the version page. + * + * @param array &$versions array of version data arrays; see EVENTS.txt + * + * @return boolean hook value + */ + function onPluginVersion(&$versions) + { + $url = 'http://status.net/wiki/Plugin:AnonymousFave'; + + $versions[] = array('name' => 'AnonymousFave', + 'version' => ANONYMOUS_FAVE_PLUGIN_VERSION, + 'author' => 'Zach Copley', + 'homepage' => $url, + 'rawdescription' => + _m('Allow anonymous users to favorite notices.')); + + return true; + } + +} diff --git a/plugins/AnonymousFave/anondisfavor.php b/plugins/AnonymousFave/anondisfavor.php new file mode 100644 index 0000000000..9fd56fdc32 --- /dev/null +++ b/plugins/AnonymousFave/anondisfavor.php @@ -0,0 +1,132 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2010, 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')) { + exit(1); +} + +/** + * Anonymous disfavor class + * + * @category Action + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ +class AnonDisfavorAction extends RedirectingAction +{ + /** + * Class handler. + * + * @param array $args query arguments + * + * @return void + */ + function handle($args) + { + parent::handle($args); + + $anon = $_SESSION['anon_nickname']; + + $profile = Profile::staticGet('nickname', $anon); + + if (empty($profile)) { + common_debug( + "AnonDisFavorAction - Anon user tried to disfave a notice but doesn't have a profile." + ); + } + + if (empty($profile) || $_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _m('Could not disfavor notice! Please make sure your browser has cookies enabled.') + ); + return; + } + + $id = $this->trimmed('notice'); + $notice = Notice::staticGet($id); + $token = $this->trimmed('token-' . $notice->id); + + if (!$token || $token != common_session_token()) { + $this->clientError(_m('There was a problem with your session token. Try again, please.')); + return; + } + + $fave = new Fave(); + $fave->user_id = $profile->id; + $fave->notice_id = $notice->id; + + if (!$fave->find(true)) { + $this->clientError(_m('This notice is not a favorite!')); + return; + } + + $result = $fave->delete(); + + if (!$result) { + common_log_db_error($fave, 'DELETE', __FILE__); + $this->serverError(_m('Could not delete favorite.')); + return; + } + + $profile->blowFavesCache(); + + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + $this->element('title', null, _m('Add to favorites')); + $this->elementEnd('head'); + $this->elementStart('body'); + $favor = new AnonFavorForm($this, $notice); + $favor->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + $this->returnToPrevious(); + } + } + + /** + * If returnto not set, return to the public stream. + * + * @return string URL + */ + function defaultReturnTo() + { + $returnto = common_get_returnto(); + if (empty($returnto)) { + return common_local_url('public'); + } else { + return $returnto; + } + } +} + diff --git a/plugins/AnonymousFave/anondisfavorform.php b/plugins/AnonymousFave/anondisfavorform.php new file mode 100644 index 0000000000..c347ed7b43 --- /dev/null +++ b/plugins/AnonymousFave/anondisfavorform.php @@ -0,0 +1,74 @@ +. + * + * @category Form + * @package StatusNet + * @author Zach Copley + * @copyright 2010 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')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/form.php'; + +/** + * Form for disfavoring a notice anonymously + * + * @category Form + * @package StatusNet + * @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/ + * + * @see DisFavorForm + */ + +class AnonDisfavorForm extends DisFavorForm +{ + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param Notice $notice notice to disfavor + */ + + function __construct($out=null, $notice=null) + { + parent::__construct($out, $notice); + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('AnonDisFavor'); + } + +} diff --git a/plugins/AnonymousFave/anonfavor.php b/plugins/AnonymousFave/anonfavor.php new file mode 100644 index 0000000000..c972f202e4 --- /dev/null +++ b/plugins/AnonymousFave/anonfavor.php @@ -0,0 +1,125 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2010, 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')) { + exit(1); +} + +/** + * Anonymous favor class + * + * @category Action + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ +class AnonFavorAction extends RedirectingAction +{ + /** + * Class handler. + * + * @param array $args query arguments + * + * @return void + */ + function handle($args) + { + parent::handle($args); + + $anon = $_SESSION['anon_nickname']; + $profile = Profile::staticGet('nickname', $anon); + + if (empty($profile)) { + common_debug( + "AnonFavorAction - Anon user tried to fave a notice but doesn't have a profile." + ); + } + + if (empty($profile) || $_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _m('Could not favor notice! Please make sure your browser has cookies enabled.') + ); + return; + } + + $id = $this->trimmed('notice'); + $notice = Notice::staticGet($id); + $token = $this->trimmed('token-' . $notice->id); + + if (empty($token) || $token != common_session_token()) { + $this->clientError(_m('There was a problem with your session token. Try again, please.')); + return; + } + + + if ($profile->hasFave($notice)) { + $this->clientError(_m('This notice is already a favorite!')); + return; + } + $fave = Fave::addNew($profile, $notice); + + if (!$fave) { + $this->serverError(_m('Could not create favorite.')); + return; + } + + $profile->blowFavesCache(); + + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + $this->element('title', null, _m('Disfavor favorite')); + $this->elementEnd('head'); + $this->elementStart('body'); + $disfavor = new AnonDisFavorForm($this, $notice); + $disfavor->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + $this->returnToPrevious(); + } + } + + /** + * If returnto not set, return to the public stream. + * + * @return string URL + */ + function defaultReturnTo() + { + $returnto = common_get_returnto(); + if (empty($returnto)) { + return common_local_url('public'); + } else { + return $returnto; + } + } +} diff --git a/plugins/AnonymousFave/anonfavorform.php b/plugins/AnonymousFave/anonfavorform.php new file mode 100644 index 0000000000..d73c2831d0 --- /dev/null +++ b/plugins/AnonymousFave/anonfavorform.php @@ -0,0 +1,74 @@ +. + * + * @category Form + * @package StatusNet + * @author Zach Copley + * @copyright 20010 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')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/form.php'; + +/** + * Form for favoring a notice anonymously + * + * @category Form + * @package StatusNet + * @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/ + * + * @see AnonDisfavorForm + */ + +class AnonFavorForm extends FavorForm +{ + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param Notice $notice notice to favor + */ + + function __construct($out=null, $notice=null) + { + parent::__construct($out, $notice); + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('AnonFavor'); + } + +} From 8fc904e95b66ac4ce97fa3b493797da9aa0a54af Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 28 Sep 2010 19:07:45 -0700 Subject: [PATCH 06/30] New DB_DataObject for storing favorites tally --- plugins/AnonymousFave/AnonymousFavePlugin.php | 36 +++ plugins/AnonymousFave/Fave_tally.php | 209 ++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 plugins/AnonymousFave/Fave_tally.php diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 3dccaf538a..98f747748c 100644 --- a/plugins/AnonymousFave/AnonymousFavePlugin.php +++ b/plugins/AnonymousFave/AnonymousFavePlugin.php @@ -55,6 +55,39 @@ class AnonymousFavePlugin extends Plugin { common_ensure_session(); } + /** + * Hook for ensuring our tables are created + * + * Ensures the fave_tally table is there and has the right columns + * + * @return boolean hook return + */ + + function onCheckSchema() + { + $schema = Schema::get(); + + // For storing total number of times a notice has been faved + + $schema->ensureTable('fave_tally', + array( + new ColumnDef('notice_id', 'integer', null, false, 'PRI'), + new ColumnDef('count', 'integer', null, false), + new ColumnDef( + 'modified', + 'timestamp', + null, + false, + null, + 'CURRENT_TIMESTAMP', + 'on update CURRENT_TIMESTAMP' + ) + ) + ); + + return true; + } + function onEndShowHTML($action) { if (!common_logged_in()) { @@ -75,6 +108,9 @@ class AnonymousFavePlugin extends Plugin { $dir = dirname(__FILE__); switch ($cls) { + case 'Fave_tally': + include_once $dir . '/' . $cls . '.php'; + return false; case 'AnonFavorAction': include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; diff --git a/plugins/AnonymousFave/Fave_tally.php b/plugins/AnonymousFave/Fave_tally.php new file mode 100644 index 0000000000..fc7e4a5790 --- /dev/null +++ b/plugins/AnonymousFave/Fave_tally.php @@ -0,0 +1,209 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2010, 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')) { + exit(1); +} + +require_once INSTALLDIR . '/classes/Memcached_DataObject.php'; + +/** + * Data class for favorites tally + * + * A class representing a total number of times a notice has been favorited + * + * @category Action + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ + +class Fave_tally extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'fave_tally'; // table name + public $notice_id; // int(4) primary_key not_null + public $count; // int(4) primary_key not_null + public $modified; // datetime not_null default_0000-00-00%2000%3A00%3A00 + + /* Static get */ + function staticGet($k, $v = NULL) { return Memcached_DataObject::staticGet('Fave_tally', $k, $v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + /** + * return table definition for DB_DataObject + * + * @return array array of column definitions + */ + + function table() + { + return array( + 'notice_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + 'count' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + 'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL + ); + } + + /** + * return key definitions for DB_DataObject + * + * @return array key definitions + */ + + function keys() + { + return array('notice_id' => 'K'); + } + + /** + * return key definitions for DB_DataObject + * + * @return array key definitions + */ + + function keyTypes() + { + return $this->keys(); + } + + /** + * Magic formula for non-autoincrementing integer primary keys + * + * @return array magic three-false array that stops auto-incrementing. + */ + + function sequenceKey() + { + return array(false, false, false); + } + + /** + * Get a single object with multiple keys + * + * @param array $kv Map of key-value pairs + * + * @return User_flag_profile found object or null + */ + + function pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('Fave_tally', $kv); + } + + /** + * Increment a notice's tally + * + * @param integer $notice_id ID of notice we're tallying + * + * @return integer the total times the notice has been faved + */ + + static function increment($notice_id) + { + $tally = Fave_tally::ensureTally($notice_id); + $count = $tally->count + 1; + $tally->count = $count; + $result = $tally->update(); + $tally->free(); + + if ($result === false) { + $msg = sprintf( + _m("Couldn't update favorite tally for notice ID %d.", $notice_id) + ); + throw new ServerException($msg); + } + + return $count; + } + + /** + * Decrement a notice's tally + * + * @param integer $notice_id ID of notice we're tallying + * + * @return integer the total times the notice has been faved + */ + + static function decrement($notice_id) + { + $tally = Fave_tally::ensureTally($notice_id); + + $count = 0; + + if ($tally->count > 0) { + $count = $tally->count - 1; + $tally->count = $count; + $result = $tally->update(); + $tally->free(); + + if ($result === false) { + $msg = sprintf( + _m("Couldn't update favorite tally for notice ID %d.", $notice_id) + ); + throw new ServerException($msg); + } + } + + return $count; + } + + /** + * Ensure a tally exists for a given notice. If we can't find + * one create one. + * + * @param integer $notice_id + * + * @return Fave_tally the tally data object + */ + + static function ensureTally($notice_id) + { + $tally = new Fave_tally(); + $result = $tally->get($notice_id); + + if (empty($result)) { + $tally->notice_id = $notice_id; + $tally->count = 0; + if ($tally->insert() === false) { + $msg = sprintf( + _m("Couldn't create favorite tally for notice ID %d.", $notice_id) + ); + throw new ServerException($msg); + } + } + + return $tally; + } +} From 968ec4cb4212480f60c6c7b5522b5ef56ca73e51 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 12:49:26 -0700 Subject: [PATCH 07/30] Add Start/EndShowNoticeInfo events --- EVENTS.txt | 6 ++++++ lib/noticelist.php | 14 +++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index 7b6ad40eb9..e0dc7514d6 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -263,6 +263,12 @@ StartShowNoticeItem: just before showing the notice item EndShowNoticeItem: just after showing the notice item - $item: the NoticeListItem object being shown +StartShowNoticeInfo: just before showing notice info +- $item: The NoticeListItem object being shown + +EndShowNoticeInfo: just after showing notice info +- $item: The NoticeListItem object being shown + StartShowNoticeOptions: just before showing notice options like fave, repeat, etc. - $item: the NoticeListItem object being shown diff --git a/lib/noticelist.php b/lib/noticelist.php index cc460005ad..df1533980a 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -226,11 +226,15 @@ class NoticeListItem extends Widget function showNoticeInfo() { $this->out->elementStart('div', 'entry-content'); - $this->showNoticeLink(); - $this->showNoticeSource(); - $this->showNoticeLocation(); - $this->showContext(); - $this->showRepeat(); + if (Event::handle('StartShowNoticeInfo', array($this))) { + $this->showNoticeLink(); + $this->showNoticeSource(); + $this->showNoticeLocation(); + $this->showContext(); + $this->showRepeat(); + Event::handle('EndShowNoticeInfo', array($this)); + } + $this->out->elementEnd('div'); } From 21708f0fa3e0950e8060ed240b4da0c643aad2fd Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 12:49:53 -0700 Subject: [PATCH 08/30] - Increment/decrement notice fave tally - Display tally in notice output --- plugins/AnonymousFave/AnonymousFavePlugin.php | 31 +++++ plugins/AnonymousFave/Fave_tally.php | 108 +++++++++++------- 2 files changed, 96 insertions(+), 43 deletions(-) diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 98f747748c..984625a881 100644 --- a/plugins/AnonymousFave/AnonymousFavePlugin.php +++ b/plugins/AnonymousFave/AnonymousFavePlugin.php @@ -136,6 +136,27 @@ class AnonymousFavePlugin extends Plugin { return true; } + + function onEndShowNoticeInfo($item) + { + common_debug("XXXXXXXXXXX onEndShowNoticeInfo"); + + $tally = Fave_tally::ensureTally($item->notice->id); + + if (!empty($tally)) { + $item->out->elementStart( + 'div', + array( + 'id' => 'notice-' . $item->notice->id . '-tally', + 'class' => 'notice-tally' + ) + ); + $item->out->raw(sprintf(_m("favored %d times"), $tally->count)); + $item->out->elementEnd('div'); + } + return true; + } + function onStartShowNoticeOptions($item) { if (!common_logged_in()) { @@ -166,6 +187,16 @@ class AnonymousFavePlugin extends Plugin { return true; } + function onEndFavorNotice($profile, $notice) + { + $tally = Fave_tally::increment($notice->id); + } + + function onEndDisfavorNotice($profile, $notice) + { + $tally = Fave_tally::decrement($notice->id); + } + function createAnonProfile() { // Get the anon user's IP, and turn it into a nickname diff --git a/plugins/AnonymousFave/Fave_tally.php b/plugins/AnonymousFave/Fave_tally.php index fc7e4a5790..0eaa3fdc76 100644 --- a/plugins/AnonymousFave/Fave_tally.php +++ b/plugins/AnonymousFave/Fave_tally.php @@ -52,7 +52,7 @@ class Fave_tally extends Memcached_DataObject public $__table = 'fave_tally'; // table name public $notice_id; // int(4) primary_key not_null - public $count; // int(4) primary_key not_null + public $count; // int(4) not_null public $modified; // datetime not_null default_0000-00-00%2000%3A00%3A00 /* Static get */ @@ -79,31 +79,48 @@ class Fave_tally extends Memcached_DataObject /** * return key definitions for DB_DataObject * - * @return array key definitions + * DB_DataObject needs to know about keys that the table has, since it + * won't appear in StatusNet's own keys list. In most cases, this will + * simply reference your keyTypes() function. + * + * @return array list of key field names */ function keys() + { + return array_keys($this->keyTypes()); + } + + /** + * return key definitions for Memcached_DataObject + * + * Our caching system uses the same key definitions, but uses a different + * method to get them. This key information is used to store and clear + * cached data, so be sure to list any key that will be used for static + * lookups. + * + * @return array associative array of key definitions, field name to type: + * 'K' for primary key: for compound keys, add an entry for each component; + * 'U' for unique keys: compound keys are not well supported here. + */ + + function keyTypes() { return array('notice_id' => 'K'); } - /** - * return key definitions for DB_DataObject - * - * @return array key definitions - */ - - function keyTypes() - { - return $this->keys(); - } - /** * Magic formula for non-autoincrementing integer primary keys * + * If a table has a single integer column as its primary key, DB_DataObject + * assumes that the column is auto-incrementing and makes a sequence table + * to do this incrementation. Since we don't need this for our class, we + * overload this method and return the magic formula that DB_DataObject needs. + * * @return array magic three-false array that stops auto-incrementing. */ + function sequenceKey() { return array(false, false, false); @@ -125,80 +142,85 @@ class Fave_tally extends Memcached_DataObject /** * Increment a notice's tally * - * @param integer $notice_id ID of notice we're tallying + * @param integer $noticeID ID of notice we're tallying * - * @return integer the total times the notice has been faved + * @return Fave_tally $tally the tally data object */ - static function increment($notice_id) + static function increment($noticeID) { - $tally = Fave_tally::ensureTally($notice_id); - $count = $tally->count + 1; - $tally->count = $count; - $result = $tally->update(); - $tally->free(); + common_debug("XXXXXXXXX Fave_tally::increment()"); + $tally = Fave_tally::ensureTally($noticeID); - if ($result === false) { + $orig = clone($tally); + $tally->count++; + $result = $tally->update($orig); + + if (!$result) { $msg = sprintf( - _m("Couldn't update favorite tally for notice ID %d.", $notice_id) + _m("Couldn't update favorite tally for notice ID %d."), + $notice_id ); throw new ServerException($msg); } - return $count; + return $tally; } /** * Decrement a notice's tally * - * @param integer $notice_id ID of notice we're tallying + * @param integer $noticeID ID of notice we're tallying * - * @return integer the total times the notice has been faved + * @return Fave_tally $tally the tally data object */ - static function decrement($notice_id) + static function decrement($noticeID) { - $tally = Fave_tally::ensureTally($notice_id); + common_debug("XXXXXXXXX Fave_tally::decrement()"); - $count = 0; + $tally = Fave_tally::ensureTally($noticeID); if ($tally->count > 0) { - $count = $tally->count - 1; - $tally->count = $count; - $result = $tally->update(); - $tally->free(); + $orig = clone($tally); + $tally->count--; + $result = $tally->update($orig); - if ($result === false) { + if (!$result) { $msg = sprintf( - _m("Couldn't update favorite tally for notice ID %d.", $notice_id) + _m("Couldn't update favorite tally for notice ID %d."), + $notice_id ); throw new ServerException($msg); } } - return $count; + return $tally; } /** * Ensure a tally exists for a given notice. If we can't find * one create one. * - * @param integer $notice_id + * @param integer $noticeID * * @return Fave_tally the tally data object */ - static function ensureTally($notice_id) + static function ensureTally($noticeID) { - $tally = new Fave_tally(); - $result = $tally->get($notice_id); + $tally = Fave_tally::staticGet('notice_id', $notice_id); - if (empty($result)) { + if (!$tally) { + common_debug("Fave_tally::ensureTally - creating tally for notice " . $notice_id); + $tally = new Fave_tally(); $tally->notice_id = $notice_id; $tally->count = 0; - if ($tally->insert() === false) { + $result = $tally->insert(); + if (!$result) { $msg = sprintf( - _m("Couldn't create favorite tally for notice ID %d.", $notice_id) + _m("Couldn't create favorite tally for notice ID %d."), + $notice_id ); throw new ServerException($msg); } From 9ef3549a7145dc6e041da15fd569bc4f42654f2e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 13:29:58 -0700 Subject: [PATCH 09/30] Intialize new fave tallys with total existing fave count per notice --- plugins/AnonymousFave/Fave_tally.php | 38 ++++++++++++++----- .../scripts/initialize_fave_tallys.php | 38 +++++++++++++++++++ 2 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 plugins/AnonymousFave/scripts/initialize_fave_tallys.php diff --git a/plugins/AnonymousFave/Fave_tally.php b/plugins/AnonymousFave/Fave_tally.php index 0eaa3fdc76..35ace6d01b 100644 --- a/plugins/AnonymousFave/Fave_tally.php +++ b/plugins/AnonymousFave/Fave_tally.php @@ -36,7 +36,7 @@ require_once INSTALLDIR . '/classes/Memcached_DataObject.php'; /** * Data class for favorites tally * - * A class representing a total number of times a notice has been favorited + * A class representing a total number of times a notice has been favored * * @category Action * @package StatusNet @@ -159,7 +159,7 @@ class Fave_tally extends Memcached_DataObject if (!$result) { $msg = sprintf( _m("Couldn't update favorite tally for notice ID %d."), - $notice_id + $noticeID ); throw new ServerException($msg); } @@ -189,7 +189,7 @@ class Fave_tally extends Memcached_DataObject if (!$result) { $msg = sprintf( _m("Couldn't update favorite tally for notice ID %d."), - $notice_id + $noticeID ); throw new ServerException($msg); } @@ -200,7 +200,7 @@ class Fave_tally extends Memcached_DataObject /** * Ensure a tally exists for a given notice. If we can't find - * one create one. + * one create one with the total number of existing faves * * @param integer $noticeID * @@ -209,18 +209,18 @@ class Fave_tally extends Memcached_DataObject static function ensureTally($noticeID) { - $tally = Fave_tally::staticGet('notice_id', $notice_id); + $tally = Fave_tally::staticGet('notice_id', $noticeID); if (!$tally) { - common_debug("Fave_tally::ensureTally - creating tally for notice " . $notice_id); + common_debug("Fave_tally::ensureTally - creating tally for notice " . $noticeID); $tally = new Fave_tally(); - $tally->notice_id = $notice_id; - $tally->count = 0; + $tally->notice_id = $noticeID; + $tally->count = Fave_tally::countExistingFaves($noticeID); $result = $tally->insert(); if (!$result) { $msg = sprintf( _m("Couldn't create favorite tally for notice ID %d."), - $notice_id + $noticeID ); throw new ServerException($msg); } @@ -228,4 +228,24 @@ class Fave_tally extends Memcached_DataObject return $tally; } + + /** + * Count the number of faves a notice already has. Used to initalize + * a tally for a notice. + * + * @param integer $noticeID ID of the notice to count faves for + * + * @return integer $total total number of time the notice has been favored + */ + + static function countExistingFaves($noticeID) + { + $fave = new Fave(); + $fave->notice_id = $noticeID; + $total = $fave->count(); + + common_debug("ZZZZZZZ notice " . $noticeID . ' has ' . $total . " faves"); + + return $total; + } } diff --git a/plugins/AnonymousFave/scripts/initialize_fave_tallys.php b/plugins/AnonymousFave/scripts/initialize_fave_tallys.php new file mode 100644 index 0000000000..f7ea6d1ef7 --- /dev/null +++ b/plugins/AnonymousFave/scripts/initialize_fave_tallys.php @@ -0,0 +1,38 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..')); + +$helptext = <<find(); + +while ($notice->fetch()) { + Fave_tally::ensureTally($notice->id); +} + From b4765594f9ea3dcd4ce5ed99c733a057e4c7db48 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 13:31:01 -0700 Subject: [PATCH 10/30] Set initialize_fave_tallys.php executable --- plugins/AnonymousFave/scripts/initialize_fave_tallys.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 plugins/AnonymousFave/scripts/initialize_fave_tallys.php diff --git a/plugins/AnonymousFave/scripts/initialize_fave_tallys.php b/plugins/AnonymousFave/scripts/initialize_fave_tallys.php old mode 100644 new mode 100755 From de6bd5c129f07a8c19aaf1094b2e4d9856d47a33 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 14:07:46 -0700 Subject: [PATCH 11/30] Add Start/EndFavorNoticeForm and Start/EndDisFavorNoticeForm hooks --- EVENTS.txt | 16 ++++++++++++++++ lib/disfavorform.php | 10 +++++++--- lib/favorform.php | 9 ++++++--- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index e0dc7514d6..bb84cc4d6a 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -774,6 +774,22 @@ EndDisfavorNotice: After saving a notice as a favorite - $profile: profile of the person faving (can be remote!) - $notice: notice being faved +StartFavorNoticeForm: starting the data in the form for favoring a notice +- $FavorForm: the favor form being shown +- $notice: notice being favored + +EndFavorNoticeForm: Ending the data in the form for favoring a notice +- $FavorForm: the favor form being shown +- $notice: notice being favored + +StartDisFavorNoticeForm: starting the data in the form for disfavoring a notice +- $DisfavorForm: the disfavor form being shown +- $notice: notice being difavored + +EndDisFavorNoticeForm: Ending the data in the form for disfavoring a notice +- $DisfavorForm: the disfavor form being shown +- $notice: notice being disfavored + StartFindMentions: start finding mentions in a block of text - $sender: sender profile - $text: plain text version of the notice diff --git a/lib/disfavorform.php b/lib/disfavorform.php index 5b135b38ad..6023766d7b 100644 --- a/lib/disfavorform.php +++ b/lib/disfavorform.php @@ -123,9 +123,13 @@ class DisfavorForm extends Form function formData() { - $this->out->hidden('notice-n'.$this->notice->id, - $this->notice->id, - 'notice'); + 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)); + } + } /** diff --git a/lib/favorform.php b/lib/favorform.php index 625df7c8b5..4e2891ffd5 100644 --- a/lib/favorform.php +++ b/lib/favorform.php @@ -123,9 +123,12 @@ class FavorForm extends Form function formData() { - $this->out->hidden('notice-n'.$this->notice->id, - $this->notice->id, - 'notice'); + 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)); + } } /** From 5ca280f203a3ab41a367d0d034ed1952c4bb7ee6 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 14:08:31 -0700 Subject: [PATCH 12/30] Ajax update notice tally --- plugins/AnonymousFave/AnonymousFavePlugin.php | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 984625a881..41542c8493 100644 --- a/plugins/AnonymousFave/AnonymousFavePlugin.php +++ b/plugins/AnonymousFave/AnonymousFavePlugin.php @@ -136,27 +136,6 @@ class AnonymousFavePlugin extends Plugin { return true; } - - function onEndShowNoticeInfo($item) - { - common_debug("XXXXXXXXXXX onEndShowNoticeInfo"); - - $tally = Fave_tally::ensureTally($item->notice->id); - - if (!empty($tally)) { - $item->out->elementStart( - 'div', - array( - 'id' => 'notice-' . $item->notice->id . '-tally', - 'class' => 'notice-tally' - ) - ); - $item->out->raw(sprintf(_m("favored %d times"), $tally->count)); - $item->out->elementEnd('div'); - } - return true; - } - function onStartShowNoticeOptions($item) { if (!common_logged_in()) { @@ -187,6 +166,33 @@ class AnonymousFavePlugin extends Plugin { return true; } + function onEndFavorNoticeForm($form, $notice) + { + $this->showTally($form->out, $notice); + } + + function onEndDisFavorNoticeForm($form, $notice) + { + $this->showTally($form->out, $notice); + } + + function showTally($out, $notice) + { + $tally = Fave_tally::ensureTally($notice->id); + + if (!empty($tally)) { + $out->elementStart( + 'div', + array( + 'id' => 'notice-' . $notice->id . '-tally', + 'class' => 'notice-tally' + ) + ); + $out->raw(sprintf(_m("favored %d times"), $tally->count)); + $out->elementEnd('div'); + } + } + function onEndFavorNotice($profile, $notice) { $tally = Fave_tally::increment($notice->id); From ba6984284425f4556777523338fa4f15a03e3884 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 15:52:18 -0700 Subject: [PATCH 13/30] - Lookup anon profiles by ID (safer because they are guranteed to be unique) and probably faster - Obfuscate the anonymous user session token to make it hard to figure out the profile ID --- plugins/AnonymousFave/AnonymousFavePlugin.php | 56 ++++++++++++------- plugins/AnonymousFave/anondisfavor.php | 10 +--- plugins/AnonymousFave/anonfavor.php | 9 +-- 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 41542c8493..264cad1748 100644 --- a/plugins/AnonymousFave/AnonymousFavePlugin.php +++ b/plugins/AnonymousFave/AnonymousFavePlugin.php @@ -151,7 +151,7 @@ class AnonymousFavePlugin extends Plugin { if (!common_logged_in()) { - $profile = $this->getAnonProfile(); + $profile = AnonymousFavePlugin::getAnonProfile(); if (!empty($profile)) { if ($profile->hasFave($item->notice)) { $disfavor = new AnonDisFavorForm($item->out, $item->notice); @@ -207,42 +207,58 @@ class AnonymousFavePlugin extends Plugin { // Get the anon user's IP, and turn it into a nickname list($proxy, $ip) = common_client_ip(); - // IP + time + random number should avoid collisions - $nickname = 'anonymous-' . $ip . '-' . time() . '-' . common_good_rand(5); + + // IP + time + random number should help to avoid collisions + $baseNickname = $ip . '-' . time() . '-' . common_good_rand(5); $profile = new Profile(); - $profile->nickname = $nickname; + $profile->nickname = $baseNickname; $id = $profile->insert(); - if (!empty($id)) { - common_log( - LOG_INFO, - "AnonymousFavePlugin - created profile for anonymous user from IP: " - . $ip - . ', nickname = ' - . $nickname - ); + if (!$id) { + throw new ServerException(_m("Couldn't create anonymous user session")); } + // Stick the Profile ID into the nickname + $orig = clone($profile); + + $profile->nickname = 'anon-' . $id . '-' . $baseNickname; + $result = $profile->update($orig); + + if (!$result) { + throw new ServerException(_m("Couldn't create anonymous user session")); + } + + common_log( + LOG_INFO, + "AnonymousFavePlugin - created profile for anonymous user from IP: " + . $ip + . ', nickname = ' + . $profile->nickname + ); + return $profile; } - function getAnonProfile() { + static function getAnonProfile() { - $anon = $_SESSION['anon_nickname']; + $token = $_SESSION['anon_token']; + $anon = base64_decode($token); $profile = null; - if (!empty($anon)) { - $profile = Profile::staticGet('nickname', $anon); + if (!empty($anon) && substr($anon, 0, 5) == 'anon-') { + $parts = explode('-', $anon); + $id = $parts[1]; + // Do Profile lookup by ID instead of nickname for safety/performance + $profile = Profile::staticGet('id', $id); } else { $profile = $this->createAnonProfile(); - $_SESSION['anon_nickname'] = $profile->nickname; + // Obfuscate so it's hard to figure out the Profile ID + $_SESSION['anon_token'] = base64_encode($profile->nickname); } - if (!empty($profile)) { - return $profile; - } + return $profile; } /** diff --git a/plugins/AnonymousFave/anondisfavor.php b/plugins/AnonymousFave/anondisfavor.php index 9fd56fdc32..f39d5a7780 100644 --- a/plugins/AnonymousFave/anondisfavor.php +++ b/plugins/AnonymousFave/anondisfavor.php @@ -54,15 +54,7 @@ class AnonDisfavorAction extends RedirectingAction { parent::handle($args); - $anon = $_SESSION['anon_nickname']; - - $profile = Profile::staticGet('nickname', $anon); - - if (empty($profile)) { - common_debug( - "AnonDisFavorAction - Anon user tried to disfave a notice but doesn't have a profile." - ); - } + $profile = AnonymousFavePlugin::getAnonProfile(); if (empty($profile) || $_SERVER['REQUEST_METHOD'] != 'POST') { $this->clientError( diff --git a/plugins/AnonymousFave/anonfavor.php b/plugins/AnonymousFave/anonfavor.php index c972f202e4..58570ced9a 100644 --- a/plugins/AnonymousFave/anonfavor.php +++ b/plugins/AnonymousFave/anonfavor.php @@ -54,14 +54,7 @@ class AnonFavorAction extends RedirectingAction { parent::handle($args); - $anon = $_SESSION['anon_nickname']; - $profile = Profile::staticGet('nickname', $anon); - - if (empty($profile)) { - common_debug( - "AnonFavorAction - Anon user tried to fave a notice but doesn't have a profile." - ); - } + $profile = AnonymousFavePlugin::getAnonProfile(); if (empty($profile) || $_SERVER['REQUEST_METHOD'] != 'POST') { $this->clientError( From f3390599901d7b7766d9813e281e0ca78e9729fd Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 16:11:32 -0700 Subject: [PATCH 14/30] Make createAnonProfile() static --- plugins/AnonymousFave/AnonymousFavePlugin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 264cad1748..47eebef9ba 100644 --- a/plugins/AnonymousFave/AnonymousFavePlugin.php +++ b/plugins/AnonymousFave/AnonymousFavePlugin.php @@ -203,7 +203,7 @@ class AnonymousFavePlugin extends Plugin { $tally = Fave_tally::decrement($notice->id); } - function createAnonProfile() { + static function createAnonProfile() { // Get the anon user's IP, and turn it into a nickname list($proxy, $ip) = common_client_ip(); @@ -253,7 +253,7 @@ class AnonymousFavePlugin extends Plugin { // Do Profile lookup by ID instead of nickname for safety/performance $profile = Profile::staticGet('id', $id); } else { - $profile = $this->createAnonProfile(); + $profile = AnonymousFavePlugin::createAnonProfile(); // Obfuscate so it's hard to figure out the Profile ID $_SESSION['anon_token'] = base64_encode($profile->nickname); } From 148ab660b69b665d949835e047d93906cebe614e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 16:28:29 -0700 Subject: [PATCH 15/30] Remove debugging statements --- plugins/AnonymousFave/AnonymousFavePlugin.php | 4 ++-- plugins/AnonymousFave/Fave_tally.php | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 47eebef9ba..72093e7f7e 100644 --- a/plugins/AnonymousFave/AnonymousFavePlugin.php +++ b/plugins/AnonymousFave/AnonymousFavePlugin.php @@ -216,7 +216,7 @@ class AnonymousFavePlugin extends Plugin { $id = $profile->insert(); if (!$id) { - throw new ServerException(_m("Couldn't create anonymous user session")); + throw new ServerException(_m("Couldn't create anonymous user session.")); } // Stick the Profile ID into the nickname @@ -226,7 +226,7 @@ class AnonymousFavePlugin extends Plugin { $result = $profile->update($orig); if (!$result) { - throw new ServerException(_m("Couldn't create anonymous user session")); + throw new ServerException(_m("Couldn't create anonymous user session.")); } common_log( diff --git a/plugins/AnonymousFave/Fave_tally.php b/plugins/AnonymousFave/Fave_tally.php index 35ace6d01b..b350d5a0a5 100644 --- a/plugins/AnonymousFave/Fave_tally.php +++ b/plugins/AnonymousFave/Fave_tally.php @@ -149,7 +149,6 @@ class Fave_tally extends Memcached_DataObject static function increment($noticeID) { - common_debug("XXXXXXXXX Fave_tally::increment()"); $tally = Fave_tally::ensureTally($noticeID); $orig = clone($tally); @@ -177,8 +176,6 @@ class Fave_tally extends Memcached_DataObject static function decrement($noticeID) { - common_debug("XXXXXXXXX Fave_tally::decrement()"); - $tally = Fave_tally::ensureTally($noticeID); if ($tally->count > 0) { @@ -212,7 +209,6 @@ class Fave_tally extends Memcached_DataObject $tally = Fave_tally::staticGet('notice_id', $noticeID); if (!$tally) { - common_debug("Fave_tally::ensureTally - creating tally for notice " . $noticeID); $tally = new Fave_tally(); $tally->notice_id = $noticeID; $tally->count = Fave_tally::countExistingFaves($noticeID); @@ -243,9 +239,6 @@ class Fave_tally extends Memcached_DataObject $fave = new Fave(); $fave->notice_id = $noticeID; $total = $fave->count(); - - common_debug("ZZZZZZZ notice " . $noticeID . ' has ' . $total . " faves"); - return $total; } } From c19e592fa80f616a18718c4650ea0ffc9661f7ce Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 27 Sep 2010 15:01:03 -0700 Subject: [PATCH 16/30] Move hasFave() to Profile --- classes/Profile.php | 35 +++++++++++++++++++++++++++++++++++ classes/User.php | 33 ++------------------------------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/classes/Profile.php b/classes/Profile.php index 3a381fcc89..668f25d2e4 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -473,6 +473,41 @@ class Profile extends Memcached_DataObject return $cnt; } + function hasFave($notice) + { + $cache = common_memcache(); + + // XXX: Kind of a hack. + + if (!empty($cache)) { + // This is the stream of favorite notices, in rev chron + // order. This forces it into cache. + + $ids = Fave::stream($this->id, 0, NOTICE_CACHE_WINDOW); + + // If it's in the list, then it's a fave + + if (in_array($notice->id, $ids)) { + return true; + } + + // If we're not past the end of the cache window, + // then the cache has all available faves, so this one + // is not a fave. + + if (count($ids) < NOTICE_CACHE_WINDOW) { + return false; + } + + // Otherwise, cache doesn't have all faves; + // fall through to the default + } + + $fave = Fave::pkeyGet(array('user_id' => $this->id, + 'notice_id' => $notice->id)); + return ((is_null($fave)) ? false : true); + } + function faveCount() { $c = common_memcache(); diff --git a/classes/User.php b/classes/User.php index b85192b29c..27299e62e0 100644 --- a/classes/User.php +++ b/classes/User.php @@ -412,37 +412,8 @@ class User extends Memcached_DataObject function hasFave($notice) { - $cache = common_memcache(); - - // XXX: Kind of a hack. - - if ($cache) { - // This is the stream of favorite notices, in rev chron - // order. This forces it into cache. - - $ids = Fave::stream($this->id, 0, NOTICE_CACHE_WINDOW); - - // If it's in the list, then it's a fave - - if (in_array($notice->id, $ids)) { - return true; - } - - // If we're not past the end of the cache window, - // then the cache has all available faves, so this one - // is not a fave. - - if (count($ids) < NOTICE_CACHE_WINDOW) { - return false; - } - - // Otherwise, cache doesn't have all faves; - // fall through to the default - } - - $fave = Fave::pkeyGet(array('user_id' => $this->id, - 'notice_id' => $notice->id)); - return ((is_null($fave)) ? false : true); + $profile = $this->getProfile(); + return $profile->hasFave($notice); } function mutuallySubscribed($other) From 3960c9ad39d96cdfef390065f15f9f0fc280f37c Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 28 Sep 2010 15:46:14 -0700 Subject: [PATCH 17/30] Move blowFavesCache() to Profile --- classes/Profile.php | 14 ++++++++++++++ classes/User.php | 11 +---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/classes/Profile.php b/classes/Profile.php index 668f25d2e4..3844077e62 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -551,6 +551,20 @@ class Profile extends Memcached_DataObject return $cnt; } + function blowFavesCache() + { + $cache = common_memcache(); + if ($cache) { + // Faves don't happen chronologically, so we need to blow + // ;last cache, too + $cache->delete(common_cache_key('fave:ids_by_user:'.$this->id)); + $cache->delete(common_cache_key('fave:ids_by_user:'.$this->id.';last')); + $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id)); + $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id.';last')); + } + $this->blowFaveCount(); + } + function blowSubscriberCount() { $c = common_memcache(); diff --git a/classes/User.php b/classes/User.php index 27299e62e0..e784fd9e9a 100644 --- a/classes/User.php +++ b/classes/User.php @@ -482,17 +482,8 @@ class User extends Memcached_DataObject function blowFavesCache() { - $cache = common_memcache(); - if ($cache) { - // Faves don't happen chronologically, so we need to blow - // ;last cache, too - $cache->delete(common_cache_key('fave:ids_by_user:'.$this->id)); - $cache->delete(common_cache_key('fave:ids_by_user:'.$this->id.';last')); - $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id)); - $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id.';last')); - } $profile = $this->getProfile(); - $profile->blowFaveCount(); + $profile->blowFavesCache(); } function getSelfTags() From fd779009b8636399472d74e86531ba9bc86cfb59 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 28 Sep 2010 15:56:11 -0700 Subject: [PATCH 18/30] Add Start/EndShowNoticeItem event hooks to single notice page --- actions/shownotice.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/actions/shownotice.php b/actions/shownotice.php index 86df5f9f30..c8e9cfe66a 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -314,10 +314,14 @@ class SingleNoticeItem extends NoticeListItem function show() { $this->showStart(); - $this->showNotice(); - $this->showNoticeAttachments(); - $this->showNoticeInfo(); - $this->showNoticeOptions(); + if (Event::handle('StartShowNoticeItem', array($this))) { + $this->showNotice(); + $this->showNoticeAttachments(); + $this->showNoticeInfo(); + $this->showNoticeOptions(); + Event::handle('EndShowNoticeItem', array($this)); + } + $this->showEnd(); } From 21759c31326ace83498466794d9b692f3f2ae077 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 28 Sep 2010 17:09:34 -0700 Subject: [PATCH 19/30] New eventsi: Start/EndShowNoticeOptions and Start/EndShowFaveForm --- EVENTS.txt | 16 ++++++++++++++-- lib/noticelist.php | 38 ++++++++++++++++++++++---------------- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index cad93a7121..d722bc4ac7 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -258,10 +258,22 @@ EndShowExportData: just after showing the
with export data (feeds) - $action: action object being shown StartShowNoticeItem: just before showing the notice item -- $action: action object being shown +- $item: The NoticeListItem object being shown EndShowNoticeItem: just after showing the notice item -- $action: action object being shown +- $item: the NoticeListItem object being shown + +StartShowNoticeOptions: just before showing notice options like fave, repeat, etc. +- $item: the NoticeListItem object being shown + +EndShowNoticeOptions: just after showing notice options like fave, repeat, etc. +- $item: the NoticeListItem object being shown + +StartShowFaveForm: just before showing the fave form +- $item: the NoticeListItem object being shown + +EndShowFaveForm: just after showing the fave form +- $item: the NoticeListItem object being shown StartShowPageNotice: just before showing the page notice (instructions or error) - $action: action object being shown diff --git a/lib/noticelist.php b/lib/noticelist.php index 529d6a3f90..cc460005ad 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -236,14 +236,17 @@ class NoticeListItem extends Widget function showNoticeOptions() { - $user = common_current_user(); - if ($user) { - $this->out->elementStart('div', 'notice-options'); - $this->showFaveForm(); - $this->showReplyLink(); - $this->showRepeatForm(); - $this->showDeleteLink(); - $this->out->elementEnd('div'); + if (Event::handle('StartShowNoticeOptions', array($this))) { + $user = common_current_user(); + if ($user) { + $this->out->elementStart('div', 'notice-options'); + $this->showFaveForm(); + $this->showReplyLink(); + $this->showRepeatForm(); + $this->showDeleteLink(); + $this->out->elementEnd('div'); + } + Event::handle('EndShowNoticeOptions', array($this)); } } @@ -270,15 +273,18 @@ class NoticeListItem extends Widget function showFaveForm() { - $user = common_current_user(); - if ($user) { - if ($user->hasFave($this->notice)) { - $disfavor = new DisfavorForm($this->out, $this->notice); - $disfavor->show(); - } else { - $favor = new FavorForm($this->out, $this->notice); - $favor->show(); + if (Event::handle('StartShowFaveForm', array($this))) { + $user = common_current_user(); + if ($user) { + if ($user->hasFave($this->notice)) { + $disfavor = new DisfavorForm($this->out, $this->notice); + $disfavor->show(); + } else { + $favor = new FavorForm($this->out, $this->notice); + $favor->show(); + } } + Event::handle('EndShowFaveForm', array($this)); } } From c10f17dc6b1e357e37fea31adbf74de003e6429c Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 28 Sep 2010 17:17:09 -0700 Subject: [PATCH 20/30] Initial plugin for allowing anonymous favoriting --- plugins/AnonymousFave/AnonymousFavePlugin.php | 198 ++++++++++++++++++ plugins/AnonymousFave/anondisfavor.php | 132 ++++++++++++ plugins/AnonymousFave/anondisfavorform.php | 74 +++++++ plugins/AnonymousFave/anonfavor.php | 125 +++++++++++ plugins/AnonymousFave/anonfavorform.php | 74 +++++++ 5 files changed, 603 insertions(+) create mode 100644 plugins/AnonymousFave/AnonymousFavePlugin.php create mode 100644 plugins/AnonymousFave/anondisfavor.php create mode 100644 plugins/AnonymousFave/anondisfavorform.php create mode 100644 plugins/AnonymousFave/anonfavor.php create mode 100644 plugins/AnonymousFave/anonfavorform.php diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php new file mode 100644 index 0000000000..3dccaf538a --- /dev/null +++ b/plugins/AnonymousFave/AnonymousFavePlugin.php @@ -0,0 +1,198 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Zach Copley + * @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); +} + +define('ANONYMOUS_FAVE_PLUGIN_VERSION', '0.1'); + +/** + * Anonymous Fave plugin to allow anonymous (not logged in) users + * to favorite notices + * + * @category Plugin + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class AnonymousFavePlugin extends Plugin { + + function onArgsInitialize() { + // We always want a session because we're tracking anon users + common_ensure_session(); + } + + function onEndShowHTML($action) + { + if (!common_logged_in()) { + // Set a place to return to when submitting forms + common_set_returnto($action->selfUrl()); + } + } + + function onEndShowScripts($action) + { + // Setup ajax calls for favoriting. Usually this is only done when + // a user is logged in. + $action->inlineScript('SN.U.NoticeFavor();'); + } + + function onAutoload($cls) + { + $dir = dirname(__FILE__); + + switch ($cls) { + case 'AnonFavorAction': + include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; + return false; + case 'AnonDisFavorAction': + include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; + return false; + case 'AnonFavorForm': + include_once $dir . '/anonfavorform.php'; + return false; + case 'AnonDisFavorForm': + include_once $dir . '/anondisfavorform.php'; + return false; + default: + return true; + } + } + + function onStartInitializeRouter($m) { + + $m->connect('main/anonfavor', array('action' => 'AnonFavor')); + $m->connect('main/anondisfavor', array('action' => 'AnonDisFavor')); + + return true; + } + + function onStartShowNoticeOptions($item) { + + if (!common_logged_in()) { + $item->out->elementStart('div', 'notice-options'); + $item->showFaveForm(); + $item->out->elementEnd('div'); + } + + return true; + } + + function onStartShowFaveForm($item) { + + if (!common_logged_in()) { + + $profile = $this->getAnonProfile(); + if (!empty($profile)) { + if ($profile->hasFave($item->notice)) { + $disfavor = new AnonDisFavorForm($item->out, $item->notice); + $disfavor->show(); + } else { + $favor = new AnonFavorForm($item->out, $item->notice); + $favor->show(); + } + } + } + + return true; + } + + function createAnonProfile() { + + // Get the anon user's IP, and turn it into a nickname + list($proxy, $ip) = common_client_ip(); + // IP + time + random number should avoid collisions + $nickname = 'anonymous-' . $ip . '-' . time() . '-' . common_good_rand(5); + + $profile = new Profile(); + $profile->nickname = $nickname; + $id = $profile->insert(); + + if (!empty($id)) { + common_log( + LOG_INFO, + "AnonymousFavePlugin - created profile for anonymous user from IP: " + . $ip + . ', nickname = ' + . $nickname + ); + } + + return $profile; + } + + function getAnonProfile() { + + $anon = $_SESSION['anon_nickname']; + + $profile = null; + + if (!empty($anon)) { + $profile = Profile::staticGet('nickname', $anon); + } else { + $profile = $this->createAnonProfile(); + $_SESSION['anon_nickname'] = $profile->nickname; + } + + if (!empty($profile)) { + return $profile; + } + } + + /** + * Provide plugin version information. + * + * This data is used when showing the version page. + * + * @param array &$versions array of version data arrays; see EVENTS.txt + * + * @return boolean hook value + */ + function onPluginVersion(&$versions) + { + $url = 'http://status.net/wiki/Plugin:AnonymousFave'; + + $versions[] = array('name' => 'AnonymousFave', + 'version' => ANONYMOUS_FAVE_PLUGIN_VERSION, + 'author' => 'Zach Copley', + 'homepage' => $url, + 'rawdescription' => + _m('Allow anonymous users to favorite notices.')); + + return true; + } + +} diff --git a/plugins/AnonymousFave/anondisfavor.php b/plugins/AnonymousFave/anondisfavor.php new file mode 100644 index 0000000000..9fd56fdc32 --- /dev/null +++ b/plugins/AnonymousFave/anondisfavor.php @@ -0,0 +1,132 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2010, 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')) { + exit(1); +} + +/** + * Anonymous disfavor class + * + * @category Action + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ +class AnonDisfavorAction extends RedirectingAction +{ + /** + * Class handler. + * + * @param array $args query arguments + * + * @return void + */ + function handle($args) + { + parent::handle($args); + + $anon = $_SESSION['anon_nickname']; + + $profile = Profile::staticGet('nickname', $anon); + + if (empty($profile)) { + common_debug( + "AnonDisFavorAction - Anon user tried to disfave a notice but doesn't have a profile." + ); + } + + if (empty($profile) || $_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _m('Could not disfavor notice! Please make sure your browser has cookies enabled.') + ); + return; + } + + $id = $this->trimmed('notice'); + $notice = Notice::staticGet($id); + $token = $this->trimmed('token-' . $notice->id); + + if (!$token || $token != common_session_token()) { + $this->clientError(_m('There was a problem with your session token. Try again, please.')); + return; + } + + $fave = new Fave(); + $fave->user_id = $profile->id; + $fave->notice_id = $notice->id; + + if (!$fave->find(true)) { + $this->clientError(_m('This notice is not a favorite!')); + return; + } + + $result = $fave->delete(); + + if (!$result) { + common_log_db_error($fave, 'DELETE', __FILE__); + $this->serverError(_m('Could not delete favorite.')); + return; + } + + $profile->blowFavesCache(); + + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + $this->element('title', null, _m('Add to favorites')); + $this->elementEnd('head'); + $this->elementStart('body'); + $favor = new AnonFavorForm($this, $notice); + $favor->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + $this->returnToPrevious(); + } + } + + /** + * If returnto not set, return to the public stream. + * + * @return string URL + */ + function defaultReturnTo() + { + $returnto = common_get_returnto(); + if (empty($returnto)) { + return common_local_url('public'); + } else { + return $returnto; + } + } +} + diff --git a/plugins/AnonymousFave/anondisfavorform.php b/plugins/AnonymousFave/anondisfavorform.php new file mode 100644 index 0000000000..c347ed7b43 --- /dev/null +++ b/plugins/AnonymousFave/anondisfavorform.php @@ -0,0 +1,74 @@ +. + * + * @category Form + * @package StatusNet + * @author Zach Copley + * @copyright 2010 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')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/form.php'; + +/** + * Form for disfavoring a notice anonymously + * + * @category Form + * @package StatusNet + * @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/ + * + * @see DisFavorForm + */ + +class AnonDisfavorForm extends DisFavorForm +{ + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param Notice $notice notice to disfavor + */ + + function __construct($out=null, $notice=null) + { + parent::__construct($out, $notice); + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('AnonDisFavor'); + } + +} diff --git a/plugins/AnonymousFave/anonfavor.php b/plugins/AnonymousFave/anonfavor.php new file mode 100644 index 0000000000..c972f202e4 --- /dev/null +++ b/plugins/AnonymousFave/anonfavor.php @@ -0,0 +1,125 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2010, 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')) { + exit(1); +} + +/** + * Anonymous favor class + * + * @category Action + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ +class AnonFavorAction extends RedirectingAction +{ + /** + * Class handler. + * + * @param array $args query arguments + * + * @return void + */ + function handle($args) + { + parent::handle($args); + + $anon = $_SESSION['anon_nickname']; + $profile = Profile::staticGet('nickname', $anon); + + if (empty($profile)) { + common_debug( + "AnonFavorAction - Anon user tried to fave a notice but doesn't have a profile." + ); + } + + if (empty($profile) || $_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _m('Could not favor notice! Please make sure your browser has cookies enabled.') + ); + return; + } + + $id = $this->trimmed('notice'); + $notice = Notice::staticGet($id); + $token = $this->trimmed('token-' . $notice->id); + + if (empty($token) || $token != common_session_token()) { + $this->clientError(_m('There was a problem with your session token. Try again, please.')); + return; + } + + + if ($profile->hasFave($notice)) { + $this->clientError(_m('This notice is already a favorite!')); + return; + } + $fave = Fave::addNew($profile, $notice); + + if (!$fave) { + $this->serverError(_m('Could not create favorite.')); + return; + } + + $profile->blowFavesCache(); + + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + $this->element('title', null, _m('Disfavor favorite')); + $this->elementEnd('head'); + $this->elementStart('body'); + $disfavor = new AnonDisFavorForm($this, $notice); + $disfavor->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + $this->returnToPrevious(); + } + } + + /** + * If returnto not set, return to the public stream. + * + * @return string URL + */ + function defaultReturnTo() + { + $returnto = common_get_returnto(); + if (empty($returnto)) { + return common_local_url('public'); + } else { + return $returnto; + } + } +} diff --git a/plugins/AnonymousFave/anonfavorform.php b/plugins/AnonymousFave/anonfavorform.php new file mode 100644 index 0000000000..d73c2831d0 --- /dev/null +++ b/plugins/AnonymousFave/anonfavorform.php @@ -0,0 +1,74 @@ +. + * + * @category Form + * @package StatusNet + * @author Zach Copley + * @copyright 20010 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')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/form.php'; + +/** + * Form for favoring a notice anonymously + * + * @category Form + * @package StatusNet + * @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/ + * + * @see AnonDisfavorForm + */ + +class AnonFavorForm extends FavorForm +{ + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param Notice $notice notice to favor + */ + + function __construct($out=null, $notice=null) + { + parent::__construct($out, $notice); + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('AnonFavor'); + } + +} From 73297d374974a431b40d0cc45a7180613b8d5762 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 28 Sep 2010 19:07:45 -0700 Subject: [PATCH 21/30] New DB_DataObject for storing favorites tally --- plugins/AnonymousFave/AnonymousFavePlugin.php | 36 +++ plugins/AnonymousFave/Fave_tally.php | 209 ++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 plugins/AnonymousFave/Fave_tally.php diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 3dccaf538a..98f747748c 100644 --- a/plugins/AnonymousFave/AnonymousFavePlugin.php +++ b/plugins/AnonymousFave/AnonymousFavePlugin.php @@ -55,6 +55,39 @@ class AnonymousFavePlugin extends Plugin { common_ensure_session(); } + /** + * Hook for ensuring our tables are created + * + * Ensures the fave_tally table is there and has the right columns + * + * @return boolean hook return + */ + + function onCheckSchema() + { + $schema = Schema::get(); + + // For storing total number of times a notice has been faved + + $schema->ensureTable('fave_tally', + array( + new ColumnDef('notice_id', 'integer', null, false, 'PRI'), + new ColumnDef('count', 'integer', null, false), + new ColumnDef( + 'modified', + 'timestamp', + null, + false, + null, + 'CURRENT_TIMESTAMP', + 'on update CURRENT_TIMESTAMP' + ) + ) + ); + + return true; + } + function onEndShowHTML($action) { if (!common_logged_in()) { @@ -75,6 +108,9 @@ class AnonymousFavePlugin extends Plugin { $dir = dirname(__FILE__); switch ($cls) { + case 'Fave_tally': + include_once $dir . '/' . $cls . '.php'; + return false; case 'AnonFavorAction': include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; diff --git a/plugins/AnonymousFave/Fave_tally.php b/plugins/AnonymousFave/Fave_tally.php new file mode 100644 index 0000000000..fc7e4a5790 --- /dev/null +++ b/plugins/AnonymousFave/Fave_tally.php @@ -0,0 +1,209 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2010, 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')) { + exit(1); +} + +require_once INSTALLDIR . '/classes/Memcached_DataObject.php'; + +/** + * Data class for favorites tally + * + * A class representing a total number of times a notice has been favorited + * + * @category Action + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + */ + +class Fave_tally extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'fave_tally'; // table name + public $notice_id; // int(4) primary_key not_null + public $count; // int(4) primary_key not_null + public $modified; // datetime not_null default_0000-00-00%2000%3A00%3A00 + + /* Static get */ + function staticGet($k, $v = NULL) { return Memcached_DataObject::staticGet('Fave_tally', $k, $v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + /** + * return table definition for DB_DataObject + * + * @return array array of column definitions + */ + + function table() + { + return array( + 'notice_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + 'count' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + 'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL + ); + } + + /** + * return key definitions for DB_DataObject + * + * @return array key definitions + */ + + function keys() + { + return array('notice_id' => 'K'); + } + + /** + * return key definitions for DB_DataObject + * + * @return array key definitions + */ + + function keyTypes() + { + return $this->keys(); + } + + /** + * Magic formula for non-autoincrementing integer primary keys + * + * @return array magic three-false array that stops auto-incrementing. + */ + + function sequenceKey() + { + return array(false, false, false); + } + + /** + * Get a single object with multiple keys + * + * @param array $kv Map of key-value pairs + * + * @return User_flag_profile found object or null + */ + + function pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('Fave_tally', $kv); + } + + /** + * Increment a notice's tally + * + * @param integer $notice_id ID of notice we're tallying + * + * @return integer the total times the notice has been faved + */ + + static function increment($notice_id) + { + $tally = Fave_tally::ensureTally($notice_id); + $count = $tally->count + 1; + $tally->count = $count; + $result = $tally->update(); + $tally->free(); + + if ($result === false) { + $msg = sprintf( + _m("Couldn't update favorite tally for notice ID %d.", $notice_id) + ); + throw new ServerException($msg); + } + + return $count; + } + + /** + * Decrement a notice's tally + * + * @param integer $notice_id ID of notice we're tallying + * + * @return integer the total times the notice has been faved + */ + + static function decrement($notice_id) + { + $tally = Fave_tally::ensureTally($notice_id); + + $count = 0; + + if ($tally->count > 0) { + $count = $tally->count - 1; + $tally->count = $count; + $result = $tally->update(); + $tally->free(); + + if ($result === false) { + $msg = sprintf( + _m("Couldn't update favorite tally for notice ID %d.", $notice_id) + ); + throw new ServerException($msg); + } + } + + return $count; + } + + /** + * Ensure a tally exists for a given notice. If we can't find + * one create one. + * + * @param integer $notice_id + * + * @return Fave_tally the tally data object + */ + + static function ensureTally($notice_id) + { + $tally = new Fave_tally(); + $result = $tally->get($notice_id); + + if (empty($result)) { + $tally->notice_id = $notice_id; + $tally->count = 0; + if ($tally->insert() === false) { + $msg = sprintf( + _m("Couldn't create favorite tally for notice ID %d.", $notice_id) + ); + throw new ServerException($msg); + } + } + + return $tally; + } +} From 331502a9790806ca48e61eb94d9489ddf7baeb55 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 12:49:26 -0700 Subject: [PATCH 22/30] Add Start/EndShowNoticeInfo events --- EVENTS.txt | 6 ++++++ lib/noticelist.php | 14 +++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index d722bc4ac7..7f6890ff0c 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -263,6 +263,12 @@ StartShowNoticeItem: just before showing the notice item EndShowNoticeItem: just after showing the notice item - $item: the NoticeListItem object being shown +StartShowNoticeInfo: just before showing notice info +- $item: The NoticeListItem object being shown + +EndShowNoticeInfo: just after showing notice info +- $item: The NoticeListItem object being shown + StartShowNoticeOptions: just before showing notice options like fave, repeat, etc. - $item: the NoticeListItem object being shown diff --git a/lib/noticelist.php b/lib/noticelist.php index cc460005ad..df1533980a 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -226,11 +226,15 @@ class NoticeListItem extends Widget function showNoticeInfo() { $this->out->elementStart('div', 'entry-content'); - $this->showNoticeLink(); - $this->showNoticeSource(); - $this->showNoticeLocation(); - $this->showContext(); - $this->showRepeat(); + if (Event::handle('StartShowNoticeInfo', array($this))) { + $this->showNoticeLink(); + $this->showNoticeSource(); + $this->showNoticeLocation(); + $this->showContext(); + $this->showRepeat(); + Event::handle('EndShowNoticeInfo', array($this)); + } + $this->out->elementEnd('div'); } From 5b49fc25bfe89c081ad1aa5af2005616eb319484 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 12:49:53 -0700 Subject: [PATCH 23/30] - Increment/decrement notice fave tally - Display tally in notice output --- plugins/AnonymousFave/AnonymousFavePlugin.php | 31 +++++ plugins/AnonymousFave/Fave_tally.php | 108 +++++++++++------- 2 files changed, 96 insertions(+), 43 deletions(-) diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 98f747748c..984625a881 100644 --- a/plugins/AnonymousFave/AnonymousFavePlugin.php +++ b/plugins/AnonymousFave/AnonymousFavePlugin.php @@ -136,6 +136,27 @@ class AnonymousFavePlugin extends Plugin { return true; } + + function onEndShowNoticeInfo($item) + { + common_debug("XXXXXXXXXXX onEndShowNoticeInfo"); + + $tally = Fave_tally::ensureTally($item->notice->id); + + if (!empty($tally)) { + $item->out->elementStart( + 'div', + array( + 'id' => 'notice-' . $item->notice->id . '-tally', + 'class' => 'notice-tally' + ) + ); + $item->out->raw(sprintf(_m("favored %d times"), $tally->count)); + $item->out->elementEnd('div'); + } + return true; + } + function onStartShowNoticeOptions($item) { if (!common_logged_in()) { @@ -166,6 +187,16 @@ class AnonymousFavePlugin extends Plugin { return true; } + function onEndFavorNotice($profile, $notice) + { + $tally = Fave_tally::increment($notice->id); + } + + function onEndDisfavorNotice($profile, $notice) + { + $tally = Fave_tally::decrement($notice->id); + } + function createAnonProfile() { // Get the anon user's IP, and turn it into a nickname diff --git a/plugins/AnonymousFave/Fave_tally.php b/plugins/AnonymousFave/Fave_tally.php index fc7e4a5790..0eaa3fdc76 100644 --- a/plugins/AnonymousFave/Fave_tally.php +++ b/plugins/AnonymousFave/Fave_tally.php @@ -52,7 +52,7 @@ class Fave_tally extends Memcached_DataObject public $__table = 'fave_tally'; // table name public $notice_id; // int(4) primary_key not_null - public $count; // int(4) primary_key not_null + public $count; // int(4) not_null public $modified; // datetime not_null default_0000-00-00%2000%3A00%3A00 /* Static get */ @@ -79,31 +79,48 @@ class Fave_tally extends Memcached_DataObject /** * return key definitions for DB_DataObject * - * @return array key definitions + * DB_DataObject needs to know about keys that the table has, since it + * won't appear in StatusNet's own keys list. In most cases, this will + * simply reference your keyTypes() function. + * + * @return array list of key field names */ function keys() + { + return array_keys($this->keyTypes()); + } + + /** + * return key definitions for Memcached_DataObject + * + * Our caching system uses the same key definitions, but uses a different + * method to get them. This key information is used to store and clear + * cached data, so be sure to list any key that will be used for static + * lookups. + * + * @return array associative array of key definitions, field name to type: + * 'K' for primary key: for compound keys, add an entry for each component; + * 'U' for unique keys: compound keys are not well supported here. + */ + + function keyTypes() { return array('notice_id' => 'K'); } - /** - * return key definitions for DB_DataObject - * - * @return array key definitions - */ - - function keyTypes() - { - return $this->keys(); - } - /** * Magic formula for non-autoincrementing integer primary keys * + * If a table has a single integer column as its primary key, DB_DataObject + * assumes that the column is auto-incrementing and makes a sequence table + * to do this incrementation. Since we don't need this for our class, we + * overload this method and return the magic formula that DB_DataObject needs. + * * @return array magic three-false array that stops auto-incrementing. */ + function sequenceKey() { return array(false, false, false); @@ -125,80 +142,85 @@ class Fave_tally extends Memcached_DataObject /** * Increment a notice's tally * - * @param integer $notice_id ID of notice we're tallying + * @param integer $noticeID ID of notice we're tallying * - * @return integer the total times the notice has been faved + * @return Fave_tally $tally the tally data object */ - static function increment($notice_id) + static function increment($noticeID) { - $tally = Fave_tally::ensureTally($notice_id); - $count = $tally->count + 1; - $tally->count = $count; - $result = $tally->update(); - $tally->free(); + common_debug("XXXXXXXXX Fave_tally::increment()"); + $tally = Fave_tally::ensureTally($noticeID); - if ($result === false) { + $orig = clone($tally); + $tally->count++; + $result = $tally->update($orig); + + if (!$result) { $msg = sprintf( - _m("Couldn't update favorite tally for notice ID %d.", $notice_id) + _m("Couldn't update favorite tally for notice ID %d."), + $notice_id ); throw new ServerException($msg); } - return $count; + return $tally; } /** * Decrement a notice's tally * - * @param integer $notice_id ID of notice we're tallying + * @param integer $noticeID ID of notice we're tallying * - * @return integer the total times the notice has been faved + * @return Fave_tally $tally the tally data object */ - static function decrement($notice_id) + static function decrement($noticeID) { - $tally = Fave_tally::ensureTally($notice_id); + common_debug("XXXXXXXXX Fave_tally::decrement()"); - $count = 0; + $tally = Fave_tally::ensureTally($noticeID); if ($tally->count > 0) { - $count = $tally->count - 1; - $tally->count = $count; - $result = $tally->update(); - $tally->free(); + $orig = clone($tally); + $tally->count--; + $result = $tally->update($orig); - if ($result === false) { + if (!$result) { $msg = sprintf( - _m("Couldn't update favorite tally for notice ID %d.", $notice_id) + _m("Couldn't update favorite tally for notice ID %d."), + $notice_id ); throw new ServerException($msg); } } - return $count; + return $tally; } /** * Ensure a tally exists for a given notice. If we can't find * one create one. * - * @param integer $notice_id + * @param integer $noticeID * * @return Fave_tally the tally data object */ - static function ensureTally($notice_id) + static function ensureTally($noticeID) { - $tally = new Fave_tally(); - $result = $tally->get($notice_id); + $tally = Fave_tally::staticGet('notice_id', $notice_id); - if (empty($result)) { + if (!$tally) { + common_debug("Fave_tally::ensureTally - creating tally for notice " . $notice_id); + $tally = new Fave_tally(); $tally->notice_id = $notice_id; $tally->count = 0; - if ($tally->insert() === false) { + $result = $tally->insert(); + if (!$result) { $msg = sprintf( - _m("Couldn't create favorite tally for notice ID %d.", $notice_id) + _m("Couldn't create favorite tally for notice ID %d."), + $notice_id ); throw new ServerException($msg); } From 4d6973cd700ff3fa9c84ead56e64852832281b31 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 13:29:58 -0700 Subject: [PATCH 24/30] Intialize new fave tallys with total existing fave count per notice --- plugins/AnonymousFave/Fave_tally.php | 38 ++++++++++++++----- .../scripts/initialize_fave_tallys.php | 38 +++++++++++++++++++ 2 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 plugins/AnonymousFave/scripts/initialize_fave_tallys.php diff --git a/plugins/AnonymousFave/Fave_tally.php b/plugins/AnonymousFave/Fave_tally.php index 0eaa3fdc76..35ace6d01b 100644 --- a/plugins/AnonymousFave/Fave_tally.php +++ b/plugins/AnonymousFave/Fave_tally.php @@ -36,7 +36,7 @@ require_once INSTALLDIR . '/classes/Memcached_DataObject.php'; /** * Data class for favorites tally * - * A class representing a total number of times a notice has been favorited + * A class representing a total number of times a notice has been favored * * @category Action * @package StatusNet @@ -159,7 +159,7 @@ class Fave_tally extends Memcached_DataObject if (!$result) { $msg = sprintf( _m("Couldn't update favorite tally for notice ID %d."), - $notice_id + $noticeID ); throw new ServerException($msg); } @@ -189,7 +189,7 @@ class Fave_tally extends Memcached_DataObject if (!$result) { $msg = sprintf( _m("Couldn't update favorite tally for notice ID %d."), - $notice_id + $noticeID ); throw new ServerException($msg); } @@ -200,7 +200,7 @@ class Fave_tally extends Memcached_DataObject /** * Ensure a tally exists for a given notice. If we can't find - * one create one. + * one create one with the total number of existing faves * * @param integer $noticeID * @@ -209,18 +209,18 @@ class Fave_tally extends Memcached_DataObject static function ensureTally($noticeID) { - $tally = Fave_tally::staticGet('notice_id', $notice_id); + $tally = Fave_tally::staticGet('notice_id', $noticeID); if (!$tally) { - common_debug("Fave_tally::ensureTally - creating tally for notice " . $notice_id); + common_debug("Fave_tally::ensureTally - creating tally for notice " . $noticeID); $tally = new Fave_tally(); - $tally->notice_id = $notice_id; - $tally->count = 0; + $tally->notice_id = $noticeID; + $tally->count = Fave_tally::countExistingFaves($noticeID); $result = $tally->insert(); if (!$result) { $msg = sprintf( _m("Couldn't create favorite tally for notice ID %d."), - $notice_id + $noticeID ); throw new ServerException($msg); } @@ -228,4 +228,24 @@ class Fave_tally extends Memcached_DataObject return $tally; } + + /** + * Count the number of faves a notice already has. Used to initalize + * a tally for a notice. + * + * @param integer $noticeID ID of the notice to count faves for + * + * @return integer $total total number of time the notice has been favored + */ + + static function countExistingFaves($noticeID) + { + $fave = new Fave(); + $fave->notice_id = $noticeID; + $total = $fave->count(); + + common_debug("ZZZZZZZ notice " . $noticeID . ' has ' . $total . " faves"); + + return $total; + } } diff --git a/plugins/AnonymousFave/scripts/initialize_fave_tallys.php b/plugins/AnonymousFave/scripts/initialize_fave_tallys.php new file mode 100644 index 0000000000..f7ea6d1ef7 --- /dev/null +++ b/plugins/AnonymousFave/scripts/initialize_fave_tallys.php @@ -0,0 +1,38 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..')); + +$helptext = <<find(); + +while ($notice->fetch()) { + Fave_tally::ensureTally($notice->id); +} + From 9109fe3c631ebef14f2de061fc6fc565ee0f7174 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 13:31:01 -0700 Subject: [PATCH 25/30] Set initialize_fave_tallys.php executable --- plugins/AnonymousFave/scripts/initialize_fave_tallys.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 plugins/AnonymousFave/scripts/initialize_fave_tallys.php diff --git a/plugins/AnonymousFave/scripts/initialize_fave_tallys.php b/plugins/AnonymousFave/scripts/initialize_fave_tallys.php old mode 100644 new mode 100755 From 0ac333ec801b8df68da69fa38a13d07960bd9649 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 14:07:46 -0700 Subject: [PATCH 26/30] Add Start/EndFavorNoticeForm and Start/EndDisFavorNoticeForm hooks --- EVENTS.txt | 16 ++++++++++++++++ lib/disfavorform.php | 10 +++++++--- lib/favorform.php | 9 ++++++--- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index 7f6890ff0c..2496416173 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -774,6 +774,22 @@ EndDisfavorNotice: After saving a notice as a favorite - $profile: profile of the person faving (can be remote!) - $notice: notice being faved +StartFavorNoticeForm: starting the data in the form for favoring a notice +- $FavorForm: the favor form being shown +- $notice: notice being favored + +EndFavorNoticeForm: Ending the data in the form for favoring a notice +- $FavorForm: the favor form being shown +- $notice: notice being favored + +StartDisFavorNoticeForm: starting the data in the form for disfavoring a notice +- $DisfavorForm: the disfavor form being shown +- $notice: notice being difavored + +EndDisFavorNoticeForm: Ending the data in the form for disfavoring a notice +- $DisfavorForm: the disfavor form being shown +- $notice: notice being disfavored + StartFindMentions: start finding mentions in a block of text - $sender: sender profile - $text: plain text version of the notice diff --git a/lib/disfavorform.php b/lib/disfavorform.php index 5b135b38ad..6023766d7b 100644 --- a/lib/disfavorform.php +++ b/lib/disfavorform.php @@ -123,9 +123,13 @@ class DisfavorForm extends Form function formData() { - $this->out->hidden('notice-n'.$this->notice->id, - $this->notice->id, - 'notice'); + 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)); + } + } /** diff --git a/lib/favorform.php b/lib/favorform.php index 625df7c8b5..4e2891ffd5 100644 --- a/lib/favorform.php +++ b/lib/favorform.php @@ -123,9 +123,12 @@ class FavorForm extends Form function formData() { - $this->out->hidden('notice-n'.$this->notice->id, - $this->notice->id, - 'notice'); + 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)); + } } /** From 0fe0f421731ee3cfa5e0bafd08559cc9bfc44422 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 14:08:31 -0700 Subject: [PATCH 27/30] Ajax update notice tally --- plugins/AnonymousFave/AnonymousFavePlugin.php | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 984625a881..41542c8493 100644 --- a/plugins/AnonymousFave/AnonymousFavePlugin.php +++ b/plugins/AnonymousFave/AnonymousFavePlugin.php @@ -136,27 +136,6 @@ class AnonymousFavePlugin extends Plugin { return true; } - - function onEndShowNoticeInfo($item) - { - common_debug("XXXXXXXXXXX onEndShowNoticeInfo"); - - $tally = Fave_tally::ensureTally($item->notice->id); - - if (!empty($tally)) { - $item->out->elementStart( - 'div', - array( - 'id' => 'notice-' . $item->notice->id . '-tally', - 'class' => 'notice-tally' - ) - ); - $item->out->raw(sprintf(_m("favored %d times"), $tally->count)); - $item->out->elementEnd('div'); - } - return true; - } - function onStartShowNoticeOptions($item) { if (!common_logged_in()) { @@ -187,6 +166,33 @@ class AnonymousFavePlugin extends Plugin { return true; } + function onEndFavorNoticeForm($form, $notice) + { + $this->showTally($form->out, $notice); + } + + function onEndDisFavorNoticeForm($form, $notice) + { + $this->showTally($form->out, $notice); + } + + function showTally($out, $notice) + { + $tally = Fave_tally::ensureTally($notice->id); + + if (!empty($tally)) { + $out->elementStart( + 'div', + array( + 'id' => 'notice-' . $notice->id . '-tally', + 'class' => 'notice-tally' + ) + ); + $out->raw(sprintf(_m("favored %d times"), $tally->count)); + $out->elementEnd('div'); + } + } + function onEndFavorNotice($profile, $notice) { $tally = Fave_tally::increment($notice->id); From f79f44801cfd76b7e9e4cbfb94917bc8b395a886 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 15:52:18 -0700 Subject: [PATCH 28/30] - Lookup anon profiles by ID (safer because they are guranteed to be unique) and probably faster - Obfuscate the anonymous user session token to make it hard to figure out the profile ID --- plugins/AnonymousFave/AnonymousFavePlugin.php | 56 ++++++++++++------- plugins/AnonymousFave/anondisfavor.php | 10 +--- plugins/AnonymousFave/anonfavor.php | 9 +-- 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 41542c8493..264cad1748 100644 --- a/plugins/AnonymousFave/AnonymousFavePlugin.php +++ b/plugins/AnonymousFave/AnonymousFavePlugin.php @@ -151,7 +151,7 @@ class AnonymousFavePlugin extends Plugin { if (!common_logged_in()) { - $profile = $this->getAnonProfile(); + $profile = AnonymousFavePlugin::getAnonProfile(); if (!empty($profile)) { if ($profile->hasFave($item->notice)) { $disfavor = new AnonDisFavorForm($item->out, $item->notice); @@ -207,42 +207,58 @@ class AnonymousFavePlugin extends Plugin { // Get the anon user's IP, and turn it into a nickname list($proxy, $ip) = common_client_ip(); - // IP + time + random number should avoid collisions - $nickname = 'anonymous-' . $ip . '-' . time() . '-' . common_good_rand(5); + + // IP + time + random number should help to avoid collisions + $baseNickname = $ip . '-' . time() . '-' . common_good_rand(5); $profile = new Profile(); - $profile->nickname = $nickname; + $profile->nickname = $baseNickname; $id = $profile->insert(); - if (!empty($id)) { - common_log( - LOG_INFO, - "AnonymousFavePlugin - created profile for anonymous user from IP: " - . $ip - . ', nickname = ' - . $nickname - ); + if (!$id) { + throw new ServerException(_m("Couldn't create anonymous user session")); } + // Stick the Profile ID into the nickname + $orig = clone($profile); + + $profile->nickname = 'anon-' . $id . '-' . $baseNickname; + $result = $profile->update($orig); + + if (!$result) { + throw new ServerException(_m("Couldn't create anonymous user session")); + } + + common_log( + LOG_INFO, + "AnonymousFavePlugin - created profile for anonymous user from IP: " + . $ip + . ', nickname = ' + . $profile->nickname + ); + return $profile; } - function getAnonProfile() { + static function getAnonProfile() { - $anon = $_SESSION['anon_nickname']; + $token = $_SESSION['anon_token']; + $anon = base64_decode($token); $profile = null; - if (!empty($anon)) { - $profile = Profile::staticGet('nickname', $anon); + if (!empty($anon) && substr($anon, 0, 5) == 'anon-') { + $parts = explode('-', $anon); + $id = $parts[1]; + // Do Profile lookup by ID instead of nickname for safety/performance + $profile = Profile::staticGet('id', $id); } else { $profile = $this->createAnonProfile(); - $_SESSION['anon_nickname'] = $profile->nickname; + // Obfuscate so it's hard to figure out the Profile ID + $_SESSION['anon_token'] = base64_encode($profile->nickname); } - if (!empty($profile)) { - return $profile; - } + return $profile; } /** diff --git a/plugins/AnonymousFave/anondisfavor.php b/plugins/AnonymousFave/anondisfavor.php index 9fd56fdc32..f39d5a7780 100644 --- a/plugins/AnonymousFave/anondisfavor.php +++ b/plugins/AnonymousFave/anondisfavor.php @@ -54,15 +54,7 @@ class AnonDisfavorAction extends RedirectingAction { parent::handle($args); - $anon = $_SESSION['anon_nickname']; - - $profile = Profile::staticGet('nickname', $anon); - - if (empty($profile)) { - common_debug( - "AnonDisFavorAction - Anon user tried to disfave a notice but doesn't have a profile." - ); - } + $profile = AnonymousFavePlugin::getAnonProfile(); if (empty($profile) || $_SERVER['REQUEST_METHOD'] != 'POST') { $this->clientError( diff --git a/plugins/AnonymousFave/anonfavor.php b/plugins/AnonymousFave/anonfavor.php index c972f202e4..58570ced9a 100644 --- a/plugins/AnonymousFave/anonfavor.php +++ b/plugins/AnonymousFave/anonfavor.php @@ -54,14 +54,7 @@ class AnonFavorAction extends RedirectingAction { parent::handle($args); - $anon = $_SESSION['anon_nickname']; - $profile = Profile::staticGet('nickname', $anon); - - if (empty($profile)) { - common_debug( - "AnonFavorAction - Anon user tried to fave a notice but doesn't have a profile." - ); - } + $profile = AnonymousFavePlugin::getAnonProfile(); if (empty($profile) || $_SERVER['REQUEST_METHOD'] != 'POST') { $this->clientError( From 54f19da3ab534f5f416fc4902841439ddbe0f10e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 16:11:32 -0700 Subject: [PATCH 29/30] Make createAnonProfile() static --- plugins/AnonymousFave/AnonymousFavePlugin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 264cad1748..47eebef9ba 100644 --- a/plugins/AnonymousFave/AnonymousFavePlugin.php +++ b/plugins/AnonymousFave/AnonymousFavePlugin.php @@ -203,7 +203,7 @@ class AnonymousFavePlugin extends Plugin { $tally = Fave_tally::decrement($notice->id); } - function createAnonProfile() { + static function createAnonProfile() { // Get the anon user's IP, and turn it into a nickname list($proxy, $ip) = common_client_ip(); @@ -253,7 +253,7 @@ class AnonymousFavePlugin extends Plugin { // Do Profile lookup by ID instead of nickname for safety/performance $profile = Profile::staticGet('id', $id); } else { - $profile = $this->createAnonProfile(); + $profile = AnonymousFavePlugin::createAnonProfile(); // Obfuscate so it's hard to figure out the Profile ID $_SESSION['anon_token'] = base64_encode($profile->nickname); } From 8e7532245a9ba98c6a70a114cedb7c841482bf2a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 16:28:29 -0700 Subject: [PATCH 30/30] Remove debugging statements --- plugins/AnonymousFave/AnonymousFavePlugin.php | 4 ++-- plugins/AnonymousFave/Fave_tally.php | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 47eebef9ba..72093e7f7e 100644 --- a/plugins/AnonymousFave/AnonymousFavePlugin.php +++ b/plugins/AnonymousFave/AnonymousFavePlugin.php @@ -216,7 +216,7 @@ class AnonymousFavePlugin extends Plugin { $id = $profile->insert(); if (!$id) { - throw new ServerException(_m("Couldn't create anonymous user session")); + throw new ServerException(_m("Couldn't create anonymous user session.")); } // Stick the Profile ID into the nickname @@ -226,7 +226,7 @@ class AnonymousFavePlugin extends Plugin { $result = $profile->update($orig); if (!$result) { - throw new ServerException(_m("Couldn't create anonymous user session")); + throw new ServerException(_m("Couldn't create anonymous user session.")); } common_log( diff --git a/plugins/AnonymousFave/Fave_tally.php b/plugins/AnonymousFave/Fave_tally.php index 35ace6d01b..b350d5a0a5 100644 --- a/plugins/AnonymousFave/Fave_tally.php +++ b/plugins/AnonymousFave/Fave_tally.php @@ -149,7 +149,6 @@ class Fave_tally extends Memcached_DataObject static function increment($noticeID) { - common_debug("XXXXXXXXX Fave_tally::increment()"); $tally = Fave_tally::ensureTally($noticeID); $orig = clone($tally); @@ -177,8 +176,6 @@ class Fave_tally extends Memcached_DataObject static function decrement($noticeID) { - common_debug("XXXXXXXXX Fave_tally::decrement()"); - $tally = Fave_tally::ensureTally($noticeID); if ($tally->count > 0) { @@ -212,7 +209,6 @@ class Fave_tally extends Memcached_DataObject $tally = Fave_tally::staticGet('notice_id', $noticeID); if (!$tally) { - common_debug("Fave_tally::ensureTally - creating tally for notice " . $noticeID); $tally = new Fave_tally(); $tally->notice_id = $noticeID; $tally->count = Fave_tally::countExistingFaves($noticeID); @@ -243,9 +239,6 @@ class Fave_tally extends Memcached_DataObject $fave = new Fave(); $fave->notice_id = $noticeID; $total = $fave->count(); - - common_debug("ZZZZZZZ notice " . $noticeID . ' has ' . $total . " faves"); - return $total; } }