From c10f17dc6b1e357e37fea31adbf74de003e6429c Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 28 Sep 2010 17:17:09 -0700 Subject: 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 (limited to 'plugins') diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php new file mode 100644 index 000000000..3dccaf538 --- /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 000000000..9fd56fdc3 --- /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 000000000..c347ed7b4 --- /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 000000000..c972f202e --- /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 000000000..d73c2831d --- /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'); + } + +} -- cgit v1.2.3-54-g00ecf From 73297d374974a431b40d0cc45a7180613b8d5762 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 28 Sep 2010 19:07:45 -0700 Subject: 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 (limited to 'plugins') diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 3dccaf538..98f747748 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 000000000..fc7e4a579 --- /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; + } +} -- cgit v1.2.3-54-g00ecf From 5b49fc25bfe89c081ad1aa5af2005616eb319484 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 12:49:53 -0700 Subject: - Increment/decrement notice fave tally - Display tally in notice output --- plugins/AnonymousFave/AnonymousFavePlugin.php | 31 +++++++++ plugins/AnonymousFave/Fave_tally.php | 94 +++++++++++++++++---------- 2 files changed, 89 insertions(+), 36 deletions(-) (limited to 'plugins') diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 98f747748..984625a88 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 fc7e4a579..0eaa3fdc7 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('notice_id' => 'K'); + return array_keys($this->keyTypes()); } /** - * return key definitions for DB_DataObject + * return key definitions for Memcached_DataObject * - * @return array key definitions + * 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 $this->keys(); + return array('notice_id' => 'K'); } /** * 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); + + $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; } /** * 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); } -- cgit v1.2.3-54-g00ecf From 4d6973cd700ff3fa9c84ead56e64852832281b31 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 13:29:58 -0700 Subject: 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 (limited to 'plugins') diff --git a/plugins/AnonymousFave/Fave_tally.php b/plugins/AnonymousFave/Fave_tally.php index 0eaa3fdc7..35ace6d01 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 000000000..f7ea6d1ef --- /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); +} + -- cgit v1.2.3-54-g00ecf From 9109fe3c631ebef14f2de061fc6fc565ee0f7174 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 13:31:01 -0700 Subject: 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 (limited to 'plugins') diff --git a/plugins/AnonymousFave/scripts/initialize_fave_tallys.php b/plugins/AnonymousFave/scripts/initialize_fave_tallys.php old mode 100644 new mode 100755 -- cgit v1.2.3-54-g00ecf From 0fe0f421731ee3cfa5e0bafd08559cc9bfc44422 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 14:08:31 -0700 Subject: Ajax update notice tally --- plugins/AnonymousFave/AnonymousFavePlugin.php | 48 +++++++++++++++------------ 1 file changed, 27 insertions(+), 21 deletions(-) (limited to 'plugins') diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 984625a88..41542c849 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); -- cgit v1.2.3-54-g00ecf From f79f44801cfd76b7e9e4cbfb94917bc8b395a886 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 15:52:18 -0700 Subject: - 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(-) (limited to 'plugins') diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 41542c849..264cad174 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 9fd56fdc3..f39d5a778 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 c972f202e..58570ced9 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( -- cgit v1.2.3-54-g00ecf From 54f19da3ab534f5f416fc4902841439ddbe0f10e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 16:11:32 -0700 Subject: Make createAnonProfile() static --- plugins/AnonymousFave/AnonymousFavePlugin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 264cad174..47eebef9b 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); } -- cgit v1.2.3-54-g00ecf From 8e7532245a9ba98c6a70a114cedb7c841482bf2a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 29 Sep 2010 16:28:29 -0700 Subject: Remove debugging statements --- plugins/AnonymousFave/AnonymousFavePlugin.php | 4 ++-- plugins/AnonymousFave/Fave_tally.php | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) (limited to 'plugins') diff --git a/plugins/AnonymousFave/AnonymousFavePlugin.php b/plugins/AnonymousFave/AnonymousFavePlugin.php index 47eebef9b..72093e7f7 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 35ace6d01..b350d5a0a 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; } } -- cgit v1.2.3-54-g00ecf