diff --git a/plugins/Poll/Poll.php b/plugins/Poll/Poll.php new file mode 100644 index 0000000000..60ec4399fd --- /dev/null +++ b/plugins/Poll/Poll.php @@ -0,0 +1,250 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2011, 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); +} + +/** + * For storing the poll options and such + * + * @category PollPlugin + * @package StatusNet + * @author Brion Vibber + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * @see DB_DataObject + */ + +class Poll extends Managed_DataObject +{ + public $__table = 'poll'; // table name + public $id; // char(36) primary key not null -> UUID + public $profile_id; // int -> profile.id + public $question; // text + public $options; // text; newline(?)-delimited + public $created; // datetime + + /** + * Get an instance by key + * + * This is a utility method to get a single instance with a given key value. + * + * @param string $k Key to use to lookup (usually 'user_id' for this class) + * @param mixed $v Value to lookup + * + * @return User_greeting_count object found, or null for no hits + * + */ + + function staticGet($k, $v=null) + { + return Memcached_DataObject::staticGet('Poll', $k, $v); + } + + /** + * Get an instance by compound key + * + * This is a utility method to get a single instance with a given set of + * key-value pairs. Usually used for the primary key for a compound key; thus + * the name. + * + * @param array $kv array of key-value mappings + * + * @return Bookmark object found, or null for no hits + * + */ + + function pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('Poll', $kv); + } + + /** + * The One True Thingy that must be defined and declared. + */ + public static function schemaDef() + { + return array( + 'description' => 'Per-notice poll data for Poll plugin', + 'fields' => array( + 'id' => array('type' => 'char', 'length' => 36, 'not null' => true, 'description' => 'UUID'), + 'uri' => array('type' => 'varchar', 'length' => 255, 'not null' => true), + 'profile_id' => array('type' => 'int'), + 'question' => array('type' => 'text'), + 'options' => array('type' => 'text'), + 'created' => array('type' => 'datetime', 'not null' => true), + ), + 'primary key' => array('id'), + 'unique keys' => array( + 'poll_uri_key' => array('uri'), + ), + ); + } + + /** + * Get a bookmark based on a notice + * + * @param Notice $notice Notice to check for + * + * @return Poll found poll or null + */ + + function getByNotice($notice) + { + return self::staticGet('uri', $notice->uri); + } + + function getOptions() + { + return explode("\n", $this->options); + } + + function getNotice() + { + return Notice::staticGet('uri', $this->uri); + } + + function bestUrl() + { + return $this->getNotice()->bestUrl(); + } + + /** + * Get the response of a particular user to this poll, if any. + * + * @param Profile $profile + * @return Poll_response object or null + */ + function getResponse(Profile $profile) + { + $pr = new Poll_response(); + $pr->poll_id = $this->id; + $pr->profile_id = $profile->id; + $pr->find(); + if ($pr->fetch()) { + return $pr; + } else { + return null; + } + } + + function countResponses() + { + $pr = new Poll_response(); + $pr->poll_id = $this->id; + $pr->groupBy('selection'); + $pr->selectAdd('count(profile_id) as votes'); + $pr->find(); + + $raw = array(); + while ($pr->fetch()) { + $raw[$pr->selection] = $pr->votes; + } + + $counts = array(); + foreach (array_keys($this->getOptions()) as $key) { + if (isset($raw[$key])) { + $counts[$key] = $raw[$key]; + } else { + $counts[$key] = 0; + } + } + return $counts; + } + + /** + * Save a new poll notice + * + * @param Profile $profile + * @param string $question + * @param array $opts (poll responses) + * + * @return Notice saved notice + */ + + static function saveNew($profile, $question, $opts, $options=null) + { + if (empty($options)) { + $options = array(); + } + + $p = new Poll(); + + $p->id = UUID::gen(); + $p->profile_id = $profile->id; + $p->question = $question; + $p->options = implode("\n", $opts); + + if (array_key_exists('created', $options)) { + $p->created = $options['created']; + } else { + $p->created = common_sql_now(); + } + + if (array_key_exists('uri', $options)) { + $p->uri = $options['uri']; + } else { + $p->uri = common_local_url('showpoll', + array('id' => $p->id)); + } + + $p->insert(); + + $content = sprintf(_m('Poll: %s %s'), + $question, + $p->uri); + $rendered = sprintf(_m('Poll: %s'), + htmlspecialchars($p->uri), + htmlspecialchars($question)); + + $tags = array('poll'); + $replies = array(); + + $options = array_merge(array('urls' => array(), + 'rendered' => $rendered, + 'tags' => $tags, + 'replies' => $replies, + 'object_type' => PollPlugin::POLL_OBJECT), + $options); + + if (!array_key_exists('uri', $options)) { + $options['uri'] = $p->uri; + } + + $saved = Notice::saveNew($profile->id, + $content, + array_key_exists('source', $options) ? + $options['source'] : 'web', + $options); + + return $saved; + } +} diff --git a/plugins/Poll/PollPlugin.php b/plugins/Poll/PollPlugin.php new file mode 100644 index 0000000000..6fa95aa0e7 --- /dev/null +++ b/plugins/Poll/PollPlugin.php @@ -0,0 +1,293 @@ +. + * + * @category PollPlugin + * @package StatusNet + * @author Brion Vibber + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Poll plugin main class + * + * @category PollPlugin + * @package StatusNet + * @author Brion Vibber + * @author Evan Prodromou + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class PollPlugin extends MicroAppPlugin +{ + const VERSION = '0.1'; + const POLL_OBJECT = 'http://apinamespace.org/activitystreams/object/poll'; + + /** + * Database schema setup + * + * @see Schema + * @see ColumnDef + * + * @return boolean hook value; true means continue processing, false means stop. + */ + + function onCheckSchema() + { + $schema = Schema::get(); + $schema->ensureTable('poll', Poll::schemaDef()); + $schema->ensureTable('poll_response', Poll_response::schemaDef()); + return true; + } + + /** + * Show the CSS necessary for this plugin + * + * @param Action $action the action being run + * + * @return boolean hook value + */ + + function onEndShowStyles($action) + { + $action->cssLink($this->path('poll.css')); + return true; + } + + /** + * Load related modules when needed + * + * @param string $cls Name of the class to be loaded + * + * @return boolean hook value; true means continue processing, false means stop. + */ + + function onAutoload($cls) + { + $dir = dirname(__FILE__); + + switch ($cls) + { + case 'ShowpollAction': + case 'NewpollAction': + case 'RespondpollAction': + include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; + return false; + case 'Poll': + case 'Poll_response': + include_once $dir.'/'.$cls.'.php'; + return false; + case 'NewPollForm': + case 'PollResponseForm': + case 'PollResultForm': + include_once $dir.'/'.strtolower($cls).'.php'; + return false; + default: + return true; + } + } + + /** + * Map URLs to actions + * + * @param Net_URL_Mapper $m path-to-action mapper + * + * @return boolean hook value; true means continue processing, false means stop. + */ + + function onRouterInitialized($m) + { + $m->connect('main/poll/new', + array('action' => 'newpoll'), + array('id' => '[0-9]+')); + + $m->connect('main/poll/:id/respond', + array('action' => 'respondpoll'), + array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')); + + return true; + } + + /** + * Plugin version data + * + * @param array &$versions array of version data + * + * @return value + */ + + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'Poll', + 'version' => self::VERSION, + 'author' => 'Brion Vibber', + 'homepage' => 'http://status.net/wiki/Plugin:Poll', + 'rawdescription' => + _m('Simple extension for supporting basic polls.')); + return true; + } + + function types() + { + return array(self::POLL_OBJECT); + } + + /** + * When a notice is deleted, delete the related Poll + * + * @param Notice $notice Notice being deleted + * + * @return boolean hook value + */ + + function deleteRelated($notice) + { + $p = Poll::getByNotice($notice); + + if (!empty($p)) { + $p->delete(); + } + + return true; + } + + /** + * Save a poll from an activity + * + * @param Profile $profile Profile to use as author + * @param Activity $activity Activity to save + * @param array $options Options to pass to bookmark-saving code + * + * @return Notice resulting notice + */ + + function saveNoticeFromActivity($activity, $profile, $options=array()) + { + // @fixme + } + + function activityObjectFromNotice($notice) + { + assert($this->isMyNotice($notice)); + + $object = new ActivityObject(); + $object->id = $notice->uri; + $object->type = self::POLL_OBJECT; + $object->title = 'Poll title'; + $object->summary = 'Poll summary'; + $object->link = $notice->bestUrl(); + + $poll = Poll::getByNotice($notice); + /** + * Adding the poll-specific data. There's no standard in AS for polls, + * so we're making stuff up. + * + * For the moment, using a kind of icky-looking schema that happens to + * work with out code for generating both Atom and JSON forms, though + * I don't like it: + * + * + * + * "poll:data": { + * "xmlns:poll": http://apinamespace.org/activitystreams/object/poll + * "question": "Who wants a poll question?" + * "option1": "Option one" + * "option2": "Option two" + * "option3": "Option three" + * } + * + */ + // @fixme there's no way to specify an XML node tree here, like + // @fixme there's no way to specify a JSON array or multi-level tree unless you break the XML attribs + // @fixme XML node contents don't get shown in JSON + $data = array('xmlns:poll' => self::POLL_OBJECT, + 'question' => $poll->question); + foreach ($poll->getOptions() as $i => $opt) { + $data['option' . ($i + 1)] = $opt; + } + $object->extra[] = array('poll:data', $data, ''); + return $object; + } + + /** + * @fixme WARNING WARNING WARNING parent class closes the final div that we + * open here, but we probably shouldn't open it here. Check parent class + * and Bookmark plugin for if that's right. + */ + function showNotice($notice, $out) + { + $user = common_current_user(); + + // @hack we want regular rendering, then just add stuff after that + $nli = new NoticeListItem($notice, $out); + $nli->showNotice(); + + $out->elementStart('div', array('class' => 'entry-content poll-content')); + $poll = Poll::getByNotice($notice); + if ($poll) { + if ($user) { + $profile = $user->getProfile(); + $response = $poll->getResponse($profile); + if ($response) { + // User has already responded; show the results. + $form = new PollResultForm($poll, $out); + } else { + $form = new PollResponseForm($poll, $out); + } + $form->show(); + } + } else { + $out->text('Poll data is missing'); + } + $out->elementEnd('div'); + + // @fixme + $out->elementStart('div', array('class' => 'entry-content')); + } + + function entryForm($out) + { + return new NewPollForm($out); + } + + // @fixme is this from parent? + function tag() + { + return 'poll'; + } + + function appTitle() + { + return _m('Poll'); + } +} diff --git a/plugins/Poll/Poll_response.php b/plugins/Poll/Poll_response.php new file mode 100644 index 0000000000..44bc421c22 --- /dev/null +++ b/plugins/Poll/Poll_response.php @@ -0,0 +1,110 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2011, 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); +} + +/** + * For storing the poll options and such + * + * @category PollPlugin + * @package StatusNet + * @author Brion Vibber + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * @see DB_DataObject + */ + +class Poll_response extends Managed_DataObject +{ + public $__table = 'poll_response'; // table name + public $poll_id; // char(36) primary key not null -> UUID + public $profile_id; // int -> profile.id + public $selection; // int -> choice # + public $created; // datetime + + /** + * Get an instance by key + * + * This is a utility method to get a single instance with a given key value. + * + * @param string $k Key to use to lookup (usually 'user_id' for this class) + * @param mixed $v Value to lookup + * + * @return User_greeting_count object found, or null for no hits + * + */ + + function staticGet($k, $v=null) + { + return Memcached_DataObject::staticGet('Poll_response', $k, $v); + } + + /** + * Get an instance by compound key + * + * This is a utility method to get a single instance with a given set of + * key-value pairs. Usually used for the primary key for a compound key; thus + * the name. + * + * @param array $kv array of key-value mappings + * + * @return Bookmark object found, or null for no hits + * + */ + + function pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('Poll_response', $kv); + } + + /** + * The One True Thingy that must be defined and declared. + */ + public static function schemaDef() + { + return array( + 'description' => 'Record of responses to polls', + 'fields' => array( + 'poll_id' => array('type' => 'char', 'length' => 36, 'not null' => true, 'description' => 'UUID'), + 'profile_id' => array('type' => 'int'), + 'selection' => array('type' => 'int'), + 'created' => array('type' => 'datetime', 'not null' => true), + ), + 'unique keys' => array( + 'poll_response_poll_id_profile_id_key' => array('poll_id', 'profile_id'), + ), + 'indexes' => array( + 'poll_response_profile_id_poll_id_index' => array('profile_id', 'poll_id'), + ) + ); + } +} diff --git a/plugins/Poll/README b/plugins/Poll/README new file mode 100644 index 0000000000..cd91a03945 --- /dev/null +++ b/plugins/Poll/README @@ -0,0 +1,21 @@ +Unfinished basic stuff: +* make pretty graphs for response counts +* ActivityStreams output of poll data is temporary; the interfaces need more flexibility +* ActivityStreams input not done yet +* need link -> show results in addition to showing results if you already voted +* way to change/cancel your vote + +Known issues: +* HTTP caching needs fixing on show-poll; may show you old data if you voted after + +Things todo: +* should we allow anonymous responses? or ways for remote profiles to respond locally? + +Fancier things todo: +* make sure backup/restore work +* make sure ostatus transfer works +* a way to do poll responses over ostatus directly? +* allow links, tags, @-references in poll question & answers? or not? + +Storage todo: +* probably separate the options into a table instead of squishing them in a text blob diff --git a/plugins/Poll/newpoll.php b/plugins/Poll/newpoll.php new file mode 100644 index 0000000000..66386affa9 --- /dev/null +++ b/plugins/Poll/newpoll.php @@ -0,0 +1,193 @@ +. + * + * @category Poll + * @package StatusNet + * @author Brion Vibber + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Add a new Poll + * + * @category Poll + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class NewPollAction extends Action +{ + protected $user = null; + protected $error = null; + protected $complete = null; + + protected $question = null; + protected $options = array(); + + /** + * Returns the title of the action + * + * @return string Action title + */ + + function title() + { + return _('New poll'); + } + + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + + function prepare($argarray) + { + parent::prepare($argarray); + + $this->user = common_current_user(); + + if (empty($this->user)) { + throw new ClientException(_("Must be logged in to post a poll."), + 403); + } + + if ($this->isPost()) { + $this->checkSessionToken(); + } + + $this->question = $this->trimmed('question'); + for ($i = 1; $i < 20; $i++) { + $opt = $this->trimmed('option' . $i); + if ($opt != '') { + $this->options[] = $opt; + } + } + + return true; + } + + /** + * Handler method + * + * @param array $argarray is ignored since it's now passed in in prepare() + * + * @return void + */ + + function handle($argarray=null) + { + parent::handle($argarray); + + if ($this->isPost()) { + $this->newPoll(); + } else { + $this->showPage(); + } + + return; + } + + /** + * Add a new Poll + * + * @return void + */ + + function newPoll() + { + try { + if (empty($this->question)) { + throw new ClientException(_('Poll must have a question.')); + } + + if (count($this->options) < 2) { + throw new ClientException(_('Poll must have at least two options.')); + } + + + $saved = Poll::saveNew($this->user->getProfile(), + $this->question, + $this->options); + + } catch (ClientException $ce) { + $this->error = $ce->getMessage(); + $this->showPage(); + return; + } + + common_redirect($saved->bestUrl(), 303); + } + + /** + * Show the Poll form + * + * @return void + */ + + function showContent() + { + if (!empty($this->error)) { + $this->element('p', 'error', $this->error); + } + + $form = new NewPollForm($this, + $this->questions, + $this->options); + + $form->show(); + + return; + } + + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + + function isReadOnly($args) + { + if ($_SERVER['REQUEST_METHOD'] == 'GET' || + $_SERVER['REQUEST_METHOD'] == 'HEAD') { + return true; + } else { + return false; + } + } +} diff --git a/plugins/Poll/newpollform.php b/plugins/Poll/newpollform.php new file mode 100644 index 0000000000..fd5f28748b --- /dev/null +++ b/plugins/Poll/newpollform.php @@ -0,0 +1,150 @@ +. + * + * @category PollPlugin + * @package StatusNet + * @author Brion Vibber + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Form to add a new poll thingy + * + * @category PollPlugin + * @package StatusNet + * @author Brion Vibber + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class NewpollForm extends Form +{ + + protected $question = null; + protected $options = array(); + + /** + * Construct a new poll form + * + * @param HTMLOutputter $out output channel + * + * @return void + */ + + function __construct($out=null, $question=null, $options=null) + { + parent::__construct($out); + } + + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'newpoll-form'; + } + + /** + * class of the form + * + * @return string class of the form + */ + + function formClass() + { + return 'form_settings'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('newpoll'); + } + + /** + * Data elements of the form + * + * @return void + */ + + function formData() + { + $this->out->elementStart('fieldset', array('id' => 'newpoll-data')); + $this->out->elementStart('ul', 'form_data'); + + $this->li(); + $this->out->input('question', + _m('Question'), + $this->question, + _m('What question are people answering?')); + $this->unli(); + + $max = 5; + if (count($this->options) + 1 > $max) { + $max = count($this->options) + 2; + } + for ($i = 0; $i < $max; $i++) { + // @fixme make extensible + if (isset($this->options[$i])) { + $default = $this->options[$i]; + } else { + $default = ''; + } + $this->li(); + $this->out->input('option' . ($i + 1), + sprintf(_m('Option %d'), $i + 1), + $default); + $this->unli(); + } + + $this->out->elementEnd('ul'); + $this->out->elementEnd('fieldset'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _m('BUTTON', 'Save')); + } +} diff --git a/plugins/Poll/pollresponseform.php b/plugins/Poll/pollresponseform.php new file mode 100644 index 0000000000..87340f926f --- /dev/null +++ b/plugins/Poll/pollresponseform.php @@ -0,0 +1,135 @@ +. + * + * @category PollPlugin + * @package StatusNet + * @author Brion Vibber + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Form to add a new poll thingy + * + * @category PollPlugin + * @package StatusNet + * @author Brion Vibber + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class PollResponseForm extends Form +{ + protected $poll; + + /** + * Construct a new poll form + * + * @param Poll $poll + * @param HTMLOutputter $out output channel + * + * @return void + */ + + function __construct(Poll $poll, HTMLOutputter $out) + { + parent::__construct($out); + $this->poll = $poll; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'pollresponse-form'; + } + + /** + * class of the form + * + * @return string class of the form + */ + + function formClass() + { + return 'form_settings'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('respondpoll', array('id' => $this->poll->id)); + } + + /** + * Data elements of the form + * + * @return void + */ + + function formData() + { + $poll = $this->poll; + $out = $this->out; + $id = "poll-" . $poll->id; + + $out->element('p', 'poll-question', $poll->question); + $out->elementStart('ul', 'poll-options'); + foreach ($poll->getOptions() as $i => $opt) { + $out->elementStart('li'); + $out->elementStart('label'); + $out->element('input', array('type' => 'radio', 'name' => 'pollselection', 'value' => $i + 1), ''); + $out->text(' ' . $opt); + $out->elementEnd('label'); + $out->elementEnd('li'); + } + $out->elementEnd('ul'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _m('BUTTON', 'Submit')); + } +} diff --git a/plugins/Poll/pollresultform.php b/plugins/Poll/pollresultform.php new file mode 100644 index 0000000000..eace105e0d --- /dev/null +++ b/plugins/Poll/pollresultform.php @@ -0,0 +1,131 @@ +. + * + * @category PollPlugin + * @package StatusNet + * @author Brion Vibber + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Form to add a new poll thingy + * + * @category PollPlugin + * @package StatusNet + * @author Brion Vibber + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class PollResultForm extends Form +{ + protected $poll; + + /** + * Construct a new poll form + * + * @param Poll $poll + * @param HTMLOutputter $out output channel + * + * @return void + */ + + function __construct(Poll $poll, HTMLOutputter $out) + { + parent::__construct($out); + $this->poll = $poll; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'pollresult-form'; + } + + /** + * class of the form + * + * @return string class of the form + */ + + function formClass() + { + return 'form_settings'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('respondpoll', array('id' => $this->poll->id)); + } + + /** + * Data elements of the form + * + * @return void + */ + + function formData() + { + $poll = $this->poll; + $out = $this->out; + $counts = $poll->countResponses(); + + $out->element('p', 'poll-question', $poll->question); + $out->elementStart('ul', 'poll-options'); + foreach ($poll->getOptions() as $i => $opt) { + $out->elementStart('li'); + $out->text($counts[$i] . ' ' . $opt); + $out->elementEnd('li'); + } + $out->elementEnd('ul'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + } +} diff --git a/plugins/Poll/respondpoll.php b/plugins/Poll/respondpoll.php new file mode 100644 index 0000000000..8ae31443fc --- /dev/null +++ b/plugins/Poll/respondpoll.php @@ -0,0 +1,189 @@ +. + * + * @category Poll + * @package StatusNet + * @author Brion Vibber + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Add a new Poll + * + * @category Poll + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class RespondPollAction extends Action +{ + protected $user = null; + protected $error = null; + protected $complete = null; + + protected $poll = null; + protected $selection = null; + + /** + * Returns the title of the action + * + * @return string Action title + */ + + function title() + { + return _m('Poll response'); + } + + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + + function prepare($argarray) + { + parent::prepare($argarray); + + $this->user = common_current_user(); + + if (empty($this->user)) { + throw new ClientException(_m("Must be logged in to respond to a poll."), + 403); + } + + if ($this->isPost()) { + $this->checkSessionToken(); + } + + $id = $this->trimmed('id'); + $this->poll = Poll::staticGet('id', $id); + if (empty($this->poll)) { + throw new ClientException(_m("Invalid or missing poll."), 404); + } + + $selection = intval($this->trimmed('pollselection')); + if ($selection < 1 || $selection > count($this->poll->getOptions())) { + throw new ClientException(_m('Invalid poll selection.')); + } + $this->selection = $selection; + + return true; + } + + /** + * Handler method + * + * @param array $argarray is ignored since it's now passed in in prepare() + * + * @return void + */ + + function handle($argarray=null) + { + parent::handle($argarray); + + if ($this->isPost()) { + $this->respondPoll(); + } else { + $this->showPage(); + } + + return; + } + + /** + * Add a new Poll + * + * @return void + */ + + function respondPoll() + { + try { + $response = new Poll_response(); + $response->poll_id = $this->poll->id; + $response->profile_id = $this->user->id; + $response->selection = $this->selection; + $response->created = common_sql_now(); + $response->insert(); + + } catch (ClientException $ce) { + $this->error = $ce->getMessage(); + $this->showPage(); + return; + } + + common_redirect($this->poll->bestUrl(), 303); + } + + /** + * Show the Poll form + * + * @return void + */ + + function showContent() + { + if (!empty($this->error)) { + $this->element('p', 'error', $this->error); + } + + $form = new PollResponseForm($this->poll, $this); + + $form->show(); + + return; + } + + /** + * Return true if read only. + * + * MAY override + * + * @param array $args other arguments + * + * @return boolean is read only action? + */ + + function isReadOnly($args) + { + if ($_SERVER['REQUEST_METHOD'] == 'GET' || + $_SERVER['REQUEST_METHOD'] == 'HEAD') { + return true; + } else { + return false; + } + } +} diff --git a/plugins/Poll/showpoll.php b/plugins/Poll/showpoll.php new file mode 100644 index 0000000000..f5002701a2 --- /dev/null +++ b/plugins/Poll/showpoll.php @@ -0,0 +1,111 @@ +. + * + * @category PollPlugin + * @package StatusNet + * @author Brion Vibber + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Show a single Poll, with associated information + * + * @category PollPlugin + * @package StatusNet + * @author Brion Vibber + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class ShowPollAction extends ShownoticeAction +{ + protected $poll = null; + + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + + function prepare($argarray) + { + OwnerDesignAction::prepare($argarray); + + $this->id = $this->trimmed('id'); + + $this->poll = Poll::staticGet('id', $this->id); + + if (empty($this->poll)) { + throw new ClientException(_m('No such poll.'), 404); + } + + $this->notice = $this->poll->getNotice(); + + if (empty($this->notice)) { + // Did we used to have it, and it got deleted? + throw new ClientException(_m('No such poll notice.'), 404); + } + + $this->user = User::staticGet('id', $this->poll->profile_id); + + if (empty($this->user)) { + throw new ClientException(_m('No such user.'), 404); + } + + $this->profile = $this->user->getProfile(); + + if (empty($this->profile)) { + throw new ServerException(_m('User without a profile.')); + } + + $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE); + + return true; + } + + /** + * Title of the page + * + * Used by Action class for layout. + * + * @return string page tile + */ + + function title() + { + return sprintf(_('%s\'s poll: %s'), + $this->user->nickname, + $this->poll->question); + } + +}