diff options
162 files changed, 10501 insertions, 5581 deletions
diff --git a/EVENTS.txt b/EVENTS.txt index 121ae175d..02b11a8a6 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -87,6 +87,18 @@ StartShowContentBlock: Showing before the content container EndShowContentBlock: Showing after the content container - $action: the current action +StartShowAside: Showing before the Aside container +- $action: the current action + +EndShowAside: Showing after the Aside container +- $action: the current action + +StartShowNoticeFormData: Showing before the notice form data +- $action: the current action + +EndShowNoticeFormData: Showing after the notice form data +- $action: the current action + StartNoticeSave: before inserting a notice (good place for content filters) - $notice: notice being saved (no ID or URI) @@ -170,12 +182,6 @@ StartShowBody: called before showing the <body> element and children EndShowBody: called after showing the <body> element (and </body>) - $action: action object being shown -StartHeadChildren: called before showing the children of <head> element (after <head> tag) -- $action: action object being shown - -EndHeadChildren: called after showing the children of <head> element (before </head>) -- $action: action object being shown - StartPersonalGroupNav: beginning of personal group nav menu - $action: action object being shown @@ -200,6 +206,12 @@ StartShowExportData: just before showing the <div> with export data (feeds) EndShowExportData: just after showing the <div> with export data (feeds) - $action: action object being shown +StartShowNoticeItem: just before showing the notice item +- $action: action object being shown + +EndShowNoticeItem: just after showing the notice item +- $action: action object being shown + StartShowPageNotice: just before showing the page notice (instructions or error) - $action: action object being shown @@ -254,3 +266,28 @@ StartApiRss: after the rss <channel> element is started StartApiAtom: after the <feed> element is started - $action: action object being shown +StartEnqueueNotice: about to add a notice to the queues (good place to add a new transport) +- $notice: the notice being added +- &$transports: modifiable list of transports (as strings) to queue for + +EndEnqueueNotice: after adding a notice to the queues +- $notice: the notice being added +- $transports: modifiable list of transports to use + +UnqueueHandleNotice: Handle a notice when no queue manager is available +- $notice: the notice to handle +- $queue: the "queue" that is being executed + +GetValidDaemons: Just before determining which daemons to run +- &$daemons: modifiable list of daemon scripts to run, filenames relative to scripts/ + +HandleQueuedNotice: Handle a queued notice at queue time (or immediately if no queue) +- &$notice: notice to handle + +StartShowHeadElements: Right after the <head> tag +- $action: the current action + +EndShowHeadElements: Right before the </head> tag; put <script>s here if you need them in <head> +- $action: the current action + +CheckSchema: chance to check the schema @@ -1037,6 +1037,14 @@ utf8: whether to talk to the database in UTF-8 mode. This is the default with new installations, but older sites may want to turn it off until they get their databases fixed up. See "UTF-8 database" above for details. +schemacheck: when to let plugins check the database schema to add + tables or update them. Values can be 'runtime' (default) + or 'script'. 'runtime' can be costly (plugins check the + schema on every hit, adding potentially several db + queries, some quite long), but not everyone knows how to + run a script. If you can, set this to 'script' and run + scripts/checkschema.php whenever you install or upgrade a + plugin. syslog ------ diff --git a/actions/allrss.php b/actions/allrss.php index 57efb73f0..28b1be27d 100644 --- a/actions/allrss.php +++ b/actions/allrss.php @@ -68,6 +68,7 @@ class AllrssAction extends Rss10Action $this->clientError(_('No such user.')); return false; } else { + $this->notices = $this->getNotices($this->limit); return true; } } diff --git a/actions/api.php b/actions/api.php deleted file mode 100644 index d570bb017..000000000 --- a/actions/api.php +++ /dev/null @@ -1,305 +0,0 @@ -<?php -/** - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * @category Actions - * @package Actions - * @author Evan Prodromou <evan@status.net> - * @author Brenda Wallace <shiny@cpan.org> - * @author Jeffery To <jeffery.to@gmail.com> - * @author Robin Millette <millette@controlyourself.ca> - * @author Tom Adams <tom@holizz.com> - * @author Christopher Vollick <psycotica0@gmail.com> - * @author CiaranG <ciaran@ciarang.com> - * @author Craig Andrews <candrews@integralblue.com> - * @author Gina Haeussge <osd@foosel.net> - * @author Mike Cochrane <mikec@mikenz.geek.nz> - * @author Sarven Capadisli <csarven@status.net> - * @license GNU Affero General Public License http://www.gnu.org/licenses/ - * @link http://status.net - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -class ApiAction extends Action -{ - - var $user; - var $content_type; - var $api_arg; - var $api_method; - var $api_action; - var $auth_user; - var $auth_pw; - - function handle($args) - { - parent::handle($args); - - $this->api_action = $this->arg('apiaction'); - $method = $this->arg('method'); - $argument = $this->arg('argument'); - $this->basic_auth_process_header(); - - if (isset($argument)) { - $cmdext = explode('.', $argument); - $this->api_arg = $cmdext[0]; - $this->api_method = $method; - $this->content_type = strtolower($cmdext[1]); - } else { - - //Requested format / content-type will be an extension on the method - $cmdext = explode('.', $method); - $this->api_method = $cmdext[0]; - $this->content_type = strtolower($cmdext[1]); - } - - if ($this->requires_auth()) { - if (!isset($this->auth_user)) { - - //This header makes basic auth go - header('WWW-Authenticate: Basic realm="StatusNet API"'); - - //If the user hits cancel -- bam! - $this->show_basic_auth_error(); - } else { - $nickname = $this->auth_user; - $password = $this->auth_pw; - $user = common_check_user($nickname, $password); - - if ($user) { - $this->user = $user; - $this->process_command(); - } else { - //basic authentication failed - list($proxy, $ip) = common_client_ip(); - - common_log(LOG_WARNING, "Failed API auth attempt, nickname = $nickname, proxy = $proxy, ip = $ip."); - $this->show_basic_auth_error(); - } - } - } else { - - // Caller might give us a username even if not required - if (isset($this->auth_user)) { - $user = User::staticGet('nickname', $this->auth_user); - if ($user) { - $this->user = $user; - } - //Twitter doesn't throw an error if the user isn't found - } - - $this->process_command(); - } - } - - function process_command() - { - $action = "twitapi$this->api_action"; - $actionfile = INSTALLDIR."/actions/$action.php"; - - if (file_exists($actionfile)) { - include_once $actionfile; - $action_class = ucfirst($action)."Action"; - $action_obj = new $action_class(); - - if (!$action_obj->prepare($this->args)) { - return; - } - - if (method_exists($action_obj, $this->api_method)) { - $apidata = array( 'content-type' => $this->content_type, - 'api_method' => $this->api_method, - 'api_arg' => $this->api_arg, - 'user' => $this->user); - - call_user_func(array($action_obj, $this->api_method), $_REQUEST, $apidata); - } else { - $this->clientError("API method not found!", $code = 404); - } - } else { - $this->clientError("API method not found!", $code = 404); - } - } - - // Whitelist of API methods that don't need authentication - function requires_auth() - { - static $noauth = array( 'statuses/public_timeline', - 'statuses/show', - 'users/show', - 'help/test', - 'help/downtime_schedule', - 'statusnet/version', - 'statusnet/config', - 'statusnet/wadl', - 'tags/timeline', - 'oembed/oembed', - 'groups/show', - 'groups/timeline', - 'groups/list_all', - 'groups/membership', - 'groups/is_member', - 'groups/timeline'); - - static $bareauth = array('statuses/user_timeline', - 'statuses/friends_timeline', - 'statuses/friends', - 'statuses/replies', - 'statuses/mentions', - 'statuses/followers', - 'favorites/favorites', - 'friendships/show', - 'groups/list_groups'); - - $fullname = "$this->api_action/$this->api_method"; - - // If the site is "private", all API methods except statusnet/config - // need authentication - - if (common_config('site', 'private')) { - return $fullname != 'statusnet/config' || false; - } - - // bareauth: only needs auth if without an argument or query param specifying user - - if (in_array($fullname, $bareauth)) { - - // Special case: friendships/show only needs auth if source_id or - // source_screen_name is not specified as a param - - if ($fullname == 'friendships/show') { - - $source_id = $this->arg('source_id'); - $source_screen_name = $this->arg('source_screen_name'); - - if (empty($source_id) && empty($source_screen_name)) { - return true; - } - - return false; - } - - // if all of these are empty, auth is required - - $id = $this->arg('id'); - $user_id = $this->arg('user_id'); - $screen_name = $this->arg('screen_name'); - - if (empty($this->api_arg) - && empty($id) - && empty($user_id) - && empty($screen_name) - ) { - return true; - } else { - return false; - } - - } else if (in_array($fullname, $noauth)) { - - // noauth: never needs auth - - return false; - } else { - - // everybody else needs auth - - return true; - } - } - - function basic_auth_process_header() - { - if (isset($_SERVER['AUTHORIZATION']) || isset($_SERVER['HTTP_AUTHORIZATION'])) { - $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION'])? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION']; - } - - if (isset($_SERVER['PHP_AUTH_USER'])) { - $this->auth_user = $_SERVER['PHP_AUTH_USER']; - $this->auth_pw = $_SERVER['PHP_AUTH_PW']; - } elseif (isset($authorization_header) && strstr(substr($authorization_header, 0, 5), 'Basic')) { - // decode the HTTP_AUTHORIZATION header on php-cgi server self - // on fcgid server the header name is AUTHORIZATION - - $auth_hash = base64_decode(substr($authorization_header, 6)); - list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash); - - // set all to null on a empty basic auth request - if ($this->auth_user == "") { - $this->auth_user = null; - $this->auth_pw = null; - } - } else { - $this->auth_user = null; - $this->auth_pw = null; - } - } - - function show_basic_auth_error() - { - header('HTTP/1.1 401 Unauthorized'); - $msg = 'Could not authenticate you.'; - - if ($this->content_type == 'xml') { - header('Content-Type: application/xml; charset=utf-8'); - $this->startXML(); - $this->elementStart('hash'); - $this->element('error', null, $msg); - $this->element('request', null, $_SERVER['REQUEST_URI']); - $this->elementEnd('hash'); - $this->endXML(); - } else if ($this->content_type == 'json') { - header('Content-Type: application/json; charset=utf-8'); - $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); - print(json_encode($error_array)); - } else { - header('Content-type: text/plain'); - print "$msg\n"; - } - } - - function isReadOnly($args) - { - $apiaction = $args['apiaction']; - $method = $args['method']; - - list($cmdtext, $fmt) = explode('.', $method); - - static $write_methods = array( - 'account' => array('update_location', 'update_delivery_device', 'end_session'), - 'blocks' => array('create', 'destroy'), - 'direct_messages' => array('create', 'destroy'), - 'favorites' => array('create', 'destroy'), - 'friendships' => array('create', 'destroy'), - 'help' => array(), - 'notifications' => array('follow', 'leave'), - 'statuses' => array('update', 'destroy'), - 'users' => array() - ); - - if (array_key_exists($apiaction, $write_methods)) { - if (!in_array($cmdtext, $write_methods[$apiaction])) { - return true; - } - } - - return false; - } -} diff --git a/actions/apiaccountratelimitstatus.php b/actions/apiaccountratelimitstatus.php new file mode 100644 index 000000000..96179f175 --- /dev/null +++ b/actions/apiaccountratelimitstatus.php @@ -0,0 +1,112 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Dummy action that emulates Twitter's rate limit status API resource + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apibareauth.php'; + +/** + * We don't have a rate limit, but some clients check this method. + * It always returns the same thing: 150 hits left. + * + * @category API + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiAccountRateLimitStatusAction extends ApiBareAuthAction +{ + + /** + * Handle the request + * + * Return some Twitter-ish data about API limits + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError( + _('API method not found!'), + 404, + $this->format + ); + return; + } + + $reset = new DateTime(); + $reset->modify('+1 hour'); + + $this->initDocument($this->format); + + if ($this->format == 'xml') { + $this->elementStart('hash'); + $this->element('remaining-hits', array('type' => 'integer'), 150); + $this->element('hourly-limit', array('type' => 'integer'), 150); + $this->element( + 'reset-time', array('type' => 'datetime'), + common_date_iso8601($reset->format('r')) + ); + $this->element( + 'reset_time_in_seconds', + array('type' => 'integer'), + strtotime('+1 hour') + ); + $this->elementEnd('hash'); + } elseif ($this->format == 'json') { + $out = array( + 'reset_time_in_seconds' => strtotime('+1 hour'), + 'remaining_hits' => 150, + 'hourly_limit' => 150, + 'reset_time' => common_date_rfc2822( + $reset->format('r') + ) + ); + print json_encode($out); + } + + $this->endDocument($this->format); + } + +} + diff --git a/actions/apiaccountverifycredentials.php b/actions/apiaccountverifycredentials.php new file mode 100644 index 000000000..08b201dbf --- /dev/null +++ b/actions/apiaccountverifycredentials.php @@ -0,0 +1,85 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Test if supplied user credentials are valid. + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * Check a user's credentials. Returns an HTTP 200 OK response code and a + * representation of the requesting user if authentication was successful; + * returns a 401 status code and an error message if not. + * + * @category API + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiAccountVerifyCredentialsAction extends ApiAuthAction +{ + + /** + * Handle the request + * + * Check whether the credentials are valid and output the result + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + switch ($this->format) { + case 'xml': + case 'json': + $args['id'] = $this->auth_user->id; + $action_obj = new ApiUserShowAction(); + if ($action_obj->prepare($args)) { + $action_obj->handle($args); + } + break; + default: + header('Content-Type: text/html; charset=utf-8'); + print 'Authorized'; + } + + } + +} diff --git a/actions/apiblockcreate.php b/actions/apiblockcreate.php new file mode 100644 index 000000000..1cab2df5d --- /dev/null +++ b/actions/apiblockcreate.php @@ -0,0 +1,114 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Block a user via the API + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * Blocks the user specified in the ID parameter as the authenticating user. + * Destroys a friendship to the blocked user if it exists. Returns the + * blocked user in the requested format when successful. + * + * @category API + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiBlockCreateAction extends ApiAuthAction +{ + var $other = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + $this->other = $this->getTargetUser($this->arg('id')); + + return true; + } + + /** + * Handle the request + * + * Save the new message + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, + $this->format + ); + return; + } + + if (empty($this->user) || empty($this->other)) { + $this->clientError(_('No such user!'), 404, $this->format); + return; + } + + if ($this->user->hasBlocked($this->other) + || $this->user->block($this->other) + ) { + $this->initDocument($this->format); + $this->showProfile($this->other, $this->format); + $this->endDocument($this->format); + } else { + $this->serverError(_('Block user failed.'), 500, $this->format); + } + + } + +} + diff --git a/actions/apiblockdestroy.php b/actions/apiblockdestroy.php new file mode 100644 index 000000000..16dbf94ca --- /dev/null +++ b/actions/apiblockdestroy.php @@ -0,0 +1,113 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Un-block a user via the API + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * Un-blocks the user specified in the ID parameter for the authenticating user. + * Returns the un-blocked user in the requested format when successful. + * + * @category API + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiBlockDestroyAction extends ApiAuthAction +{ + var $other = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + $this->other = $this->getTargetUser($this->arg('id')); + + return true; + } + + /** + * Handle the request + * + * Save the new message + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, + $this->format + ); + return; + } + + if (empty($this->user) || empty($this->other)) { + $this->clientError(_('No such user!'), 404, $this->format); + return; + } + + if (!$this->user->hasBlocked($this->other) + || $this->user->unblock($this->other) + ) { + $this->initDocument($this->format); + $this->showProfile($this->other, $this->format); + $this->endDocument($this->format); + } else { + $this->serverError(_('Unblock user failed.')); + } + + } + +} + diff --git a/actions/apidirectmessage.php b/actions/apidirectmessage.php new file mode 100644 index 000000000..a21fe86d2 --- /dev/null +++ b/actions/apidirectmessage.php @@ -0,0 +1,375 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show a the direct messages from or to a user + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Adrian Lang <mail@adrianlang.de> + * @author Evan Prodromou <evan@status.net> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * Show a list of direct messages from or to the authenticating user + * + * @category API + * @package StatusNet + * @author Adrian Lang <mail@adrianlang.de> + * @author Evan Prodromou <evan@status.net> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiDirectMessageAction extends ApiAuthAction +{ + var $messages = null; + var $title = null; + var $subtitle = null; + var $link = null; + var $selfuri_base = null; + var $id = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return; + } + + $server = common_root_url(); + $taguribase = common_config('integration', 'taguri'); + + if ($this->arg('sent')) { + + // Action was called by /api/direct_messages/sent.format + + $this->title = sprintf( + _("Direct messages from %s"), + $this->user->nickname + ); + $this->subtitle = sprintf( + _("All the direct messages sent from %s"), + $this->user->nickname + ); + $this->link = $server . $this->user->nickname . '/outbox'; + $this->selfuri_base = common_root_url() . 'api/direct_messages/sent'; + $this->id = "tag:$taguribase:SentDirectMessages:" . $this->user->id; + } else { + $this->title = sprintf( + _("Direct messages to %s"), + $this->user->nickname + ); + $this->subtitle = sprintf( + _("All the direct messages sent to %s"), + $this->user->nickname + ); + $this->link = $server . $this->user->nickname . '/inbox'; + $this->selfuri_base = common_root_url() . 'api/direct_messages'; + $this->id = "tag:$taguribase:DirectMessages:" . $this->user->id; + } + + $this->messages = $this->getMessages(); + + return true; + } + + /** + * Handle the request + * + * Show the messages + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showMessages(); + } + + /** + * Show the messages + * + * @return void + */ + + function showMessages() + { + switch($this->format) { + case 'xml': + $this->showXmlDirectMessages(); + break; + case 'rss': + $this->showRssDirectMessages(); + break; + case 'atom': + $this->showAtomDirectMessages(); + break; + case 'json': + $this->showJsonDirectMessages(); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } + + /** + * Get notices + * + * @return array notices + */ + + function getMessages() + { + $message = new Message(); + + if ($this->arg('sent')) { + $message->from_profile = $this->user->id; + } else { + $message->to_profile = $this->user->id; + } + + if (!empty($this->max_id)) { + $message->whereAdd('id <= ' . $this->max_id); + } + + if (!empty($this->since_id)) { + $message->whereAdd('id > ' . $this->since_id); + } + + if (!empty($since)) { + $d = date('Y-m-d H:i:s', $this->since); + $message->whereAdd("created > '$d'"); + } + + $message->orderBy('created DESC, id DESC'); + $message->limit((($this->page - 1) * $this->count), $this->count); + $message->find(); + + $messages = array(); + + while ($message->fetch()) { + $messages[] = clone($message); + } + + return $messages; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this notice last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->messages)) { + return strtotime($this->messages[0]->created); + } + + return null; + } + + /** + * Shows a list of direct messages as Twitter-style XML array + * + * @return void + */ + + function showXmlDirectMessages() + { + $this->initDocument('xml'); + $this->elementStart('direct-messages', array('type' => 'array')); + + foreach ($this->messages as $m) { + $dm_array = $this->directMessageArray($m); + $this->showXmlDirectMessage($dm_array); + } + + $this->elementEnd('direct-messages'); + $this->endDocument('xml'); + } + + /** + * Shows a list of direct messages as a JSON encoded array + * + * @return void + */ + + function showJsonDirectMessages() + { + $this->initDocument('json'); + + $dmsgs = array(); + + foreach ($this->messages as $m) { + $dm_array = $this->directMessageArray($m); + array_push($dmsgs, $dm_array); + } + + $this->showJsonObjects($dmsgs); + $this->endDocument('json'); + } + + /** + * Shows a list of direct messages as RSS items + * + * @return void + */ + + function showRssDirectMessages() + { + $this->initDocument('rss'); + + $this->element('title', null, $this->title); + + $this->element('link', null, $this->link); + $this->element('description', null, $this->subtitle); + $this->element('language', null, 'en-us'); + + $this->element( + 'atom:link', + array( + 'type' => 'application/rss+xml', + 'href' => $this->selfuri_base . '.rss', + 'rel' => self + ), + null + ); + $this->element('ttl', null, '40'); + + foreach ($this->messages as $m) { + $entry = $this->rssDirectMessageArray($m); + $this->showTwitterRssItem($entry); + } + + $this->endTwitterRss(); + } + + /** + * Shows a list of direct messages as Atom entries + * + * @return void + */ + + function showAtomDirectMessages() + { + $this->initDocument('atom'); + + $this->element('title', null, $this->title); + $this->element('id', null, $this->id); + + $selfuri = common_root_url() . 'api/direct_messages.atom'; + + $this->element( + 'link', array( + 'href' => $this->link, + 'rel' => 'alternate', + 'type' => 'text/html'), + null + ); + $this->element( + 'link', array( + 'href' => $this->selfuri_base . '.atom', 'rel' => 'self', + 'type' => 'application/atom+xml'), + null + ); + $this->element('updated', null, common_date_iso8601('now')); + $this->element('subtitle', null, $this->subtitle); + + foreach ($this->messages as $m) { + $entry = $this->rssDirectMessageArray($m); + $this->showTwitterAtomEntry($entry); + } + + $this->endDocument('atom'); + } + + /** + * An entity tag for this notice + * + * Returns an Etag based on the action name, language, and + * timestamps of the notice + * + * @return string etag + */ + + function etag() + { + if (!empty($this->messages)) { + + $last = count($this->messages) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + strtotime($this->messages[0]->created), + strtotime($this->messages[$last]->created) + ) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apidirectmessagenew.php b/actions/apidirectmessagenew.php new file mode 100644 index 000000000..fa6cafbe8 --- /dev/null +++ b/actions/apidirectmessagenew.php @@ -0,0 +1,188 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Send a direct message via the API + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Adrian Lang <mail@adrianlang.de> + * @author Evan Prodromou <evan@status.net> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * Creates a new direct message from the authenticating user to + * the user specified by id. + * + * @category API + * @package StatusNet + * @author Adrian Lang <mail@adrianlang.de> + * @author Evan Prodromou <evan@status.net> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiDirectMessageNewAction extends ApiAuthAction +{ + var $source = null; + var $other = null; + var $content = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return; + } + + $this->source = $this->trimmed('source'); // Not supported by Twitter. + + $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api'); + if (empty($thtis->source) || in_array($this->source, $reserved_sources)) { + $source = 'api'; + } + + $this->content = $this->trimmed('text'); + + $this->user = $this->auth_user; + + $user_param = $this->trimmed('user'); + $user_id = $this->arg('user_id'); + $screen_name = $this->trimmed('screen_name'); + + if (isset($user_param) || isset($user_id) || isset($screen_name)) { + $this->other = $this->getTargetUser($user_param); + } + + return true; + } + + /** + * Handle the request + * + * Save the new message + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, + $this->format + ); + return; + } + + if (empty($this->content)) { + $this->clientError( + _('No message text!'), + 406, + $this->format + ); + } else { + $content_shortened = common_shorten_links($this->content); + if (Message::contentTooLong($content_shortened)) { + $this->clientError( + sprintf( + _('That\'s too long. Max message size is %d chars.'), + Message::maxContent() + ), + 406, + $this->format + ); + return; + } + } + + if (empty($this->other)) { + $this->clientError(_('Recipient user not found.'), 403, $this->format); + return; + } else if (!$this->user->mutuallySubscribed($this->other)) { + $this->clientError( + _('Can\'t send direct messages to users who aren\'t your friend.'), + 403, + $this->format + ); + return; + } else if ($this->user->id == $this->other->id) { + + // Note: sending msgs to yourself is allowed by Twitter + + $errmsg = 'Don\'t send a message to yourself; ' . + 'just say it to yourself quietly instead.' + + $this->clientError(_($errmsg), 403, $this->format); + return; + } + + $message = Message::saveNew( + $this->user->id, + $this->other->id, + html_entity_decode($this->content, ENT_NOQUOTES, 'UTF-8'), + $this->source + ); + + if (is_string($message)) { + $this->serverError($message); + return; + } + + mail_notify_message($message, $this->user, $this->other); + + if ($this->format == 'xml') { + $this->showSingleXmlDirectMessage($message); + } elseif ($this->format == 'json') { + $this->showSingleJsondirectMessage($message); + } + } + +} + diff --git a/actions/apifavoritecreate.php b/actions/apifavoritecreate.php new file mode 100644 index 000000000..a80a6492e --- /dev/null +++ b/actions/apifavoritecreate.php @@ -0,0 +1,168 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Add a notice to a user's list of favorite notices via the API + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * Favorites the status specified in the ID parameter as the authenticating user. + * Returns the favorite status when successful. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiFavoriteCreateAction extends ApiAuthAction +{ + var $notice = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + $this->notice = Notice::staticGet($this->arg('id')); + + return true; + } + + /** + * Handle the request + * + * Check the format and show the user info + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, + $this->format + ); + return; + } + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError( + _('API method not found!'), + 404, + $this->format + ); + return; + } + + if (empty($this->notice)) { + $this->clientError( + _('No status found with that ID.'), + 404, + $this->format + ); + return; + } + + // Note: Twitter lets you fave things repeatedly via API. + + if ($this->user->hasFave($this->notice)) { + $this->clientError( + _('This status is already a favorite!'), + 403, + $this->format + ); + return; + } + + $fave = Fave::addNew($this->user, $this->notice); + + if (empty($fave)) { + $this->clientError( + _('Could not create favorite.') + 403, + $this->format + ); + return; + } + + $this->notify($fave, $this->notice, $this->user); + $this->user->blowFavesCache(); + + if ($this->format == 'xml') { + $this->showSingleXmlStatus($this->notice); + } elseif ($this->format == 'json') { + $this->show_single_json_status($this->notice); + } + } + + /** + * Notify the author of the favorite that the user likes their notice + * + * @param Favorite $fave the favorite in question + * @param Notice $notice the notice that's been faved + * @param User $user the user doing the favoriting + * + * @return void + */ + function notify($fave, $notice, $user) + { + $other = User::staticGet('id', $notice->profile_id); + if ($other && $other->id != $user->id) { + if ($other->email && $other->emailnotifyfav) { + mail_notify_fave($other, $user, $notice); + } + // XXX: notify by IM + // XXX: notify by SMS + } + } + +} diff --git a/actions/apifavoritedestroy.php b/actions/apifavoritedestroy.php new file mode 100644 index 000000000..f131d1c7f --- /dev/null +++ b/actions/apifavoritedestroy.php @@ -0,0 +1,150 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Remote a notice from a user's list of favorite notices via the API + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * Un-favorites the status specified in the ID parameter as the authenticating user. + * Returns the un-favorited status in the requested format when successful. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiFavoriteDestroyAction extends ApiAuthAction +{ + + var $notice = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + $this->notice = Notice::staticGet($this->arg('id')); + + return true; + } + + /** + * Handle the request + * + * Check the format and show the user info + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, + $this->format + ); + return; + } + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError( + _('API method not found!'), + 404, + $this->format + ); + return; + } + + if (empty($this->notice)) { + $this->clientError( + _('No status found with that ID.'), + 404, + $this->format + ); + return; + } + + $fave = new Fave(); + $fave->user_id = $this->user->id; + $fave->notice_id = $this->notice->id; + + if (!$fave->find(true)) { + $this->clientError( + _('That status is not a favorite!'), + 403, + $this->favorite + ); + return; + } + + $result = $fave->delete(); + + if (!$result) { + common_log_db_error($fave, 'DELETE', __FILE__); + $this->clientError( + _('Could not delete favorite.'), + 404, + $this->format + ); + return; + } + + $this->user->blowFavesCache(); + + if ($this->format == 'xml') { + $this->showSingleXmlStatus($this->notice); + } elseif ($this->format == 'json') { + $this->show_single_json_status($this->notice); + } + } + +} diff --git a/actions/apifriendshipscreate.php b/actions/apifriendshipscreate.php new file mode 100644 index 000000000..a824e734b --- /dev/null +++ b/actions/apifriendshipscreate.php @@ -0,0 +1,137 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Subscribe to a user via the API + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * Allows the authenticating users to follow (subscribe) the user specified in + * the ID parameter. Returns the befriended user in the requested format when + * successful. Returns a string describing the failure condition when unsuccessful. + * + * @category API + * @package StatusNet + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiFriendshipsCreateAction extends ApiAuthAction +{ + var $other = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + $this->other = $this->getTargetUser($id); + + return true; + } + + /** + * Handle the request + * + * Check the format and show the user info + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, + $this->format + ); + return; + } + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError( + _('API method not found!'), + 404, + $this->format + ); + return; + } + + if (empty($this->other)) { + $this->clientError( + _('Could not follow user: User not found.'), + 403, + $this->format + ); + return; + } + + if ($this->user->isSubscribed($this->other)) { + $errmsg = sprintf( + _('Could not follow user: %s is already on your list.'), + $this->other->nickname + ); + $this->clientError($errmsg, 403, $this->format); + return; + } + + $result = subs_subscribe_to($this->user, $this->other); + + if (is_string($result)) { + $this->clientError($result, 403, $this->format); + return; + } + + $this->initDocument($this->format); + $this->showProfile($this->other, $this->format); + $this->endDocument($this->format); + } + +} diff --git a/actions/apifriendshipsdestroy.php b/actions/apifriendshipsdestroy.php new file mode 100644 index 000000000..3d9b7e001 --- /dev/null +++ b/actions/apifriendshipsdestroy.php @@ -0,0 +1,139 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Unsubscribe to a user via API + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * Allows the authenticating users to unfollow (unsubscribe) the user specified in + * the ID parameter. Returns the unfollowed user in the requested format when + * successful. Returns a string describing the failure condition when unsuccessful. + * + * @category API + * @package StatusNet + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiFriendshipsDestroyAction extends ApiAuthAction +{ + var $other = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + $this->other = $this->getTargetUser($id); + + return true; + } + + /** + * Handle the request + * + * Check the format and show the user info + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, + $this->format + ); + return; + } + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError( + _('API method not found!'), + 404, + $this->format + ); + return; + } + + if (empty($this->other)) { + $this->clientError( + _('Could not unfollow user: User not found.'), + 403, + $this->format + ); + return; + } + + // Don't allow unsubscribing from yourself! + + if ($this->user->id == $this->other->id) { + $this->clientError( + _("You cannot unfollow yourself!"), + 403, + $this->format + ); + return; + } + + $result = subs_unsubscribe_user($this->user, $this->other->nickname); + + if (is_string($result)) { + $this->clientError($result, 403, $this->format); + return; + } + + $this->initDocument($this->format); + $this->showProfile($this->other, $this->format); + $this->endDocument($this->format); + } + +} diff --git a/actions/apifriendshipsexists.php b/actions/apifriendshipsexists.php new file mode 100644 index 000000000..ae50c512c --- /dev/null +++ b/actions/apifriendshipsexists.php @@ -0,0 +1,128 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show whether there is a friendship between two users + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Tests for the existence of friendship between two users. Will return true if + * user_a follows user_b, otherwise will return false. + * + * @category API + * @package StatusNet + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiFriendshipsExistsAction extends ApiAction +{ + var $user_a = null; + var $user_b = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $user_a_id = $this->trimmed('user_a'); + $user_b_id = $this->trimmed('user_b'); + + common_debug("user_a = " . $user_a_id); + common_debug("user_b = " . $user_b_id); + + + $this->user_a = $this->getTargetUser($user_a_id); + + if (empty($this->user_a)) { + common_debug('gargargra'); + } + + $this->user_b = $this->getTargetUser($user_b_id); + + return true; + } + + /** + * Handle the request + * + * Check the format and show the user info + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (empty($this->user_a) || empty($this->user_b)) { + $this->clientError( + _('Two user ids or screen_names must be supplied.'), + 400, + $this->format + ); + return; + } + + $result = $this->user_a->isSubscribed($this->user_b); + + switch ($this->format) { + case 'xml': + $this->initDocument('xml'); + $this->element('friends', null, $result); + $this->endDocument('xml'); + break; + case 'json': + $this->initDocument('json'); + print json_encode($result); + $this->endDocument('json'); + break; + default: + break; + } + } + +} diff --git a/actions/apifriendshipsshow.php b/actions/apifriendshipsshow.php new file mode 100644 index 000000000..8fc436738 --- /dev/null +++ b/actions/apifriendshipsshow.php @@ -0,0 +1,168 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show information about the relationship between two users + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apibareauth.php'; + +/** + * Outputs detailed information about the relationship between two users + * + * @category API + * @package StatusNet + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiFriendshipsShowAction extends ApiBareAuthAction +{ + var $source = null; + var $target = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $source_id = (int)$this->trimmed('source_id'); + $source_screen_name = $this->trimmed('source_screen_name'); + $target_id = (int)$this->trimmed('target_id'); + $target_screen_name = $this->trimmed('target_screen_name'); + + if (!empty($source_id)) { + $this->source = User::staticGet($source_id); + } elseif (!empty($source_screen_name)) { + $this->source = User::staticGet('nickname', $source_screen_name); + } else { + $this->source = $this->auth_user; + } + + if (!empty($target_id)) { + $this->target = User::staticGet($target_id); + } elseif (!empty($target_screen_name)) { + $this->target = User::staticGet('nickname', $target_screen_name); + } + + return true; + } + + + /** + * Determines whether this API resource requires auth. Overloaded to look + * return true in case source_id and source_screen_name are both empty + * + * @return boolean true or false + */ + + function requiresAuth() + { + if (common_config('site', 'private')) { + return true; + } + + $source_id = $this->trimmed('source_id'); + $source_screen_name = $this->trimmed('source_screen_name'); + + if (empty($source_id) && empty($source_screen_name)) { + return true; + } + + return false; + } + + /** + * Handle the request + * + * Check the format and show the user info + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError(_('API method not found!'), 404); + return; + } + + if (empty($this->source)) { + $this->clientError( + _('Could not determine source user.'), + 404 + ); + return; + } + + if (empty($this->target)) { + $this->clientError( + _('Could not find target user.'), + 404 + ); + return; + } + + $result = $this->twitterRelationshipArray($this->source, $this->target); + + switch ($this->format) { + case 'xml': + $this->initDocument('xml'); + $this->showTwitterXmlRelationship($result[relationship]); + $this->endDocument('xml'); + break; + case 'json': + $this->initDocument('json'); + print json_encode($result); + $this->endDocument('json'); + break; + default: + break; + } + + } + +} diff --git a/actions/apigroupcreate.php b/actions/apigroupcreate.php new file mode 100644 index 000000000..cdb2afb5b --- /dev/null +++ b/actions/apigroupcreate.php @@ -0,0 +1,381 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Create a group via the API + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * Make a new group. Sets the authenticated user as the administrator of the group. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiGroupCreateAction extends ApiAuthAction +{ + var $group = null; + var $nickname = null; + var $fullname = null; + var $homepage = null; + var $description = null; + var $location = null; + var $aliasstring = null; + var $aliases = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + + $this->nickname = $this->arg('nickname'); + $this->fullname = $this->arg('full_name'); + $this->homepage = $this->arg('homepage'); + $this->description = $this->arg('description'); + $this->location = $this->arg('location'); + $this->aliasstring = $this->arg('aliases'); + + return true; + } + + /** + * Handle the request + * + * Save the new group + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (!common_config('inboxes', 'enabled')) { + $this->serverError( + _('Inboxes must be enabled for groups to work'), + 400, + $this->format + ); + return false; + } + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, + $this->format + ); + return; + } + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return; + } + + if ($this->validateParams() == false) { + return; + } + + $group = new User_group(); + + $group->query('BEGIN'); + + $group->nickname = $this->nickname; + $group->fullname = $this->fullname; + $group->homepage = $this->homepage; + $group->description = $this->description; + $group->location = $this->location; + $group->created = common_sql_now(); + + $result = $group->insert(); + + if (!$result) { + common_log_db_error($group, 'INSERT', __FILE__); + $this->serverError( + _('Could not create group.'), + 500, + $this->format + ); + return; + } + + $result = $group->setAliases($this->aliases); + + if (!$result) { + $this->serverError( + _('Could not create aliases.'), + 500, + $this->format + ); + return; + } + + $member = new Group_member(); + + $member->group_id = $group->id; + $member->profile_id = $this->user->id; + $member->is_admin = 1; + $member->created = $group->created; + + $result = $member->insert(); + + if (!$result) { + common_log_db_error($member, 'INSERT', __FILE__); + $this->serverError( + _('Could not set group membership.'), + 500, + $this->format + ); + return; + } + + $group->query('COMMIT'); + + switch($this->format) { + case 'xml': + $this->showSingleXmlGroup($group); + break; + case 'json': + $this->showSingleJsonGroup($group); + break; + default: + $this->clientError( + _('API method not found!'), + 404, + $this->format + ); + break; + } + + } + + /** + * Validate params for the new group + * + * @return void + */ + + function validateParams() + { + $valid = Validate::string( + $this->nickname, array( + 'min_length' => 1, + 'max_length' => 64, + 'format' => NICKNAME_FMT + ) + ); + + if (!$valid) { + $this->clientError( + _( + 'Nickname must have only lowercase letters ' . + 'and numbers and no spaces.' + ), + 403, + $this->format + ); + return false; + } elseif ($this->groupNicknameExists($this->nickname)) { + $this->clientError( + _('Nickname already in use. Try another one.'), + 403, + $this->format + ); + return false; + } else if (!User_group::allowedNickname($this->nickname)) { + $this->clientError( + _('Not a valid nickname.'), + 403, + $this->format + ); + return false; + + } elseif ( + !is_null($this->homepage) + && strlen($this->homepage) > 0 + && !Validate::uri( + $this->homepage, array( + 'allowed_schemes' => + array('http', 'https') + ) + )) { + $this->clientError( + _('Homepage is not a valid URL.'), + 403, + $this->format + ); + return false; + } elseif ( + !is_null($this->fullname) + && mb_strlen($this->fullname) > 255) { + $this->clientError( + _('Full name is too long (max 255 chars).'), + 403, + $this->format + ); + return false; + } elseif (User_group::descriptionTooLong($this->description)) { + $this->clientError( + sprintf( + _('Description is too long (max %d chars).'), + User_group::maxDescription() + ), + 403, + $this->format + ); + return false; + } elseif ( + !is_null($this->location) + && mb_strlen($this->location) > 255) { + $this->clientError( + _('Location is too long (max 255 chars).'), + 403, + $this->format + ); + return false; + } + + if (!empty($this->aliasstring)) { + $this->aliases = array_map( + 'common_canonical_nickname', + array_unique(preg_split('/[\s,]+/', $this->aliasstring)) + ); + } else { + $this->aliases = array(); + } + + if (count($this->aliases) > common_config('group', 'maxaliases')) { + $this->clientError( + sprintf( + _('Too many aliases! Maximum %d.'), + common_config('group', 'maxaliases') + ), + 403, + $this->format + ); + return false; + } + + foreach ($this->aliases as $alias) { + + $valid = Validate::string( + $alias, array( + 'min_length' => 1, + 'max_length' => 64, + 'format' => NICKNAME_FMT + ) + ); + + if (!$valid) { + $this->clientError( + sprintf(_('Invalid alias: "%s"'), $alias), + 403, + $this->format + ); + return false; + } + if ($this->groupNicknameExists($alias)) { + $this->clientError( + sprintf( + _('Alias "%s" already in use. Try another one.'), + $alias + ), + 403, + $this->format + ); + return false; + } + + // XXX assumes alphanum nicknames + + if (strcmp($alias, $this->nickname) == 0) { + $this->clientError( + _('Alias can\'t be the same as nickname.'), + 403, + $this->format + ); + return false; + } + } + + // Evarything looks OK + + return true; + } + + /** + * Check to see whether a nickname is already in use by a group + * + * @param String $nickname The nickname in question + * + * @return boolean true or false + */ + + function groupNicknameExists($nickname) + { + $group = User_group::staticGet('nickname', $nickname); + + if (!empty($group)) { + return true; + } + + $alias = Group_alias::staticGet('alias', $nickname); + + if (!empty($alias)) { + return true; + } + + return false; + } + +} diff --git a/actions/apigroupismember.php b/actions/apigroupismember.php new file mode 100644 index 000000000..a8a40a6b3 --- /dev/null +++ b/actions/apigroupismember.php @@ -0,0 +1,122 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Check to see whether a user a member of a group + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apibareauth.php'; + +/** + * Returns whether a user is a member of a specified group. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiGroupIsMemberAction extends ApiBareAuthAction +{ + var $group = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->getTargetUser(null); + $this->group = $this->getTargetGroup(null); + + return true; + } + + /** + * Handle the request + * + * Save the new message + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return; + } + + if (empty($this->group)) { + $this->clientError('Group not found!', 404, $this->format); + return false; + } + + $is_member = $this->user->isMember($this->group); + + switch($this->format) { + case 'xml': + $this->initDocument('xml'); + $this->element('is_member', null, $is_member); + $this->endDocument('xml'); + break; + case 'json': + $this->initDocument('json'); + $this->showJsonObjects(array('is_member' => $is_member)); + $this->endDocument('json'); + break; + default: + $this->clientError( + _('API method not found!'), + 400, + $this->format + ); + break; + } + } + +} diff --git a/actions/apigroupjoin.php b/actions/apigroupjoin.php new file mode 100644 index 000000000..071cd9290 --- /dev/null +++ b/actions/apigroupjoin.php @@ -0,0 +1,163 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Join a group via the API + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * Joins the authenticated user to the group speicified by ID + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiGroupJoinAction extends ApiAuthAction +{ + var $group = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + $this->group = $this->getTargetGroup($this->arg('id')); + + return true; + } + + /** + * Handle the request + * + * Save the new message + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, + $this->format + ); + return; + } + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return; + } + + if (empty($this->group)) { + $this->clientError('Group not found!', 404, $this->format); + return false; + } + + if ($this->user->isMember($this->group)) { + $this->clientError( + _('You are already a member of that group.'), + 403, + $this->format + ); + return; + } + + if (Group_block::isBlocked($this->group, $this->user->getProfile())) { + $this->clientError( + _('You have been blocked from that group by the admin.'), + 403, + $this->format + ); + return; + } + + $member = new Group_member(); + + $member->group_id = $this->group->id; + $member->profile_id = $this->user->id; + $member->created = common_sql_now(); + + $result = $member->insert(); + + if (!$result) { + common_log_db_error($member, 'INSERT', __FILE__); + $this->serverError( + sprintf( + _('Could not join user %s to group %s.'), + $this->user->nickname, + $this->group->nickname + ) + ); + return; + } + + switch($this->format) { + case 'xml': + $this->show_single_xml_group($this->group); + break; + case 'json': + $this->showSingleJsonGroup($this->group); + break; + default: + $this->clientError( + _('API method not found!'), + 404, + $this->format + ); + break; + } + } + +} diff --git a/actions/apigroupleave.php b/actions/apigroupleave.php new file mode 100644 index 000000000..0d4bb9e4d --- /dev/null +++ b/actions/apigroupleave.php @@ -0,0 +1,149 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Leave a group via the API + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * Removes the authenticated user from the group specified by ID + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiGroupLeaveAction extends ApiAuthAction +{ + var $group = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + $this->group = $this->getTargetGroup($this->arg('id')); + + return true; + } + + /** + * Handle the request + * + * Save the new message + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, + $this->format + ); + return; + } + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return; + } + + if (empty($this->group)) { + $this->clientError('Group not found!', 404, $this->format); + return false; + } + + $member = new Group_member(); + + $member->group_id = $this->group->id; + $member->profile_id = $this->auth->id; + + if (!$member->find(true)) { + $this->serverError(_('You are not a member of this group.')); + return; + } + + $result = $member->delete(); + + if (!$result) { + common_log_db_error($member, 'INSERT', __FILE__); + $this->serverError( + sprintf( + _('Could not remove user %s to group %s.'), + $this->user->nickname, + $this->$group->nickname + ) + ); + return; + } + + switch($this->format) { + case 'xml': + $this->show_single_xml_group($this->group); + break; + case 'json': + $this->showSingleJsonGroup($this->group); + break; + default: + $this->clientError( + _('API method not found!'), + 404, + $this->format + ); + break; + } + } + +} diff --git a/actions/apigrouplist.php b/actions/apigrouplist.php new file mode 100644 index 000000000..c529c1e40 --- /dev/null +++ b/actions/apigrouplist.php @@ -0,0 +1,223 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Check to see whether a user a member of a group + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apibareauth.php'; + +/** + * Returns whether a user is a member of a specified group. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiGroupListAction extends ApiBareAuthAction +{ + var $groups = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->getTargetUser($id); + $this->groups = $this->getGroups(); + + return true; + } + + /** + * Handle the request + * + * Show the user's groups + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return; + } + + $sitename = common_config('site', 'name'); + $title = sprintf(_("%s's groups"), $this->user->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:Groups"; + $link = common_local_url( + 'usergroups', + array('nickname' => $this->user->nickname) + ); + $subtitle = sprintf( + _("Groups %s is a member of on %s."), + $this->user->nickname, + $sitename + ); + + switch($this->format) { + case 'xml': + $this->showXmlGroups($this->groups); + break; + case 'rss': + $this->showRssGroups($this->groups, $title, $link, $subtitle); + break; + case 'atom': + $selfuri = common_root_url() . 'api/statusnet/groups/list/' . + $this->user->id . '.atom'; + $this->showAtomGroups( + $this->groups, + $title, + $id, + $link, + $subtitle, + $selfuri + ); + break; + case 'json': + $this->showJsonGroups($this->groups); + break; + default: + $this->clientError( + _('API method not found!'), + 404, + $this->format + ); + break; + } + + } + + /** + * Get groups + * + * @return array groups + */ + + function getGroups() + { + $groups = array(); + + $group = $this->user->getGroups( + ($this->page - 1) * $this->count, + $this->count, + $this->since_id, + $this->max_id, + $this->since + ); + + while ($group->fetch()) { + $groups[] = clone($group); + } + + return $groups; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest group the user has joined + */ + + function lastModified() + { + if (!empty($this->groups) && (count($this->groups) > 0)) { + return strtotime($this->groups[0]->created); + } + + return null; + } + + /** + * An entity tag for this list of groups + * + * Returns an Etag based on the action name, language, user ID and + * timestamps of the first and last group the user has joined + * + * @return string etag + */ + + function etag() + { + if (!empty($this->groups) && (count($this->groups) > 0)) { + + $last = count($this->groups) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->user->id, + strtotime($this->groups[0]->created), + strtotime($this->groups[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apigrouplistall.php b/actions/apigrouplistall.php new file mode 100644 index 000000000..89469f36f --- /dev/null +++ b/actions/apigrouplistall.php @@ -0,0 +1,208 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show the newest groups + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Returns of the lastest 20 groups for the site + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiGroupListAllAction extends ApiAction +{ + var $groups = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->getTargetUser($id); + $this->groups = $this->getGroups(); + + return true; + } + + /** + * Handle the request + * + * Show the user's groups + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + $sitename = common_config('site', 'name'); + $title = sprintf(_("%s groups"), $sitename); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:Groups"; + $link = common_local_url('groups'); + $subtitle = sprintf(_("groups on %s"), $sitename); + + switch($this->format) { + case 'xml': + $this->showXmlGroups($this->groups); + break; + case 'rss': + $this->showRssGroups($this->groups, $title, $link, $subtitle); + break; + case 'atom': + $selfuri = common_root_url() . + 'api/statusnet/groups/list_all.atom'; + $this->showAtomGroups( + $this->groups, + $title, + $id, + $link, + $subtitle, + $selfuri + ); + break; + case 'json': + $this->showJsonGroups($this->groups); + break; + default: + $this->clientError( + _('API method not found!'), + 404, + $this->format + ); + break; + } + + } + + /** + * Get groups + * + * @return array groups + */ + + function getGroups() + { + $groups = array(); + + // XXX: Use the $page, $count, $max_id, $since_id, and $since parameters + + $group = new User_group(); + $group->orderBy('created DESC'); + $group->find(); + + while ($group->fetch()) { + $groups[] = clone($group); + } + + return $groups; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the site's latest group + */ + + function lastModified() + { + if (!empty($this->groups) && (count($this->groups) > 0)) { + return strtotime($this->groups[0]->created); + } + + return null; + } + + /** + * An entity tag for this list of groups + * + * Returns an Etag based on the action name, language, and + * timestamps of the first and last group the user has joined + * + * @return string etag + */ + + function etag() + { + if (!empty($this->groups) && (count($this->groups) > 0)) { + + $last = count($this->groups) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + strtotime($this->groups[0]->created), + strtotime($this->groups[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apigroupmembership.php b/actions/apigroupmembership.php new file mode 100644 index 000000000..b31e47b39 --- /dev/null +++ b/actions/apigroupmembership.php @@ -0,0 +1,192 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * List a group's members + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * List 20 newest members of the group specified by name or ID. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiGroupMembershipAction extends ApiAction +{ + var $group = null; + var $profiles = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->group = $this->getTargetGroup($this->arg('id')); + $this->profiles = $this->getProfiles(); + + return true; + } + + /** + * Handle the request + * + * Show the members of the group + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + // XXX: RSS and Atom + + switch($this->format) { + case 'xml': + $this->showTwitterXmlUsers($this->profiles); + break; + case 'json': + $this->showJsonUsers($this->profiles); + break; + default: + $this->clientError( + _('API method not found!'), + 404, + $this->format + ); + break; + } + } + + /** + * Fetch the members of a group + * + * @return array $profiles list of profiles + */ + + function getProfiles() + { + $profiles = array(); + + $profile = $this->group->getMembers( + ($this->page - 1) * $this->count, + $this->count, + $this->since_id, + $this->max_id, + $this->since + ); + + while ($profile->fetch()) { + $profiles[] = clone($profile); + } + + return $profiles; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this list of profiles last modified? + * + * @return string datestamp of the lastest profile in the group + */ + + function lastModified() + { + if (!empty($this->profiles) && (count($this->profiles) > 0)) { + return strtotime($this->profiles[0]->created); + } + + return null; + } + + /** + * An entity tag for this list of groups + * + * Returns an Etag based on the action name, language + * the group id, and timestamps of the first and last + * user who has joined the group + * + * @return string etag + */ + + function etag() + { + if (!empty($this->profiles) && (count($this->profiles) > 0)) { + + $last = count($this->profiles) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->group->id, + strtotime($this->profiles[0]->created), + strtotime($this->profiles[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apigroupshow.php b/actions/apigroupshow.php new file mode 100644 index 000000000..2bdb22bc4 --- /dev/null +++ b/actions/apigroupshow.php @@ -0,0 +1,152 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show information about a group + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Outputs detailed information about the group specified by ID + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiGroupShowAction extends ApiAction +{ + var $group = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->group = $this->getTargetGroup($this->arg('id')); + + return true; + } + + /** + * Handle the request + * + * Check the format and show the user info + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (empty($this->group)) { + $this->clientError( + 'Group not found!', + 404, + $this->format + ); + return; + } + + switch($this->format) { + case 'xml': + $this->show_single_xml_group($this->group); + break; + case 'json': + $this->showSingleJsonGroup($this->group); + break; + default: + $this->clientError(_('API method not found!'), 404, $this->format); + break; + } + + } + + /** + * When was this group last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->group)) { + return strtotime($this->group->modified); + } + + return null; + } + + /** + * An entity tag for this group + * + * Returns an Etag based on the action name, language, and + * timestamps of the notice + * + * @return string etag + */ + + function etag() + { + if (!empty($this->group)) { + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->group->id, + strtotime($this->group->modified)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apihelptest.php b/actions/apihelptest.php new file mode 100644 index 000000000..e4ef55f2e --- /dev/null +++ b/actions/apihelptest.php @@ -0,0 +1,96 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Test that you can connect to the API + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Returns the string "ok" in the requested format with a 200 OK HTTP status code. + * + * @category API + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiHelpTestAction extends ApiAction +{ + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + return true; + } + + /** + * Handle the request + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($this->format == 'xml') { + $this->initDocument('xml'); + $this->element('ok', null, 'true'); + $this->endDocument('xml'); + } elseif ($this->format == 'json') { + $this->initDocument('json'); + print '"ok"'; + $this->endDocument('json'); + } else { + $this->clientError( + _('API method not found!'), + 404, + $this->format + ); + } + } + +} + diff --git a/actions/apistatusesdestroy.php b/actions/apistatusesdestroy.php new file mode 100644 index 000000000..8dc8793b5 --- /dev/null +++ b/actions/apistatusesdestroy.php @@ -0,0 +1,154 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Destroy a notice through the API + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Tom Blankenship <mac65@mac65.com> + * @author Mike Cochrane <mikec@mikenz.geek.nz> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * Deletes one of the authenticating user's statuses (notices). + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Tom Blankenship <mac65@mac65.com> + * @author Mike Cochrane <mikec@mikenz.geek.nz> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiStatusesDestroyAction extends ApiAuthAction +{ + var $status = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + $this->notice_id = (int)$this->trimmed('id'); + + if (empty($notice_id)) { + $this->notice_id = (int)$this->arg('id'); + } + + $this->notice = Notice::staticGet((int)$this->notice_id); + + return true; + } + + /** + * Handle the request + * + * Delete the notice and all related replies + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError(_('API method not found!'), $code = 404); + return; + } + + if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) { + $this->clientError(_('This method requires a POST or DELETE.'), + 400, $this->format); + return; + } + + if (empty($this->notice)) { + $this->clientError(_('No status found with that ID.'), + 404, $this->format); + return; + } + + if ($this->user->id == $this->notice->profile_id) { + $replies = new Reply; + $replies->get('notice_id', $this->notice_id); + $replies->delete(); + $this->notice->delete(); + + if ($this->format == 'xml') { + $this->showSingleXmlStatus($this->notice); + } elseif ($this->format == 'json') { + $this->show_single_json_status($this->notice); + } + } else { + $this->clientError(_('You may not delete another user\'s status.'), + 403, $this->format); + } + + $this->showNotice(); + } + + /** + * Show the deleted notice + * + * @return void + */ + + function showNotice() + { + if (!empty($this->notice)) { + if ($this->format == 'xml') { + $this->showSingleXmlStatus($this->notice); + } elseif ($this->format == 'json') { + $this->show_single_json_status($this->notice); + } + } + } + +} diff --git a/actions/apistatusesshow.php b/actions/apistatusesshow.php new file mode 100644 index 000000000..3be22ca59 --- /dev/null +++ b/actions/apistatusesshow.php @@ -0,0 +1,206 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show a notice (as a Twitter-style status) + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Tom Blankenship <mac65@mac65.com> + * @author Mike Cochrane <mikec@mikenz.geek.nz> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Returns the notice specified by id as a Twitter-style status and inline user + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Tom Blankenship <mac65@mac65.com> + * @author Mike Cochrane <mikec@mikenz.geek.nz> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiStatusesShowAction extends ApiAction +{ + + var $notice_id = null; + var $notice = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + // 'id' is an undocumented parameter in Twitter's API. Several + // clients make use of it, so we support it too. + + // show.json?id=12345 takes precedence over /show/12345.json + + $this->notice_id = (int)$this->trimmed('id'); + + if (empty($notice_id)) { + $this->notice_id = (int)$this->arg('id'); + } + + $this->notice = Notice::staticGet((int)$this->notice_id); + + return true; + } + + /** + * Handle the request + * + * Check the format and show the notice + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError(_('API method not found!'), $code = 404); + return; + } + + $this->showNotice(); + } + + /** + * Show the notice + * + * @return void + */ + + function showNotice() + { + if (!empty($this->notice)) { + if ($this->format == 'xml') { + $this->showSingleXmlStatus($this->notice); + } elseif ($this->format == 'json') { + $this->show_single_json_status($this->notice); + } + } else { + + // XXX: Twitter just sets a 404 header and doens't bother + // to return an err msg + + $deleted = Deleted_notice::staticGet($this->notice_id); + + if (!empty($deleted)) { + $this->clientError( + _('Status deleted.'), + 410, + $this->format + ); + } else { + $this->clientError( + _('No status with that ID found.'), + 404, + $this->format + ); + } + } + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this notice last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->notice)) { + return strtotime($this->notice->created); + } + + return null; + } + + /** + * An entity tag for this notice + * + * Returns an Etag based on the action name, language, and + * timestamps of the notice + * + * @return string etag + */ + + function etag() + { + if (!empty($this->notice)) { + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->notice->id, + strtotime($this->notice->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php new file mode 100644 index 000000000..0d71e1512 --- /dev/null +++ b/actions/apistatusesupdate.php @@ -0,0 +1,241 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Post a notice (update your status) through the API + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Tom Blankenship <mac65@mac65.com> + * @author Mike Cochrane <mikec@mikenz.geek.nz> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +/** + * Updates the authenticating user's status (posts a notice). + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Tom Blankenship <mac65@mac65.com> + * @author Mike Cochrane <mikec@mikenz.geek.nz> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiStatusesUpdateAction extends ApiAuthAction +{ + var $source = null; + var $status = null; + var $in_reply_to_status_id = null; + + static $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api'); + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return false; + } + + $this->status = $this->trimmed('status'); + + if (empty($this->status)) { + $this->clientError( + 'Client must provide a \'status\' parameter with a value.', + 400, + $this->format + ); + + return false; + } + + $this->source = $this->trimmed('source'); + + if (empty($this->source) || in_array($source, $this->reserved_sources)) { + $this->source = 'api'; + } + + $this->in_reply_to_status_id + = intval($this->trimmed('in_reply_to_status_id')); + + return true; + } + + /** + * Handle the request + * + * Make a new notice for the update, save it, and show it + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, $this->format + ); + return; + } + + $status_shortened = common_shorten_links($this->status); + + if (Notice::contentTooLong($status_shortened)) { + + // Note: Twitter truncates anything over 140, flags the status + // as "truncated." + + $this->clientError( + sprintf( + _('That\'s too long. Max notice size is %d chars.'), + Notice::maxContent() + ), + 406, + $this->format + ); + + return; + } + + // Check for commands + + $inter = new CommandInterpreter(); + $cmd = $inter->handle_command($this->user, $status_shortened); + + if ($cmd) { + + if ($this->supported($cmd)) { + $cmd->execute(new Channel()); + } + + // Cmd not supported? Twitter just returns your latest status. + // And, it returns your last status whether the cmd was successful + // or not! + + $this->notice = $this->user->getCurrentNotice(); + + } else { + + $reply_to = null; + + if (!empty($this->in_reply_to_status_id)) { + + // Check whether notice actually exists + + $reply = Notice::staticGet($this->in_reply_to_status_id); + + if ($reply) { + $reply_to = $this->in_reply_to_status_id; + } else { + $this->clientError( + _('Not found'), + $code = 404, + $this->format + ); + return; + } + } + + $this->notice = Notice::saveNew( + $this->user->id, + html_entity_decode($this->status, ENT_NOQUOTES, 'UTF-8'), + $this->source, + 1, + $reply_to + ); + + common_broadcast_notice($this->notice); + } + + $this->showNotice(); + } + + /** + * Show the resulting notice + * + * @return void + */ + + function showNotice() + { + if (!empty($this->notice)) { + if ($this->format == 'xml') { + $this->showSingleXmlStatus($this->notice); + } elseif ($this->format == 'json') { + $this->show_single_json_status($this->notice); + } + } + } + + /** + * Is this command supported when doing an update from the API? + * + * @param string $cmd the command to check for + * + * @return boolean true or false + */ + + function supported($cmd) + { + static $cmdlist = array('MessageCommand', 'SubCommand', 'UnsubCommand', + 'FavCommand', 'OnCommand', 'OffCommand'); + + if (in_array(get_class($cmd), $cmdlist)) { + return true; + } + + return false; + } + +} diff --git a/actions/apistatusnetconfig.php b/actions/apistatusnetconfig.php new file mode 100644 index 000000000..ed1d151bf --- /dev/null +++ b/actions/apistatusnetconfig.php @@ -0,0 +1,142 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Dump of configuration variables + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Gives a full dump of configuration variables for this instance + * of StatusNet, minus variables that may be security-sensitive (like + * passwords). + * URL: http://identi.ca/api/statusnet/config.(xml|json) + * Formats: xml, json + * + * @category API + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiStatusnetConfigAction extends ApiAction +{ + var $keys = array( + 'site' => array('name', 'server', 'theme', 'path', 'fancy', 'language', + 'email', 'broughtby', 'broughtbyurl', 'closed', + 'inviteonly', 'private'), + 'license' => array('url', 'title', 'image'), + 'nickname' => array('featured'), + 'throttle' => array('enabled', 'count', 'timespan'), + 'xmpp' => array('enabled', 'server', 'user') + ); + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + return true; + } + + /** + * Handle the request + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + switch ($this->format) { + case 'xml': + $this->initDocument('xml'); + $this->elementStart('config'); + + // XXX: check that all sections and settings are legal XML elements + + common_debug(var_export($this->keys, true)); + + foreach ($this->keys as $section => $settings) { + $this->elementStart($section); + foreach ($settings as $setting) { + $value = common_config($section, $setting); + if (is_array($value)) { + $value = implode(',', $value); + } else if ($value === false) { + $value = 'false'; + } else if ($value === true) { + $value = 'true'; + } + $this->element($setting, null, $value); + } + $this->elementEnd($section); + } + $this->elementEnd('config'); + $this->endDocument('xml'); + break; + case 'json': + $result = array(); + foreach ($this->keys as $section => $settings) { + $result[$section] = array(); + foreach ($settings as $setting) { + $result[$section][$setting] + = common_config($section, $setting); + } + } + $this->initDocument('json'); + $this->showJsonObjects($result); + $this->endDocument('json'); + break; + default: + $this->clientError( + _('API method not found!'), + 404, + $this->format + ); + break; + } + } + +} + diff --git a/actions/apistatusnetversion.php b/actions/apistatusnetversion.php new file mode 100644 index 000000000..e73ab983b --- /dev/null +++ b/actions/apistatusnetversion.php @@ -0,0 +1,102 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * A version stamp for the API + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Returns a version number for this version of StatusNet, which + * should make things a bit easier for upgrades. + * URL: http://identi.ca/api/statusnet/version.(xml|json) + * Formats: xml, js + * + * @category API + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiStatusnetVersionAction extends ApiAction +{ + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + return true; + } + + /** + * Handle the request + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + switch ($this->format) { + case 'xml': + $this->initDocument('xml'); + $this->element('version', null, STATUSNET_VERSION); + $this->endDocument('xml'); + break; + case 'json': + $this->initDocument('json'); + print '"'.STATUSNET_VERSION.'"'; + $this->endDocument('json'); + break; + default: + $this->clientError( + _('API method not found!'), + 404, + $this->format + ); + break; + } + } + +} + diff --git a/actions/apisubscriptions.php b/actions/apisubscriptions.php new file mode 100644 index 000000000..bc68dd192 --- /dev/null +++ b/actions/apisubscriptions.php @@ -0,0 +1,266 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Base class for showing subscription information in the API + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apibareauth.php'; + +/** + * This class outputs a list of profiles as Twitter-style user and status objects. + * It is used by the API methods /api/statuses/(friends|followers). To support the + * social graph methods it also can output a simple list of IDs. + * + * @category API + * @package StatusNet + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiSubscriptionsAction extends ApiBareAuthAction +{ + var $profiles = null; + var $tag = null; + var $lite = null; + var $ids_only = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->tag = $this->arg('tag'); + + // Note: Twitter no longer supports 'lite' + $this->lite = $this->arg('lite'); + + $this->ids_only = $this->arg('ids_only'); + + // If called as a social graph method, show 5000 per page, otherwise 100 + + $this->count = isset($this->ids_only) ? + 5000 : (int)$this->arg('count', 100); + + $this->user = $this->getTargetUser($this->arg('id')); + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return false; + } + + $this->profiles = $this->getProfiles(); + + return true; + } + + /** + * Handle the request + * + * Show the profiles + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError(_('API method not found!'), $code = 404); + return; + } + + $this->initDocument($this->format); + + if (isset($this->ids_only)) { + $this->showIds(); + } else { + $this->showProfiles(isset($this->lite) ? false : true); + } + + $this->endDocument($this->format); + } + + /** + * Get profiles - should get overrrided + * + * @return array Profiles + */ + + function getProfiles() + { + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest profile in the stream + */ + + function lastModified() + { + if (!empty($this->profiles) && (count($this->profiles) > 0)) { + return strtotime($this->profiles[0]->created); + } + + return null; + } + + /** + * An entity tag for this action + * + * Returns an Etag based on the action name, language, user ID, and + * timestamps of the first and last profiles in the subscriptions list + * There's also an indicator to show whether this action is being called + * as /api/statuses/(friends|followers) or /api/(friends|followers)/ids + * + * @return string etag + */ + + function etag() + { + if (!empty($this->profiles) && (count($this->profiles) > 0)) { + + $last = count($this->profiles) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->user->id, + isset($this->ids_only) ? 'IDs' : 'Profiles', + strtotime($this->profiles[0]->created), + strtotime($this->profiles[$last]->created)) + ) + . '"'; + } + + return null; + } + + /** + * Show the profiles as Twitter-style useres and statuses + * + * @param boolean $include_statuses Whether to include the latest status + * with each user. Default true. + * + * @return void + */ + + function showProfiles($include_statuses = true) + { + switch ($this->format) { + case 'xml': + $this->elementStart('users', array('type' => 'array')); + foreach ($this->profiles as $profile) { + $this->showProfile( + $profile, + $this->format, + null, + $include_statuses + ); + } + $this->elementEnd('users'); + break; + case 'json': + $arrays = array(); + foreach ($this->profiles as $profile) { + $arrays[] = $this->twitterUserArray( + $profile, + $include_statuses + ); + } + print json_encode($arrays); + break; + default: + $this->clientError(_('Unsupported format.')); + break; + } + } + + /** + * Show the IDs of the profiles only. 5000 per page. To support + * the 'social graph' methods: /api/(friends|followers)/ids + * + * @return void + */ + + function showIds() + { + switch ($this->format) { + case 'xml': + $this->elementStart('ids'); + foreach ($this->profiles as $profile) { + $this->element('id', null, $profile->id); + } + $this->elementEnd('ids'); + break; + case 'json': + $ids = array(); + foreach ($this->profiles as $profile) { + $ids[] = (int)$profile->id; + } + print json_encode($ids); + break; + default: + $this->clientError(_('Unsupported format.')); + break; + } + } + +} diff --git a/actions/apitimelinefavorites.php b/actions/apitimelinefavorites.php new file mode 100644 index 000000000..b8ae74f13 --- /dev/null +++ b/actions/apitimelinefavorites.php @@ -0,0 +1,237 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show a user's favorite notices + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/apibareauth.php'; + +/** + * Returns the 20 most recent favorite notices for the authenticating user or user + * specified by the ID parameter in the requested format. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineFavoritesAction extends ApiBareAuthAction +{ + var $notices = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->getTargetUser($this->arg('id')); + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return; + } + + $this->notices = $this->getNotices(); + + return true; + } + + /** + * Handle the request + * + * Just show the notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showTimeline(); + } + + /** + * Show the timeline of notices + * + * @return void + */ + + function showTimeline() + { + $profile = $this->user->getProfile(); + + $sitename = common_config('site', 'name'); + $title = sprintf( + _('%s / Favorites from %s'), + $sitename, + $this->user->nickname + ); + + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:Favorites:" . $this->user->id; + $link = common_local_url( + 'favorites', + array('nickname' => $this->user->nickname) + ); + $subtitle = sprintf( + _('%s updates favorited by %s / %s.'), + $sitename, + $profile->getBestName(), + $this->user->nickname + ); + + switch($this->format) { + case 'xml': + $this->showXmlTimeline($this->notices); + break; + case 'rss': + $this->showRssTimeline($this->notices, $title, $link, $subtitle); + break; + case 'atom': + $selfuri = common_root_url() . + ltrim($_SERVER['QUERY_STRING'], 'p='); + $this->showAtomTimeline( + $this->notices, $title, $id, $link, $subtitle, + null, $selfuri + ); + break; + case 'json': + $this->showJsonTimeline($this->notices); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } + + /** + * Get notices + * + * @return array notices + */ + + function getNotices() + { + $notices = array(); + + if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) { + $notice = $this->user->favoriteNotices( + ($this->page-1) * $this->count, + $this->count, + true + ); + } else { + $notice = $this->user->favoriteNotices( + ($this->page-1) * $this->count, + $this->count, + false + ); + } + + while ($notice->fetch()) { + $notices[] = clone($notice); + } + + return $notices; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + return strtotime($this->notices[0]->created); + } + + return null; + } + + /** + * An entity tag for this stream + * + * Returns an Etag based on the action name, language, user ID, and + * timestamps of the first and last notice in the timeline + * + * @return string etag + */ + + function etag() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + + $last = count($this->notices) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->user->id, + strtotime($this->notices[0]->created), + strtotime($this->notices[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apitimelinefriends.php b/actions/apitimelinefriends.php new file mode 100644 index 000000000..1ea35866e --- /dev/null +++ b/actions/apitimelinefriends.php @@ -0,0 +1,247 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show the friends timeline + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author mac65 <mac65@mac65.com> + * @author Mike Cochrane <mikec@mikenz.geek.nz> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apibareauth.php'; + +/** + * Returns the most recent notices (default 20) posted by the target user. + * This is the equivalent of 'You and friends' page accessed via Web. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author mac65 <mac65@mac65.com> + * @author Mike Cochrane <mikec@mikenz.geek.nz> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineFriendsAction extends ApiBareAuthAction +{ + var $notices = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->getTargetUser($this->arg('id')); + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return; + } + + $this->notices = $this->getNotices(); + + return true; + } + + /** + * Handle the request + * + * Just show the notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showTimeline(); + } + + /** + * Show the timeline of notices + * + * @return void + */ + + function showTimeline() + { + $profile = $this->user->getProfile(); + $sitename = common_config('site', 'name'); + $title = sprintf(_("%s and friends"), $this->user->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:FriendsTimeline:" . $this->user->id; + $link = common_local_url( + 'all', array('nickname' => $this->user->nickname) + ); + $subtitle = sprintf( + _('Updates from %1$s and friends on %2$s!'), + $this->user->nickname, $sitename + ); + + switch($this->format) { + case 'xml': + $this->showXmlTimeline($this->notices); + break; + case 'rss': + $this->showRssTimeline($this->notices, $title, $link, $subtitle); + break; + case 'atom': + + $target_id = $this->arg('id'); + + if (isset($target_id)) { + $selfuri = common_root_url() . + 'api/statuses/friends_timeline/' . + $target_id . '.atom'; + } else { + $selfuri = common_root_url() . + 'api/statuses/friends_timeline.atom'; + } + + $this->showAtomTimeline( + $this->notices, $title, $id, $link, + $subtitle, null, $selfuri + ); + break; + case 'json': + $this->showJsonTimeline($this->notices); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } + + /** + * Get notices + * + * @return array notices + */ + + function getNotices() + { + $notices = array(); + + if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) { + $notice = $this->user->noticeInbox( + ($this->page-1) * $this->count, + $this->count, $this->since_id, + $this->max_id, $this->since + ); + } else { + $notice = $this->user->noticesWithFriends( + ($this->page-1) * $this->count, + $this->count, $this->since_id, + $this->max_id, $this->since + ); + } + + while ($notice->fetch()) { + $notices[] = clone($notice); + } + + return $notices; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + return strtotime($this->notices[0]->created); + } + + return null; + } + + /** + * An entity tag for this stream + * + * Returns an Etag based on the action name, language, user ID, and + * timestamps of the first and last notice in the timeline + * + * @return string etag + */ + + function etag() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + + $last = count($this->notices) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->user->id, + strtotime($this->notices[0]->created), + strtotime($this->notices[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apitimelinegroup.php b/actions/apitimelinegroup.php new file mode 100644 index 000000000..5d0542918 --- /dev/null +++ b/actions/apitimelinegroup.php @@ -0,0 +1,231 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show a group's notices + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Returns the most recent notices (default 20) posted to the group specified by ID + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineGroupAction extends ApiAction +{ + + var $group = null; + var $notices = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->group = $this->getTargetGroup($this->arg('id')); + $this->notices = $this->getNotices(); + + return true; + } + + /** + * Handle the request + * + * Just show the notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showTimeline(); + } + + /** + * Show the timeline of notices + * + * @return void + */ + + function showTimeline() + { + $sitename = common_config('site', 'name'); + $title = sprintf(_("%s timeline"), $this->group->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:GroupTimeline:" . $this->group->id; + $link = common_local_url( + 'showgroup', + array('nickname' => $this->group->nickname) + ); + $subtitle = sprintf( + _('Updates from %1$s on %2$s!'), + $this->group->nickname, + $sitename + ); + + switch($this->format) { + case 'xml': + $this->showXmlTimeline($this->notices); + break; + case 'rss': + $this->showRssTimeline($this->notices, $title, $link, $subtitle); + break; + case 'atom': + $selfuri = common_root_url() . + 'api/statusnet/groups/timeline/' . + $this->group->nickname . '.atom'; + $this->showAtomTimeline( + $this->notices, + $title, + $id, + $link, + $subtitle, + null, + $selfuri + ); + break; + case 'json': + $this->showJsonTimeline($this->notices); + break; + default: + $this->clientError( + _('API method not found!'), + 404, + $this->format + ); + break; + } + } + + /** + * Get notices + * + * @return array notices + */ + + function getNotices() + { + $notices = array(); + + $notice = $this->group->getNotices( + ($this->page-1) * $this->count, + $this->count, + $this->since_id, + $this->max_id, + $this->since + ); + + while ($notice->fetch()) { + $notices[] = clone($notice); + } + + return $notices; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + return strtotime($this->notices[0]->created); + } + + return null; + } + + /** + * An entity tag for this stream + * + * Returns an Etag based on the action name, language, group ID and + * timestamps of the first and last notice in the timeline + * + * @return string etag + */ + + function etag() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + + $last = count($this->notices) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->group->id, + strtotime($this->notices[0]->created), + strtotime($this->notices[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apitimelinementions.php b/actions/apitimelinementions.php new file mode 100644 index 000000000..fe5ff0f28 --- /dev/null +++ b/actions/apitimelinementions.php @@ -0,0 +1,233 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show notices mentioning a user (@nickname) + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author mac65 <mac65@mac65.com> + * @author Mike Cochrane <mikec@mikenz.geek.nz> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apibareauth.php'; + +/** + * Returns the most recent (default 20) mentions (status containing @nickname) + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author mac65 <mac65@mac65.com> + * @author Mike Cochrane <mikec@mikenz.geek.nz> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineMentionsAction extends ApiBareAuthAction +{ + + var $notices = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->getTargetUser($this->arg('id')); + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return; + } + + $this->notices = $this->getNotices(); + + return true; + } + + /** + * Handle the request + * + * Just show the notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showTimeline(); + } + + /** + * Show the timeline of notices + * + * @return void + */ + + function showTimeline() + { + $profile = $this->user->getProfile(); + + $sitename = common_config('site', 'name'); + $title = sprintf( + _('%1$s / Updates mentioning %2$s'), + $sitename, $this->user->nickname + ); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:Mentions:" . $this->user->id; + $link = common_local_url( + 'replies', + array('nickname' => $this->user->nickname) + ); + $subtitle = sprintf( + _('%1$s updates that reply to updates from %2$s / %3$s.'), + $sitename, $this->user->nickname, $profile->getBestName() + ); + + switch($this->format) { + case 'xml': + $this->showXmlTimeline($this->notices); + break; + case 'rss': + $this->showRssTimeline($this->notices, $title, $link, $subtitle); + break; + case 'atom': + $selfuri = common_root_url() . + ltrim($_SERVER['QUERY_STRING'], 'p='); + $this->showAtomTimeline( + $this->notices, $title, $id, $link, $subtitle, + null, $selfuri + ); + break; + case 'json': + $this->showJsonTimeline($this->notices); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } + + /** + * Get notices + * + * @return array notices + */ + + function getNotices() + { + $notices = array(); + + $notice = $this->user->getReplies( + ($this->page - 1) * $this->count, $this->count, + $this->since_id, $this->max_id, $this->since + ); + + while ($notice->fetch()) { + $notices[] = clone($notice); + } + + return $notices; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + return strtotime($this->notices[0]->created); + } + + return null; + } + + /** + * An entity tag for this stream + * + * Returns an Etag based on the action name, language, user ID, and + * timestamps of the first and last notice in the timeline + * + * @return string etag + */ + + function etag() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + + $last = count($this->notices) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->user->id, + strtotime($this->notices[0]->created), + strtotime($this->notices[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apitimelinepublic.php b/actions/apitimelinepublic.php new file mode 100644 index 000000000..58e267734 --- /dev/null +++ b/actions/apitimelinepublic.php @@ -0,0 +1,213 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show the public timeline + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author mac65 <mac65@mac65.com> + * @author Mike Cochrane <mikec@mikenz.geek.nz> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Returns the most recent notices (default 20) posted by everybody + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author mac65 <mac65@mac65.com> + * @author Mike Cochrane <mikec@mikenz.geek.nz> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelinePublicAction extends ApiAction +{ + + var $notices = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->notices = $this->getNotices(); + + return true; + } + + /** + * Handle the request + * + * Just show the notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showTimeline(); + } + + /** + * Show the timeline of notices + * + * @return void + */ + + function showTimeline() + { + $sitename = common_config('site', 'name'); + $title = sprintf(_("%s public timeline"), $sitename); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:PublicTimeline"; + $link = common_root_url(); + $subtitle = sprintf(_("%s updates from everyone!"), $sitename); + + switch($this->format) { + case 'xml': + $this->showXmlTimeline($this->notices); + break; + case 'rss': + $this->showRssTimeline($this->notices, $title, $link, $subtitle); + break; + case 'atom': + $selfuri = common_root_url() . 'api/statuses/public_timeline.atom'; + $this->showAtomTimeline( + $this->notices, $title, $id, $link, + $subtitle, null, $selfuri + ); + break; + case 'json': + $this->showJsonTimeline($this->notices); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } + + /** + * Get notices + * + * @return array notices + */ + + function getNotices() + { + $notices = array(); + + $notice = Notice::publicStream( + ($this->page - 1) * $this->count, $this->count, $this->since_id, + $this->max_id, $this->since + ); + + while ($notice->fetch()) { + $notices[] = clone($notice); + } + + return $notices; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + return strtotime($this->notices[0]->created); + } + + return null; + } + + /** + * An entity tag for this stream + * + * Returns an Etag based on the action name, language, and + * timestamps of the first and last notice in the timeline + * + * @return string etag + */ + + function etag() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + + $last = count($this->notices) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + strtotime($this->notices[0]->created), + strtotime($this->notices[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apitimelinetag.php b/actions/apitimelinetag.php new file mode 100644 index 000000000..a274daac0 --- /dev/null +++ b/actions/apitimelinetag.php @@ -0,0 +1,224 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show the latest notices for a given tag + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Returns the 20 most recent notices tagged by a given tag + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineTagAction extends ApiAction +{ + + var $notices = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->tag = $this->arg('tag'); + $this->notices = $this->getNotices(); + + return true; + } + + /** + * Handle the request + * + * Just show the notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showTimeline(); + } + + /** + * Show the timeline of notices + * + * @return void + */ + + function showTimeline() + { + $sitename = common_config('site', 'name'); + $title = sprintf(_("Notices tagged with %s"), $this->tag); + $link = common_local_url( + 'tag', + array('tag' => $this->tag) + ); + $subtitle = sprintf( + _('Updates tagged with %1$s on %2$s!'), + $this->tag, + $sitename + ); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:TagTimeline:".$tag; + + switch($this->format) { + case 'xml': + $this->showXmlTimeline($this->notices); + break; + case 'rss': + $this->showRssTimeline($this->notices, $title, $link, $subtitle); + break; + case 'atom': + $selfuri = common_root_url() . + 'api/statusnet/tags/timeline/' . + $this->tag . '.atom'; + $this->showAtomTimeline( + $this->notices, + $title, + $id, + $link, + $subtitle, + null, + $selfuri + ); + break; + case 'json': + $this->showJsonTimeline($this->notices); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } + + /** + * Get notices + * + * @return array notices + */ + + function getNotices() + { + $notices = array(); + + $notice = Notice_tag::getStream( + $this->tag, + ($this->page - 1) * $this->count, + $this->count + 1 + ); + + while ($notice->fetch()) { + $notices[] = clone($notice); + } + + return $notices; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + return strtotime($this->notices[0]->created); + } + + return null; + } + + /** + * An entity tag for this stream + * + * Returns an Etag based on the action name, language, and + * timestamps of the first and last notice in the timeline + * + * @return string etag + */ + + function etag() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + + $last = count($this->notices) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->tag, + strtotime($this->notices[0]->created), + strtotime($this->notices[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php new file mode 100644 index 000000000..285735fd1 --- /dev/null +++ b/actions/apitimelineuser.php @@ -0,0 +1,248 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show a user's timeline + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author mac65 <mac65@mac65.com> + * @author Mike Cochrane <mikec@mikenz.geek.nz> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apibareauth.php'; + +/** + * Returns the most recent notices (default 20) posted by the authenticating + * user. Another user's timeline can be requested via the id parameter. This + * is the API equivalent of the user profile web page. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author mac65 <mac65@mac65.com> + * @author Mike Cochrane <mikec@mikenz.geek.nz> + * @author Robin Millette <robin@millette.info> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineUserAction extends ApiBareAuthAction +{ + + var $notices = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->getTargetUser($this->arg('id')); + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return; + } + + $this->notices = $this->getNotices(); + + return true; + } + + /** + * Handle the request + * + * Just show the notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showTimeline(); + } + + /** + * Show the timeline of notices + * + * @return void + */ + + function showTimeline() + { + $profile = $this->user->getProfile(); + + $sitename = common_config('site', 'name'); + $title = sprintf(_("%s timeline"), $this->user->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:UserTimeline:" . $this->user->id; + $link = common_local_url( + 'showstream', + array('nickname' => $this->user->nickname) + ); + $subtitle = sprintf( + _('Updates from %1$s on %2$s!'), + $this->user->nickname, $sitename + ); + + // FriendFeed's SUP protocol + // Also added RSS and Atom feeds + + $suplink = common_local_url('sup', null, null, $this->user->id); + header('X-SUP-ID: ' . $suplink); + + switch($this->format) { + case 'xml': + $this->showXmlTimeline($this->notices); + break; + case 'rss': + $this->showRssTimeline( + $this->notices, $title, $link, + $subtitle, $suplink + ); + break; + case 'atom': + if (isset($apidata['api_arg'])) { + $selfuri = common_root_url() . + 'api/statuses/user_timeline/' . + $apidata['api_arg'] . '.atom'; + } else { + $selfuri = common_root_url() . + 'api/statuses/user_timeline.atom'; + } + $this->showAtomTimeline( + $this->notices, $title, $id, $link, + $subtitle, $suplink, $selfuri + ); + break; + case 'json': + $this->showJsonTimeline($this->notices); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + + } + + /** + * Get notices + * + * @return array notices + */ + + function getNotices() + { + $notices = array(); + + $notice = $this->user->getNotices( + ($this->page-1) * $this->count, $this->count, + $this->since_id, $this->max_id, $this->since + ); + + while ($notice->fetch()) { + $notices[] = clone($notice); + } + + return $notices; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + return strtotime($this->notices[0]->created); + } + + return null; + } + + /** + * An entity tag for this stream + * + * Returns an Etag based on the action name, language, user ID, and + * timestamps of the first and last notice in the timeline + * + * @return string etag + */ + + function etag() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + + $last = count($this->notices) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->user->id, + strtotime($this->notices[0]->created), + strtotime($this->notices[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apiuserfollowers.php b/actions/apiuserfollowers.php new file mode 100644 index 000000000..e8d92a773 --- /dev/null +++ b/actions/apiuserfollowers.php @@ -0,0 +1,89 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show a user's followers (subscribers) + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apibareauth.php'; + +/** + * Ouputs the authenticating user's followers (subscribers), each with + * current Twitter-style status inline. They are ordered by the order + * in which they subscribed to the user, 100 at a time. + * + * @category API + * @package StatusNet + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiUserFollowersAction extends ApiSubscriptionsAction +{ + /** + * Get the user's subscribers (followers) as an array of profiles + * + * @return array Profiles + */ + + function getProfiles() + { + $offset = ($this->page - 1) * $this->count; + $limit = $this->count + 1; + + $subs = null; + + if (isset($this->tag)) { + $subs = $this->user->getTaggedSubscribers( + $this->tag, $offset, $limit + ); + } else { + $subs = $this->user->getSubscribers( + $offset, + $limit + ); + } + + $profiles = array(); + + if (!empty($subs)) { + while ($subs->fetch()) { + $profiles[] = clone($subs); + } + } + + return $profiles; + } + +} diff --git a/actions/apiuserfriends.php b/actions/apiuserfriends.php new file mode 100644 index 000000000..741a26e58 --- /dev/null +++ b/actions/apiuserfriends.php @@ -0,0 +1,89 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show a user's friends (subscriptions) + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apibareauth.php'; + +/** + * Ouputs the authenticating user's friends (subscriptions), each with + * current Twitter-style status inline. They are ordered by the date + * in which the user subscribed to them, 100 at a time. + * + * @category API + * @package StatusNet + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiUserFriendsAction extends ApiSubscriptionsAction +{ + /** + * Get the user's subscriptions (friends) as an array of profiles + * + * @return array Profiles + */ + + function getProfiles() + { + $offset = ($this->page - 1) * $this->count; + $limit = $this->count + 1; + + $subs = null; + + if (isset($this->tag)) { + $subs = $this->user->getTaggedSubscriptions( + $this->tag, $offset, $limit + ); + } else { + $subs = $this->user->getSubscriptions( + $offset, + $limit + ); + } + + $profiles = array(); + + if (!empty($subs)) { + while ($subs->fetch()) { + $profiles[] = clone($subs); + } + } + + return $profiles; + } + +} diff --git a/actions/apiusershow.php b/actions/apiusershow.php new file mode 100644 index 000000000..b3a939b43 --- /dev/null +++ b/actions/apiusershow.php @@ -0,0 +1,126 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show a user's profile information + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author mac65 <mac65@mac65.com> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Ouputs information for a user, specified by ID or screen name. + * The user's most recent status will be returned inline. + * + * @category API + * @package StatusNet + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author mac65 <mac65@mac65.com> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiUserShowAction extends ApiAction +{ + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $email = $this->arg('email'); + + // XXX: email field deprecated in Twitter's API + + if (!empty($email)) { + $this->user = User::staticGet('email', $email); + } else { + $this->user = $this->getTargetUser($this->arg('id')); + } + + return true; + } + + /** + * Handle the request + * + * Check the format and show the user info + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (empty($this->user)) { + $this->clientError(_('Not found.'), 404, $this->format); + return; + } + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError(_('API method not found!'), $code = 404); + return; + } + + $profile = $this->user->getProfile(); + + if (empty($profile)) { + $this->clientError(_('User has no profile.')); + return; + } + + $twitter_user = $this->twitterUserArray($this->user->getProfile(), true); + + if ($this->format == 'xml') { + $this->initDocument('xml'); + $this->showTwitterXmlUser($twitter_user); + $this->endDocument('xml'); + } elseif ($this->format == 'json') { + $this->initDocument('json'); + $this->showJsonObjects($twitter_user); + $this->endDocument('json'); + } + + } + +} diff --git a/actions/avatarsettings.php b/actions/avatarsettings.php index 02a684b38..ded419dd7 100644 --- a/actions/avatarsettings.php +++ b/actions/avatarsettings.php @@ -362,13 +362,13 @@ class AvatarsettingsAction extends AccountSettingsAction $profile = $user->getProfile(); $avatar = $profile->getOriginalAvatar(); - $avatar->delete(); + if($avatar) $avatar->delete(); $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); - $avatar->delete(); + if($avatar) $avatar->delete(); $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE); - $avatar->delete(); + if($avatar) $avatar->delete(); $avatar = $profile->getAvatar(AVATAR_MINI_SIZE); - $avatar->delete(); + if($avatar) $avatar->delete(); $this->showForm(_('Avatar deleted.'), true); } diff --git a/actions/deletenotice.php b/actions/deletenotice.php index 3d040f2fa..4a48a9c34 100644 --- a/actions/deletenotice.php +++ b/actions/deletenotice.php @@ -32,15 +32,45 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/deleteaction.php'; - -class DeletenoticeAction extends DeleteAction +class DeletenoticeAction extends Action { - var $error = null; + var $error = null; + var $user = null; + var $notice = null; + var $profile = null; + var $user_profile = null; + + function prepare($args) + { + parent::prepare($args); + + $this->user = common_current_user(); + $notice_id = $this->trimmed('notice'); + $this->notice = Notice::staticGet($notice_id); + + if (!$this->notice) { + common_user_error(_('No such notice.')); + exit; + } + + $this->profile = $this->notice->getProfile(); + $this->user_profile = $this->user->getProfile(); + + return true; + } function handle($args) { parent::handle($args); + + if (!common_logged_in()) { + common_user_error(_('Not logged in.')); + exit; + } else if ($this->notice->profile_id != $this->user_profile->id && + !$this->user->hasRight(Right::deleteOthersNotice)) { + common_user_error(_('Can\'t delete this notice.')); + exit; + } // XXX: Ajax! if ($_SERVER['REQUEST_METHOD'] == 'POST') { diff --git a/actions/editgroup.php b/actions/editgroup.php index 0c2dc8bdf..5dd039f8a 100644 --- a/actions/editgroup.php +++ b/actions/editgroup.php @@ -250,7 +250,6 @@ class EditgroupAction extends GroupDesignAction $this->group->homepage = $homepage; $this->group->description = $description; $this->group->location = $location; - $this->group->created = common_sql_now(); $result = $this->group->update($orig); diff --git a/actions/favoritesrss.php b/actions/favoritesrss.php index 2d5ce9854..62f06e841 100644 --- a/actions/favoritesrss.php +++ b/actions/favoritesrss.php @@ -50,11 +50,11 @@ require_once INSTALLDIR.'/lib/rssaction.php'; */ class FavoritesrssAction extends Rss10Action { - + /** The user whose favorites to display */ - + var $user = null; - + /** * Find the user to display by supplied nickname * @@ -66,7 +66,7 @@ class FavoritesrssAction extends Rss10Action function prepare($args) { parent::prepare($args); - + $nickname = $this->trimmed('nickname'); $this->user = User::staticGet('nickname', $nickname); @@ -74,10 +74,11 @@ class FavoritesrssAction extends Rss10Action $this->clientError(_('No such user.')); return false; } else { + $this->notices = $this->getNotices($this->limit); return true; } } - + /** * Get notices * diff --git a/actions/finishremotesubscribe.php b/actions/finishremotesubscribe.php index 5f6807d10..b1cec66f4 100644 --- a/actions/finishremotesubscribe.php +++ b/actions/finishremotesubscribe.php @@ -143,67 +143,4 @@ class FinishremotesubscribeAction extends Action $user->nickname)), 303); } - - function add_avatar($profile, $url) - { - $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar'); - copy($url, $temp_filename); - $imagefile = new ImageFile($profile->id, $temp_filename); - $filename = Avatar::filename($profile->id, - image_type_to_extension($imagefile->type), - null, - common_timestamp()); - rename($temp_filename, Avatar::path($filename)); - return $profile->setOriginal($filename); - } - - function access_token($omb) - { - - common_debug('starting request for access token', __FILE__); - - $con = omb_oauth_consumer(); - $tok = new OAuthToken($omb['token'], $omb['secret']); - - common_debug('using request token "'.$tok.'"', __FILE__); - - $url = $omb['access_token_url']; - - common_debug('using access token url "'.$url.'"', __FILE__); - - # XXX: Is this the right thing to do? Strip off GET params and make them - # POST params? Seems wrong to me. - - $parsed = parse_url($url); - $params = array(); - parse_str($parsed['query'], $params); - - $req = OAuthRequest::from_consumer_and_token($con, $tok, "POST", $url, $params); - - $req->set_parameter('omb_version', OMB_VERSION_01); - - # XXX: test to see if endpoint accepts this signature method - - $req->sign_request(omb_hmac_sha1(), $con, $tok); - - # We re-use this tool's fetcher, since it's pretty good - - common_debug('posting to access token url "'.$req->get_normalized_http_url().'"', __FILE__); - common_debug('posting request data "'.$req->to_postdata().'"', __FILE__); - - $fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); - $result = $fetcher->post($req->get_normalized_http_url(), - $req->to_postdata(), - array('User-Agent: StatusNet/' . STATUSNET_VERSION)); - - common_debug('got result: "'.print_r($result,true).'"', __FILE__); - - if ($result->status != 200) { - return null; - } - - parse_str($result->body, $return); - - return array($return['oauth_token'], $return['oauth_token_secret']); - } } diff --git a/actions/foafgroup.php b/actions/foafgroup.php new file mode 100644 index 000000000..f5fd7fe88 --- /dev/null +++ b/actions/foafgroup.php @@ -0,0 +1,173 @@ +<?php +/* + * StatusNet the distributed open-source microblogging tool + * Copyright (C) 2008, 2009, StatusNet, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category Mail + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @author Toby Inkster <mail@tobyinkster.co.uk> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +class FoafGroupAction extends Action +{ + function isReadOnly($args) + { + return true; + } + + function prepare($args) + { + parent::prepare($args); + + $nickname_arg = $this->arg('nickname'); + + if (empty($nickname_arg)) { + $this->clientError(_('No such group.'), 404); + return false; + } + + $this->nickname = common_canonical_nickname($nickname_arg); + + // Permanent redirect on non-canonical nickname + + if ($nickname_arg != $this->nickname) { + common_redirect(common_local_url('foafgroup', + array('nickname' => $this->nickname)), + 301); + return false; + } + + $this->group = User_group::staticGet('nickname', $this->nickname); + + if (!$this->group) { + $this->clientError(_('No such group.'), 404); + return false; + } + + common_set_returnto($this->selfUrl()); + + return true; + } + + function handle($args) + { + parent::handle($args); + + header('Content-Type: application/rdf+xml'); + + $this->startXML(); + $this->elementStart('rdf:RDF', array('xmlns:rdf' => + 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'xmlns:dcterms' => + 'http://purl.org/dc/terms/', + 'xmlns:sioc' => + 'http://rdfs.org/sioc/ns#', + 'xmlns:foaf' => + 'http://xmlns.com/foaf/0.1/', + 'xmlns:statusnet' => + 'http://status.net/ont/', + 'xmlns' => 'http://xmlns.com/foaf/0.1/')); + + $this->showPpd(common_local_url('foafgroup', array('nickname' => $this->nickname)), $this->group->permalink()); + + $this->elementStart('Group', array('rdf:about' => + $this->group->permalink())); + if ($this->group->fullname) { + $this->element('name', null, $this->group->fullname); + } + if ($this->group->description) { + $this->element('dcterms:description', null, $this->group->description); + } + if ($this->group->nickname) { + $this->element('dcterms:identifier', null, $this->group->nickname); + $this->element('nick', null, $this->group->nickname); + } + foreach ($this->group->getAliases() as $alias) { + $this->element('nick', null, $alias); + } + if ($this->group->homeUrl()) { + $this->element('weblog', array('rdf:resource' => $this->group->homeUrl())); + } + if ($this->group->homepage) { + $this->element('page', array('rdf:resource' => $this->group->homepage)); + } + if ($this->group->homepage_logo) { + $this->element('depiction', array('rdf:resource' => $this->group->homepage_logo)); + } + + $members = $this->group->getMembers(); + $member_details = array(); + while ($members->fetch()) { + $member_uri = common_local_url('userbyid', array('id'=>$members->id)); + $member_details[$member_uri] = array( + 'nickname' => $members->nickname + ); + $this->element('member', array('rdf:resource' => $member_uri)); + } + + $admins = $this->group->getAdmins(); + while ($admins->fetch()) { + $admin_uri = common_local_url('userbyid', array('id'=>$admins->id)); + $member_details[$admin_uri]['is_admin'] = true; + $this->element('statusnet:groupAdmin', array('rdf:resource' => $admin_uri)); + } + + $this->elementEnd('Group'); + + ksort($member_details); + foreach ($member_details as $uri => $details) { + if ($details['is_admin']) + { + $this->elementStart('Agent', array('rdf:about' => $uri)); + $this->element('nick', null, $details['nickname']); + $this->elementStart('holdsAccount'); + $this->elementStart('sioc:User', array('rdf:about'=>$uri.'#acct')); + $this->elementStart('sioc:has_function'); + $this->elementStart('statusnet:GroupAdminRole'); + $this->element('sioc:scope', array('rdf:resource' => $this->group->permalink())); + $this->elementEnd('statusnet:GroupAdminRole'); + $this->elementEnd('sioc:has_function'); + $this->elementEnd('sioc:User'); + $this->elementEnd('holdsAccount'); + $this->elementEnd('Agent'); + } + else + { + $this->element('Agent', array( + 'foaf:nick' => $details['nickname'], + 'rdf:about' => $uri, + )); + } + } + + $this->elementEnd('rdf:RDF'); + $this->endXML(); + } + + function showPpd($foaf_url, $person_uri) + { + $this->elementStart('Document', array('rdf:about' => $foaf_url)); + $this->element('primaryTopic', array('rdf:resource' => $person_uri)); + $this->elementEnd('Document'); + } + +}
\ No newline at end of file diff --git a/actions/grouprss.php b/actions/grouprss.php index 70c1ded48..6a6b55e78 100644 --- a/actions/grouprss.php +++ b/actions/grouprss.php @@ -104,6 +104,7 @@ class groupRssAction extends Rss10Action return false; } + $this->notices = $this->getNotices($this->limit); return true; } diff --git a/actions/groupsearch.php b/actions/groupsearch.php index f0cca7156..55f4cee62 100644 --- a/actions/groupsearch.php +++ b/actions/groupsearch.php @@ -105,7 +105,7 @@ class GroupSearchResults extends GroupList function __construct($user_group, $terms, $action) { - parent::__construct($user_group, $terms, $action); + parent::__construct($user_group, null, $action); $this->terms = array_map('preg_quote', array_map('htmlspecialchars', $terms)); $this->pattern = '/('.implode('|',$terms).')/i'; diff --git a/actions/newnotice.php b/actions/newnotice.php index 4c6372c2b..d5b0332f4 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -255,13 +255,6 @@ class NewnoticeAction extends Action $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1, ($replyto == 'false') ? null : $replyto); - if (is_string($notice)) { - if (isset($filename)) { - $this->deleteFile($filename); - } - $this->clientError($notice); - } - if (isset($mimetype)) { $this->attachFile($notice, $fileRecord); } @@ -433,13 +426,14 @@ class NewnoticeAction extends Action $content = $this->trimmed('status_textarea'); if (!$content) { $replyto = $this->trimmed('replyto'); + $inreplyto = $this->trimmed('inreplyto'); $profile = Profile::staticGet('nickname', $replyto); if ($profile) { $content = '@' . $profile->nickname . ' '; } } - $notice_form = new NoticeForm($this, '', $content); + $notice_form = new NoticeForm($this, '', $content, null, $inreplyto); $notice_form->show(); } diff --git a/actions/publicrss.php b/actions/publicrss.php index 593888b9f..0c5d061cb 100644 --- a/actions/publicrss.php +++ b/actions/publicrss.php @@ -50,8 +50,22 @@ require_once INSTALLDIR.'/lib/rssaction.php'; class PublicrssAction extends Rss10Action { /** + * Read arguments and initialize members + * + * @param array $args Arguments from $_REQUEST + * @return boolean success + */ + + function prepare($args) + { + parent::prepare($args); + $this->notices = $this->getNotices($this->limit); + return true; + } + + /** * Initialization. - * + * * @return boolean true */ function init() @@ -73,7 +87,7 @@ class PublicrssAction extends Rss10Action while ($notice->fetch()) { $notices[] = clone($notice); } - + return $notices; } diff --git a/actions/repliesrss.php b/actions/repliesrss.php index c71c9226f..76aae21ad 100644 --- a/actions/repliesrss.php +++ b/actions/repliesrss.php @@ -38,6 +38,7 @@ class RepliesrssAction extends Rss10Action $this->clientError(_('No such user.')); return false; } else { + $this->notices = $this->getNotices($this->limit); return true; } } diff --git a/actions/showgroup.php b/actions/showgroup.php index ff9949762..a67765ce5 100644 --- a/actions/showgroup.php +++ b/actions/showgroup.php @@ -345,7 +345,12 @@ class ShowgroupAction extends GroupDesignAction 'method' => 'timeline', 'argument' => $this->group->nickname.'.atom')), sprintf(_('Notice feed for %s group (Atom)'), - $this->group->nickname))); + $this->group->nickname)), + new Feed(Feed::FOAF, + common_local_url('foafgroup', + array('nickname' => $this->group->nickname)), + sprintf(_('FOAF for %s group'), + $this->group->nickname))); } /** diff --git a/actions/showstream.php b/actions/showstream.php index 2e9679fae..cdac4f47b 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -378,8 +378,13 @@ class ShowstreamAction extends ProfileAction $this->showEmptyListMessage(); } + $args = array('nickname' => $this->user->nickname); + if (!empty($this->tag)) + { + $args['tag'] = $this->tag; + } $this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page, - 'showstream', array('nickname' => $this->user->nickname)); + 'showstream', $args); } function showAnonymousMessage() diff --git a/actions/twitapiaccount.php b/actions/twitapiaccount.php deleted file mode 100644 index 93c8443c9..000000000 --- a/actions/twitapiaccount.php +++ /dev/null @@ -1,127 +0,0 @@ -<?php -/* - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once(INSTALLDIR.'/lib/twitterapi.php'); - -class TwitapiaccountAction extends TwitterapiAction -{ - function verify_credentials($args, $apidata) - { - parent::handle($args); - - switch ($apidata['content-type']) { - case 'xml': - case 'json': - $action_obj = new TwitapiusersAction(); - $action_obj->prepare($args); - call_user_func(array($action_obj, 'show'), $args, $apidata); - break; - default: - header('Content-Type: text/html; charset=utf-8'); - print 'Authorized'; - } - } - - function end_session($args, $apidata) - { - parent::handle($args); - $this->serverError(_('API method under construction.'), $code=501); - } - - function update_location($args, $apidata) - { - parent::handle($args); - - if ($_SERVER['REQUEST_METHOD'] != 'POST') { - $this->clientError(_('This method requires a POST.'), - 400, $apidata['content-type']); - return; - } - - $location = trim($this->arg('location')); - - if (!is_null($location) && mb_strlen($location) > 255) { - - // XXX: But Twitter just truncates and runs with it. -- Zach - $this->clientError(_('That\'s too long. Max notice size is 255 chars.'), - 406, $apidate['content-type']); - return; - } - - $user = $apidata['user']; // Always the auth user - $profile = $user->getProfile(); - - $orig_profile = clone($profile); - $profile->location = $location; - - $result = $profile->update($orig_profile); - - if (empty($result)) { - common_log_db_error($profile, 'UPDATE', __FILE__); - $this->serverError(_('Couldn\'t save profile.')); - return; - } - - common_broadcast_profile($profile); - $type = $apidata['content-type']; - - $this->init_document($type); - $this->show_profile($profile, $type); - $this->end_document($type); - } - - - function update_delivery_device($args, $apidata) - { - parent::handle($args); - $this->serverError(_('API method under construction.'), $code=501); - } - - // We don't have a rate limit, but some clients check this method. - // It always returns the same thing: 100 hit left. - function rate_limit_status($args, $apidata) - { - parent::handle($args); - - $type = $apidata['content-type']; - $this->init_document($type); - - if ($apidata['content-type'] == 'xml') { - $this->elementStart('hash'); - $this->element('remaining-hits', array('type' => 'integer'), 100); - $this->element('hourly-limit', array('type' => 'integer'), 100); - $this->element('reset-time', array('type' => 'datetime'), null); - $this->element('reset_time_in_seconds', array('type' => 'integer'), 0); - $this->elementEnd('hash'); - } elseif ($apidata['content-type'] == 'json') { - - $out = array('reset_time_in_seconds' => 0, - 'remaining_hits' => 100, - 'hourly_limit' => 100, - 'reset_time' => ''); - print json_encode($out); - } - - $this->end_document($type); - } -} diff --git a/actions/twitapiblocks.php b/actions/twitapiblocks.php deleted file mode 100644 index ed17946ae..000000000 --- a/actions/twitapiblocks.php +++ /dev/null @@ -1,74 +0,0 @@ -<?php -/* - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once(INSTALLDIR.'/lib/twitterapi.php'); - -class TwitapiblocksAction extends TwitterapiAction -{ - - function create($args, $apidata) - { - - parent::handle($args); - - $blockee = $this->get_user($apidata['api_arg'], $apidata); - - if (empty($blockee)) { - $this->clientError('Not Found', 404, $apidata['content-type']); - return; - } - - $user = $apidata['user']; // Always the auth user - - if ($user->hasBlocked($blockee) || $user->block($blockee)) { - $type = $apidata['content-type']; - $this->init_document($type); - $this->show_profile($blockee, $type); - $this->end_document($type); - } else { - $this->serverError(_('Block user failed.')); - } - } - - function destroy($args, $apidata) - { - parent::handle($args); - $blockee = $this->get_user($apidata['api_arg'], $apidata); - - if (empty($blockee)) { - $this->clientError('Not Found', 404, $apidata['content-type']); - return; - } - - $user = $apidata['user']; - - if (!$user->hasBlocked($blockee) || $user->unblock($blockee)) { - $type = $apidata['content-type']; - $this->init_document($type); - $this->show_profile($blockee, $type); - $this->end_document($type); - } else { - $this->serverError(_('Unblock user failed.')); - } - } -}
\ No newline at end of file diff --git a/actions/twitapidirect_messages.php b/actions/twitapidirect_messages.php deleted file mode 100644 index 08b8f4e9c..000000000 --- a/actions/twitapidirect_messages.php +++ /dev/null @@ -1,305 +0,0 @@ -<?php -/* - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once(INSTALLDIR.'/lib/twitterapi.php'); - -class Twitapidirect_messagesAction extends TwitterapiAction -{ - - function direct_messages($args, $apidata) - { - parent::handle($args); - return $this->show_messages($args, $apidata, 'received'); - } - - function sent($args, $apidata) - { - parent::handle($args); - return $this->show_messages($args, $apidata, 'sent'); - } - - function show_messages($args, $apidata, $type) - { - $user = $apidata['user']; // Always the auth user - - $message = new Message(); - $title = null; - $subtitle = null; - $link = null; - $server = common_root_url(); - - if ($type == 'received') { - $message->to_profile = $user->id; - $title = sprintf(_("Direct messages to %s"), $user->nickname); - $subtitle = sprintf(_("All the direct messages sent to %s"), - $user->nickname); - $link = $server . $user->nickname . '/inbox'; - } else { - $message->from_profile = $user->id; - $title = _('Direct Messages You\'ve Sent'); - $subtitle = sprintf(_("All the direct messages sent from %s"), - $user->nickname); - $link = $server . $user->nickname . '/outbox'; - } - - $page = (int)$this->arg('page', 1); - $count = (int)$this->arg('count', 20); - $max_id = (int)$this->arg('max_id', 0); - $since_id = (int)$this->arg('since_id', 0); - $since = $this->arg('since'); - - if ($max_id) { - $message->whereAdd("id <= $max_id"); - } - - if ($since_id) { - $message->whereAdd("id > $since_id"); - } - - if ($since) { - $d = date('Y-m-d H:i:s', $since); - $message->whereAdd("created > '$d'"); - } - - $message->orderBy('created DESC, id DESC'); - $message->limit((($page-1)*$count), $count); - $message->find(); - - switch($apidata['content-type']) { - case 'xml': - $this->show_xml_dmsgs($message); - break; - case 'rss': - $this->show_rss_dmsgs($message, $title, $link, $subtitle); - break; - case 'atom': - $selfuri = common_root_url() . 'api/direct_messages'; - $selfuri .= ($type == 'received') ? '.atom' : '/sent.atom'; - $taguribase = common_config('integration', 'taguri'); - - if ($type == 'sent') { - $id = "tag:$taguribase:SentDirectMessages:" . $user->id; - } else { - $id = "tag:$taguribase:DirectMessages:" . $user->id; - } - - $this->show_atom_dmsgs($message, $title, $link, $subtitle, - $selfuri, $id); - break; - case 'json': - $this->show_json_dmsgs($message); - break; - default: - $this->clientError(_('API method not found!'), $code = 404); - } - - } - - // had to change this from "new" to "create" to avoid PHP reserved word - function create($args, $apidata) - { - parent::handle($args); - - if ($_SERVER['REQUEST_METHOD'] != 'POST') { - $this->clientError(_('This method requires a POST.'), - 400, $apidata['content-type']); - return; - } - - $user = $apidata['user']; - $source = $this->trimmed('source'); // Not supported by Twitter. - - $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api'); - if (empty($source) || in_array($source, $reserved_sources)) { - $source = 'api'; - } - - $content = $this->trimmed('text'); - - if (empty($content)) { - $this->clientError(_('No message text!'), - $code = 406, $apidata['content-type']); - } else { - $content_shortened = common_shorten_links($content); - if (Message::contentTooLong($content_shortened)) { - $this->clientError(sprintf(_('That\'s too long. Max message size is %d chars.'), - Message::maxContent()), - $code = 406, $apidata['content-type']); - return; - } - } - - $other = $this->get_user($this->trimmed('user')); - - if (empty($other)) { - $this->clientError(_('Recipient user not found.'), - $code = 403, $apidata['content-type']); - return; - } else if (!$user->mutuallySubscribed($other)) { - $this->clientError(_('Can\'t send direct messages to users who aren\'t your friend.'), - $code = 403, $apidata['content-type']); - return; - } else if ($user->id == $other->id) { - // Sending msgs to yourself is allowed by Twitter - $this->clientError(_('Don\'t send a message to yourself; just say it to yourself quietly instead.'), - $code = 403, $apidata['content-type']); - return; - } - - $message = Message::saveNew($user->id, $other->id, - html_entity_decode($content, ENT_NOQUOTES, 'UTF-8'), $source); - - if (is_string($message)) { - $this->serverError($message); - return; - } - - $this->notify($user, $other, $message); - - if ($apidata['content-type'] == 'xml') { - $this->show_single_xml_dmsg($message); - } elseif ($apidata['content-type'] == 'json') { - $this->show_single_json_dmsg($message); - } - - } - - function destroy($args, $apidata) - { - parent::handle($args); - $this->serverError(_('API method under construction.'), $code=501); - } - - function show_xml_dmsgs($message) - { - - $this->init_document('xml'); - $this->elementStart('direct-messages', array('type' => 'array')); - - if (is_array($message)) { - foreach ($message as $m) { - $twitter_dm = $this->twitter_dmsg_array($m); - $this->show_twitter_xml_dmsg($twitter_dm); - } - } else { - while ($message->fetch()) { - $twitter_dm = $this->twitter_dmsg_array($message); - $this->show_twitter_xml_dmsg($twitter_dm); - } - } - - $this->elementEnd('direct-messages'); - $this->end_document('xml'); - - } - - function show_json_dmsgs($message) - { - - $this->init_document('json'); - - $dmsgs = array(); - - if (is_array($message)) { - foreach ($message as $m) { - $twitter_dm = $this->twitter_dmsg_array($m); - array_push($dmsgs, $twitter_dm); - } - } else { - while ($message->fetch()) { - $twitter_dm = $this->twitter_dmsg_array($message); - array_push($dmsgs, $twitter_dm); - } - } - - $this->show_json_objects($dmsgs); - $this->end_document('json'); - - } - - function show_rss_dmsgs($message, $title, $link, $subtitle) - { - - $this->init_document('rss'); - - $this->elementStart('channel'); - $this->element('title', null, $title); - - $this->element('link', null, $link); - $this->element('description', null, $subtitle); - $this->element('language', null, 'en-us'); - $this->element('ttl', null, '40'); - - if (is_array($message)) { - foreach ($message as $m) { - $entry = $this->twitter_rss_dmsg_array($m); - $this->show_twitter_rss_item($entry); - } - } else { - while ($message->fetch()) { - $entry = $this->twitter_rss_dmsg_array($message); - $this->show_twitter_rss_item($entry); - } - } - - $this->elementEnd('channel'); - $this->end_twitter_rss(); - - } - - function show_atom_dmsgs($message, $title, $link, $subtitle, $selfuri, $id) - { - - $this->init_document('atom'); - - $this->element('title', null, $title); - $this->element('id', null, $id); - $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null); - $this->element('link', array('href' => $selfuri, 'rel' => 'self', - 'type' => 'application/atom+xml'), null); - $this->element('updated', null, common_date_iso8601('now')); - $this->element('subtitle', null, $subtitle); - - if (is_array($message)) { - foreach ($message as $m) { - $entry = $this->twitter_rss_dmsg_array($m); - $this->show_twitter_atom_entry($entry); - } - } else { - while ($message->fetch()) { - $entry = $this->twitter_rss_dmsg_array($message); - $this->show_twitter_atom_entry($entry); - } - } - - $this->end_document('atom'); - } - - // swiped from MessageAction. Should it be place in util.php? - function notify($from, $to, $message) - { - mail_notify_message($message, $from, $to); - # XXX: Jabber, SMS notifications... probably queued - } - -} diff --git a/actions/twitapifavorites.php b/actions/twitapifavorites.php deleted file mode 100644 index f8943fe2d..000000000 --- a/actions/twitapifavorites.php +++ /dev/null @@ -1,216 +0,0 @@ -<?php -/* - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once(INSTALLDIR.'/lib/twitterapi.php'); - -class TwitapifavoritesAction extends TwitterapiAction -{ - - function favorites($args, $apidata) - { - parent::handle($args); - - $this->auth_user = $apidata['user']; - $user = $this->get_user($apidata['api_arg'], $apidata); - - if (empty($user)) { - if ($apidata['content-type'] == 'xml') { - $this->show_single_xml_status($notice); - } elseif ($apidata['content-type'] == 'json') { - $this->show_single_json_status($notice); - } - $this->clientError('Not Found', 404, $apidata['content-type']); - return; - } - - $profile = $user->getProfile(); - - $sitename = common_config('site', 'name'); - $title = sprintf(_('%s / Favorites from %s'), $sitename, - $user->nickname); - $taguribase = common_config('integration', 'taguri'); - $id = "tag:$taguribase:Favorites:".$user->id; - $link = common_local_url('favorites', - array('nickname' => $user->nickname)); - $subtitle = sprintf(_('%s updates favorited by %s / %s.'), $sitename, - $profile->getBestName(), $user->nickname); - - $page = (int)$this->arg('page', 1); - $count = (int)$this->arg('count', 20); - $max_id = (int)$this->arg('max_id', 0); - $since_id = (int)$this->arg('since_id', 0); - $since = $this->arg('since'); - - if (!empty($this->auth_user) && $this->auth_user->id == $user->id) { - $notice = $user->favoriteNotices(($page-1)*$count, $count, true); - } else { - $notice = $user->favoriteNotices(($page-1)*$count, $count, false); - } - - switch($apidata['content-type']) { - case 'xml': - $this->show_xml_timeline($notice); - break; - case 'rss': - $this->show_rss_timeline($notice, $title, $link, $subtitle); - break; - case 'atom': - if (isset($apidata['api_arg'])) { - $selfuri = $selfuri = common_root_url() . - 'api/favorites/' . $apidata['api_arg'] . '.atom'; - } else { - $selfuri = $selfuri = common_root_url() . - 'api/favorites.atom'; - } - $this->show_atom_timeline($notice, $title, $id, $link, - $subtitle, null, $selfuri); - break; - case 'json': - $this->show_json_timeline($notice); - break; - default: - $this->clientError(_('API method not found!'), $code = 404); - } - - } - - function create($args, $apidata) - { - parent::handle($args); - - // Check for RESTfulness - if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) { - $this->clientError(_('This method requires a POST or DELETE.'), - 400, $apidata['content-type']); - return; - } - - if (!in_array($apidata['content-type'], array('xml', 'json'))) { - $this->clientError(_('API method not found!'), $code = 404); - return; - } - - $user = $apidata['user']; // Always the auth user - $notice_id = $apidata['api_arg']; - $notice = Notice::staticGet($notice_id); - - if (empty($notice)) { - $this->clientError(_('No status found with that ID.'), - 404, $apidata['content-type']); - return; - } - - // XXX: Twitter lets you fave things repeatedly via api. - if ($user->hasFave($notice)) { - $this->clientError(_('This status is already a favorite!'), - 403, $apidata['content-type']); - return; - } - - $fave = Fave::addNew($user, $notice); - - if (empty($fave)) { - $this->clientError(_('Could not create favorite.')); - return; - } - - $this->notify($fave, $notice, $user); - $user->blowFavesCache(); - - if ($apidata['content-type'] == 'xml') { - $this->show_single_xml_status($notice); - } elseif ($apidata['content-type'] == 'json') { - $this->show_single_json_status($notice); - } - - } - - function destroy($args, $apidata) - { - parent::handle($args); - - // Check for RESTfulness - if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) { - $this->clientError(_('This method requires a POST or DELETE.'), - 400, $apidata['content-type']); - return; - } - - if (!in_array($apidata['content-type'], array('xml', 'json'))) { - $this->clientError(_('API method not found!'), $code = 404); - return; - } - - $user = $apidata['user']; // Always the auth user - $notice_id = $apidata['api_arg']; - $notice = Notice::staticGet($notice_id); - - if (empty($notice)) { - $this->clientError(_('No status found with that ID.'), - 404, $apidata['content-type']); - return; - } - - $fave = new Fave(); - $fave->user_id = $this->id; - $fave->notice_id = $notice->id; - - if (!$fave->find(true)) { - $this->clientError(_('That status is not a favorite!'), - 403, $apidata['content-type']); - return; - } - - $result = $fave->delete(); - - if (!$result) { - common_log_db_error($fave, 'DELETE', __FILE__); - $this->clientError(_('Could not delete favorite.'), 404); - return; - } - - $user->blowFavesCache(); - - if ($apidata['content-type'] == 'xml') { - $this->show_single_xml_status($notice); - } elseif ($apidata['content-type'] == 'json') { - $this->show_single_json_status($notice); - } - - } - - // XXX: these two funcs swiped from faves. - // Maybe put in util.php, or some common base class? - - function notify($fave, $notice, $user) - { - $other = User::staticGet('id', $notice->profile_id); - if ($other && $other->id != $user->id) { - if ($other->email && $other->emailnotifyfav) { - mail_notify_fave($other, $user, $notice); - } - # XXX: notify by IM - # XXX: notify by SMS - } - } -} diff --git a/actions/twitapifriendships.php b/actions/twitapifriendships.php deleted file mode 100644 index eea8945c3..000000000 --- a/actions/twitapifriendships.php +++ /dev/null @@ -1,250 +0,0 @@ -<?php -/* - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once(INSTALLDIR.'/lib/twitterapi.php'); - -class TwitapifriendshipsAction extends TwitterapiAction -{ - - function create($args, $apidata) - { - parent::handle($args); - - if ($_SERVER['REQUEST_METHOD'] != 'POST') { - $this->clientError(_('This method requires a POST.'), - 400, $apidata['content-type']); - return; - } - - $id = $apidata['api_arg']; - $other = $this->get_user($id); - - if (empty($other)) { - $this->clientError(_('Could not follow user: User not found.'), - 403, $apidata['content-type']); - return; - } - - $user = $apidata['user']; - - if ($user->isSubscribed($other)) { - $errmsg = sprintf(_('Could not follow user: %s is already on your list.'), - $other->nickname); - $this->clientError($errmsg, 403, $apidata['content-type']); - return; - } - - $sub = new Subscription(); - - $sub->query('BEGIN'); - - $sub->subscriber = $user->id; - $sub->subscribed = $other->id; - $sub->created = DB_DataObject_Cast::dateTime(); # current time - - $result = $sub->insert(); - - if (empty($result)) { - $errmsg = sprintf(_('Could not follow user: %s is already on your list.'), - $other->nickname); - $this->clientError($errmsg, 400, $apidata['content-type']); - return; - } - - $sub->query('COMMIT'); - - mail_subscribe_notify($other, $user); - - $type = $apidata['content-type']; - $this->init_document($type); - $this->show_profile($other, $type); - $this->end_document($type); - - } - - function destroy($args, $apidata) - { - parent::handle($args); - - if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) { - $this->clientError(_('This method requires a POST or DELETE.'), - 400, $apidata['content-type']); - return; - } - - $id = $apidata['api_arg']; - - # We can't subscribe to a remote person, but we can unsub - - $other = $this->get_profile($id); - $user = $apidata['user']; // Alwyas the auth user - - if ($user->id == $other->id) { - $this->clientError(_("You cannot unfollow yourself!"), - 403, $apidata['content-type']); - return; - } - - $sub = new Subscription(); - $sub->subscriber = $user->id; - $sub->subscribed = $other->id; - - if ($sub->find(true)) { - $sub->query('BEGIN'); - $sub->delete(); - $sub->query('COMMIT'); - } else { - $this->clientError(_('You are not friends with the specified user.'), - 403, $apidata['content-type']); - return; - } - - $type = $apidata['content-type']; - $this->init_document($type); - $this->show_profile($other, $type); - $this->end_document($type); - - } - - function exists($args, $apidata) - { - parent::handle($args); - - if (!in_array($apidata['content-type'], array('xml', 'json'))) { - $this->clientError(_('API method not found!'), $code = 404); - return; - } - - $user_a_id = $this->trimmed('user_a'); - $user_b_id = $this->trimmed('user_b'); - - $user_a = $this->get_user($user_a_id); - $user_b = $this->get_user($user_b_id); - - if (empty($user_a) || empty($user_b)) { - $this->clientError(_('Two user ids or screen_names must be supplied.'), - 400, $apidata['content-type']); - return; - } - - $result = $user_a->isSubscribed($user_b); - - switch ($apidata['content-type']) { - case 'xml': - $this->init_document('xml'); - $this->element('friends', null, $result); - $this->end_document('xml'); - break; - case 'json': - $this->init_document('json'); - print json_encode($result); - $this->end_document('json'); - break; - default: - break; - } - - } - - function show($args, $apidata) - { - parent::handle($args); - - if (!in_array($apidata['content-type'], array('xml', 'json'))) { - $this->clientError(_('API method not found!'), $code = 404); - return; - } - - $source_id = (int)$this->trimmed('source_id'); - $source_screen_name = $this->trimmed('source_screen_name'); - - // If the source is not specified for an unauthenticated request, - // the method will return an HTTP 403. - - if (empty($source_id) && empty($source_screen_name)) { - if (empty($apidata['user'])) { - $this->clientError(_('Could not determine source user.'), - $code = 403); - return; - } - } - - $source = null; - - if (!empty($source_id)) { - $source = User::staticGet($source_id); - } elseif (!empty($source_screen_name)) { - $source = User::staticGet('nickname', $source_screen_name); - } else { - $source = $apidata['user']; - } - - // If a source or target is specified but does not exist, - // the method will return an HTTP 404. - - if (empty($source)) { - $this->clientError(_('Could not determine source user.'), - $code = 404); - return; - } - - $target_id = (int)$this->trimmed('target_id'); - $target_screen_name = $this->trimmed('target_screen_name'); - - $target = null; - - if (!empty($target_id)) { - $target = User::staticGet($target_id); - } elseif (!empty($target_screen_name)) { - $target = User::staticGet('nickname', $target_screen_name); - } else { - $this->clientError(_('Target user not specified.'), - $code = 403); - return; - } - - if (empty($target)) { - $this->clientError(_('Could not find target user.'), - $code = 404); - return; - } - - $result = $this->twitter_relationship_array($source, $target); - - switch ($apidata['content-type']) { - case 'xml': - $this->init_document('xml'); - $this->show_twitter_xml_relationship($result[relationship]); - $this->end_document('xml'); - break; - case 'json': - $this->init_document('json'); - print json_encode($result); - $this->end_document('json'); - break; - default: - break; - } - } - -} diff --git a/actions/twitapigroups.php b/actions/twitapigroups.php deleted file mode 100644 index 4deb1b764..000000000 --- a/actions/twitapigroups.php +++ /dev/null @@ -1,329 +0,0 @@ -<?php -/** - * StatusNet, the distributed open-source microblogging tool - * - * StatusNet extensions to the Twitter-like API for groups - * - * PHP version 5 - * - * LICENCE: 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 <http://www.gnu.org/licenses/>. - * - * @category Twitter - * @package StatusNet - * @author Craig Andrews <candrews@integralblue.com> - * @author Zach Copley <zach@status.net> - * @copyright 2009 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR.'/lib/twitterapi.php'; - -/** - * Group-specific API methods - * - * This class handles StatusNet group API methods. - * - * @category Twitter - * @package StatusNet - * @author Craig Andrews <candrews@integralblue.com> - * @author Zach Copley <zach@status.net> - * @copyright 2009 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - - class TwitapigroupsAction extends TwitterapiAction - { - - function list_groups($args, $apidata) - { - parent::handle($args); - - common_debug("in groups api action"); - - $this->auth_user = $apidata['user']; - $user = $this->get_user($apidata['api_arg'], $apidata); - - if (empty($user)) { - $this->clientError('Not Found', 404, $apidata['content-type']); - return; - } - - $page = (int)$this->arg('page', 1); - $count = (int)$this->arg('count', 20); - $max_id = (int)$this->arg('max_id', 0); - $since_id = (int)$this->arg('since_id', 0); - $since = $this->arg('since'); - $group = $user->getGroups(($page-1)*$count, - $count, $since_id, $max_id, $since); - - $sitename = common_config('site', 'name'); - $title = sprintf(_("%s's groups"), $user->nickname); - $taguribase = common_config('integration', 'taguri'); - $id = "tag:$taguribase:Groups"; - $link = common_root_url(); - $subtitle = sprintf(_("groups %s is a member of on %s"), $user->nickname, $sitename); - - switch($apidata['content-type']) { - case 'xml': - $this->show_xml_groups($group); - break; - case 'rss': - $this->show_rss_groups($group, $title, $link, $subtitle); - break; - case 'atom': - $selfuri = common_root_url() . 'api/statusnet/groups/list/' . $user->id . '.atom'; - $this->show_atom_groups($group, $title, $id, $link, - $subtitle, $selfuri); - break; - case 'json': - $this->show_json_groups($group); - break; - default: - $this->clientError(_('API method not found!'), $code = 404); - break; - } - } - - function list_all($args, $apidata) - { - parent::handle($args); - - common_debug("in groups api action"); - - $page = (int)$this->arg('page', 1); - $count = (int)$this->arg('count', 20); - $max_id = (int)$this->arg('max_id', 0); - $since_id = (int)$this->arg('since_id', 0); - $since = $this->arg('since'); - - /* TODO: - Use the $page, $count, $max_id, $since_id, and $since parameters - */ - $group = new User_group(); - $group->orderBy('created DESC'); - $group->find(); - - $sitename = common_config('site', 'name'); - $title = sprintf(_("%s groups"), $sitename); - $taguribase = common_config('integration', 'taguri'); - $id = "tag:$taguribase:Groups"; - $link = common_root_url(); - $subtitle = sprintf(_("groups on %s"), $sitename); - - switch($apidata['content-type']) { - case 'xml': - $this->show_xml_groups($group); - break; - case 'rss': - $this->show_rss_groups($group, $title, $link, $subtitle); - break; - case 'atom': - $selfuri = common_root_url() . 'api/statusnet/groups/list_all.atom'; - $this->show_atom_groups($group, $title, $id, $link, - $subtitle, $selfuri); - break; - case 'json': - $this->show_json_groups($group); - break; - default: - $this->clientError(_('API method not found!'), $code = 404); - break; - } - } - - function show($args, $apidata) - { - parent::handle($args); - - common_debug("in groups api action"); - - $this->auth_user = $apidata['user']; - $group = $this->get_group($apidata['api_arg'], $apidata); - - if (empty($group)) { - $this->clientError('Not Found', 404, $apidata['content-type']); - return; - } - - switch($apidata['content-type']) { - case 'xml': - $this->show_single_xml_group($group); - break; - case 'json': - $this->show_single_json_group($group); - break; - default: - $this->clientError(_('API method not found!'), $code = 404); - } - } - - function timeline($args, $apidata) - { - parent::handle($args); - - common_debug("in groups api action"); - - $this->auth_user = $apidata['user']; - $group = $this->get_group($apidata['api_arg'], $apidata); - - if (empty($group)) { - $this->clientError('Not Found', 404, $apidata['content-type']); - return; - } - - $sitename = common_config('site', 'name'); - $title = sprintf(_("%s timeline"), $group->nickname); - $taguribase = common_config('integration', 'taguri'); - $id = "tag:$taguribase:GroupTimeline:".$group->id; - $link = common_local_url('showgroup', - array('nickname' => $group->nickname)); - $subtitle = sprintf(_('Updates from %1$s on %2$s!'), - $group->nickname, $sitename); - - $page = (int)$this->arg('page', 1); - $count = (int)$this->arg('count', 20); - $max_id = (int)$this->arg('max_id', 0); - $since_id = (int)$this->arg('since_id', 0); - $since = $this->arg('since'); - - $notice = $group->getNotices(($page-1)*$count, - $count, $since_id, $max_id, $since); - - switch($apidata['content-type']) { - case 'xml': - $this->show_xml_timeline($notice); - break; - case 'rss': - $this->show_rss_timeline($notice, $title, $link, $subtitle); - break; - case 'atom': - if (isset($apidata['api_arg'])) { - $selfuri = common_root_url() . - 'api/statusnet/groups/timeline/' . - $apidata['api_arg'] . '.atom'; - } else { - $selfuri = common_root_url() . - 'api/statusnet/groups/timeline.atom'; - } - $this->show_atom_timeline($notice, $title, $id, $link, - $subtitle, null, $selfuri); - break; - case 'json': - $this->show_json_timeline($notice); - break; - default: - $this->clientError(_('API method not found!'), $code = 404); - } - } - - function membership($args, $apidata) - { - parent::handle($args); - - common_debug("in groups api action"); - - $this->auth_user = $apidata['user']; - $group = $this->get_group($apidata['api_arg'], $apidata); - - if (empty($group)) { - $this->clientError('Not Found', 404, $apidata['content-type']); - return; - } - - $sitename = common_config('site', 'name'); - $title = sprintf(_("Members of %s group"), $group->nickname); - $taguribase = common_config('integration', 'taguri'); - $id = "tag:$taguribase:GroupMembership:".$group->id; - $link = common_local_url('showgroup', - array('nickname' => $group->nickname)); - $subtitle = sprintf(_('Members of %1$s on %2$s'), - $group->nickname, $sitename); - - $page = (int)$this->arg('page', 1); - $count = (int)$this->arg('count', 20); - $max_id = (int)$this->arg('max_id', 0); - $since_id = (int)$this->arg('since_id', 0); - $since = $this->arg('since'); - - $member = $group->getMembers(($page-1)*$count, - $count, $since_id, $max_id, $since); - - switch($apidata['content-type']) { - case 'xml': - $this->show_twitter_xml_users($member); - break; - //TODO implement the RSS and ATOM content types - /*case 'rss': - $this->show_rss_users($member, $title, $link, $subtitle); - break;*/ - /*case 'atom': - if (isset($apidata['api_arg'])) { - $selfuri = common_root_url() . - 'api/statusnet/groups/membership/' . - $apidata['api_arg'] . '.atom'; - } else { - $selfuri = common_root_url() . - 'api/statusnet/groups/membership.atom'; - } - $this->show_atom_users($member, $title, $id, $link, - $subtitle, null, $selfuri); - break;*/ - case 'json': - $this->show_json_users($member); - break; - default: - $this->clientError(_('API method not found!'), $code = 404); - } - } - - function is_member($args, $apidata) - { - parent::handle($args); - - common_debug("in groups api action"); - - $this->auth_user = $apidata['user']; - $group = User_group::staticGet($args['group_id']); - if(! $group){ - $this->clientError(_('Group not found'), $code = 500); - } - $user = User::staticGet('id', $args['user_id']); - if(! $user){ - $this->clientError(_('User not found'), $code = 500); - } - - $is_member=$user->isMember($group); - - switch($apidata['content-type']) { - case 'xml': - $this->init_document('xml'); - $this->element('is_member', null, $is_member); - $this->end_document('xml'); - break; - case 'json': - $this->init_document('json'); - $this->show_json_objects(array('is_member'=>$is_member)); - $this->end_document('json'); - break; - default: - $this->clientError(_('API method not found!'), $code = 404); - } - } -} diff --git a/actions/twitapihelp.php b/actions/twitapihelp.php deleted file mode 100644 index 81381620e..000000000 --- a/actions/twitapihelp.php +++ /dev/null @@ -1,57 +0,0 @@ -<?php -/* - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once(INSTALLDIR.'/lib/twitterapi.php'); - -class TwitapihelpAction extends TwitterapiAction -{ - - /* Returns the string "ok" in the requested format with a 200 OK HTTP status code. - * URL:http://identi.ca/api/help/test.format - * Formats: xml, json - */ - function test($args, $apidata) - { - parent::handle($args); - - if ($apidata['content-type'] == 'xml') { - $this->init_document('xml'); - $this->element('ok', null, 'true'); - $this->end_document('xml'); - } elseif ($apidata['content-type'] == 'json') { - $this->init_document('json'); - print '"ok"'; - $this->end_document('json'); - } else { - $this->clientError(_('API method not found!'), $code=404); - } - - } - - function downtime_schedule($args, $apidata) - { - parent::handle($args); - $this->serverError(_('API method under construction.'), $code=501); - } - -}
\ No newline at end of file diff --git a/actions/twitapisearchatom.php b/actions/twitapisearchatom.php index 2f587d604..7d618c471 100644 --- a/actions/twitapisearchatom.php +++ b/actions/twitapisearchatom.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/twitterapi.php'; +require_once INSTALLDIR.'/lib/api.php'; /** * Action for outputting search results in Twitter compatible Atom @@ -46,10 +46,10 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ * - * @see TwitterapiAction + * @see ApiAction */ -class TwitapisearchatomAction extends TwitterapiAction +class TwitapisearchatomAction extends ApiAction { var $cnt; @@ -340,7 +340,7 @@ class TwitapisearchatomAction extends TwitterapiAction // TODO: Here is where we'd put in a link to an atom feed for threads $this->element("twitter:source", null, - htmlentities($this->source_link($notice->source))); + htmlentities($this->sourceLink($notice->source))); $this->elementStart('author'); diff --git a/actions/twitapisearchjson.php b/actions/twitapisearchjson.php index c628ee624..c7fa741a0 100644 --- a/actions/twitapisearchjson.php +++ b/actions/twitapisearchjson.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/twitterapi.php'; +require_once INSTALLDIR.'/lib/api.php'; require_once INSTALLDIR.'/lib/jsonsearchresultslist.php'; /** @@ -42,10 +42,10 @@ require_once INSTALLDIR.'/lib/jsonsearchresultslist.php'; * @author Zach Copley <zach@status.net> * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ - * @see TwitterapiAction + * @see ApiAction */ -class TwitapisearchjsonAction extends TwitterapiAction +class TwitapisearchjsonAction extends ApiAction { var $query; var $lang; @@ -134,9 +134,9 @@ class TwitapisearchjsonAction extends TwitterapiAction $results = new JSONSearchResultsList($notice, $q, $this->rpp, $this->page); - $this->init_document('json'); + $this->initDocument('json'); $results->show(); - $this->end_document('json'); + $this->endDocument('json'); } /** diff --git a/actions/twitapistatuses.php b/actions/twitapistatuses.php deleted file mode 100644 index ad6654dff..000000000 --- a/actions/twitapistatuses.php +++ /dev/null @@ -1,606 +0,0 @@ -<?php -/* - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once(INSTALLDIR.'/lib/twitterapi.php'); - -class TwitapistatusesAction extends TwitterapiAction -{ - - function public_timeline($args, $apidata) - { - // XXX: To really live up to the spec we need to build a list - // of notices by users who have custom avatars, so fix this SQL -- Zach - - parent::handle($args); - - $sitename = common_config('site', 'name'); - $title = sprintf(_("%s public timeline"), $sitename); - $taguribase = common_config('integration', 'taguri'); - $id = "tag:$taguribase:PublicTimeline"; - $link = common_root_url(); - $subtitle = sprintf(_("%s updates from everyone!"), $sitename); - - $page = (int)$this->arg('page', 1); - $count = (int)$this->arg('count', 20); - $max_id = (int)$this->arg('max_id', 0); - $since_id = (int)$this->arg('since_id', 0); - $since = $this->arg('since'); - - $notice = Notice::publicStream(($page-1)*$count, $count, $since_id, - $max_id, $since); - - switch($apidata['content-type']) { - case 'xml': - $this->show_xml_timeline($notice); - break; - case 'rss': - $this->show_rss_timeline($notice, $title, $link, $subtitle); - break; - case 'atom': - $selfuri = common_root_url() . 'api/statuses/public_timeline.atom'; - $this->show_atom_timeline($notice, $title, $id, $link, - $subtitle, null, $selfuri); - break; - case 'json': - $this->show_json_timeline($notice); - break; - default: - $this->clientError(_('API method not found!'), $code = 404); - break; - } - - } - - function friends_timeline($args, $apidata) - { - parent::handle($args); - - $this->auth_user = $apidata['user']; - $user = $this->get_user($apidata['api_arg'], $apidata); - - if (empty($user)) { - $this->clientError(_('No such user!'), 404, - $apidata['content-type']); - return; - } - - $profile = $user->getProfile(); - $sitename = common_config('site', 'name'); - $title = sprintf(_("%s and friends"), $user->nickname); - $taguribase = common_config('integration', 'taguri'); - $id = "tag:$taguribase:FriendsTimeline:" . $user->id; - $link = common_local_url('all', - array('nickname' => $user->nickname)); - $subtitle = sprintf(_('Updates from %1$s and friends on %2$s!'), - $user->nickname, $sitename); - - $page = (int)$this->arg('page', 1); - $count = (int)$this->arg('count', 20); - $max_id = (int)$this->arg('max_id', 0); - $since_id = (int)$this->arg('since_id', 0); - $since = $this->arg('since'); - - if (!empty($this->auth_user) && $this->auth_user->id == $user->id) { - $notice = $user->noticeInbox(($page-1)*$count, - $count, $since_id, $max_id, $since); - } else { - $notice = $user->noticesWithFriends(($page-1)*$count, - $count, $since_id, $max_id, $since); - } - - switch($apidata['content-type']) { - case 'xml': - $this->show_xml_timeline($notice); - break; - case 'rss': - $this->show_rss_timeline($notice, $title, $link, $subtitle); - break; - case 'atom': - if (isset($apidata['api_arg'])) { - $selfuri = common_root_url() . - 'api/statuses/friends_timeline/' . - $apidata['api_arg'] . '.atom'; - } else { - $selfuri = common_root_url() . - 'api/statuses/friends_timeline.atom'; - } - $this->show_atom_timeline($notice, $title, $id, $link, - $subtitle, null, $selfuri); - break; - case 'json': - $this->show_json_timeline($notice); - break; - default: - $this->clientError(_('API method not found!'), $code = 404); - } - - } - - function user_timeline($args, $apidata) - { - parent::handle($args); - - $this->auth_user = $apidata['user']; - $user = $this->get_user($apidata['api_arg'], $apidata); - - if (empty($user)) { - $this->clientError('Not Found', 404, $apidata['content-type']); - return; - } - - $profile = $user->getProfile(); - - $sitename = common_config('site', 'name'); - $title = sprintf(_("%s timeline"), $user->nickname); - $taguribase = common_config('integration', 'taguri'); - $id = "tag:$taguribase:UserTimeline:".$user->id; - $link = common_local_url('showstream', - array('nickname' => $user->nickname)); - $subtitle = sprintf(_('Updates from %1$s on %2$s!'), - $user->nickname, $sitename); - - # FriendFeed's SUP protocol - # Also added RSS and Atom feeds - - $suplink = common_local_url('sup', null, null, $user->id); - header('X-SUP-ID: '.$suplink); - - $page = (int)$this->arg('page', 1); - $count = (int)$this->arg('count', 20); - $max_id = (int)$this->arg('max_id', 0); - $since_id = (int)$this->arg('since_id', 0); - $since = $this->arg('since'); - - $notice = $user->getNotices(($page-1)*$count, - $count, $since_id, $max_id, $since); - - switch($apidata['content-type']) { - case 'xml': - $this->show_xml_timeline($notice); - break; - case 'rss': - $this->show_rss_timeline($notice, $title, $link, - $subtitle, $suplink); - break; - case 'atom': - if (isset($apidata['api_arg'])) { - $selfuri = common_root_url() . - 'api/statuses/user_timeline/' . - $apidata['api_arg'] . '.atom'; - } else { - $selfuri = common_root_url() . - 'api/statuses/user_timeline.atom'; - } - $this->show_atom_timeline($notice, $title, $id, $link, - $subtitle, $suplink, $selfuri); - break; - case 'json': - $this->show_json_timeline($notice); - break; - default: - $this->clientError(_('API method not found!'), $code = 404); - } - - } - - function update($args, $apidata) - { - parent::handle($args); - - if (!in_array($apidata['content-type'], array('xml', 'json'))) { - $this->clientError(_('API method not found!'), $code = 404); - return; - } - - if ($_SERVER['REQUEST_METHOD'] != 'POST') { - $this->clientError(_('This method requires a POST.'), - 400, $apidata['content-type']); - return; - } - - $user = $apidata['user']; // Always the auth user - - $status = $this->trimmed('status'); - $source = $this->trimmed('source'); - $in_reply_to_status_id = - intval($this->trimmed('in_reply_to_status_id')); - $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api'); - - if (empty($source) || in_array($source, $reserved_sources)) { - $source = 'api'; - } - - if (empty($status)) { - - // XXX: Note: In this case, Twitter simply returns '200 OK' - // No error is given, but the status is not posted to the - // user's timeline. Seems bad. Shouldn't we throw an - // errror? -- Zach - return; - - } else { - - $status_shortened = common_shorten_links($status); - - if (Notice::contentTooLong($status_shortened)) { - - // XXX: Twitter truncates anything over 140, flags the status - // as "truncated." Sending this error may screw up some clients - // that assume Twitter will truncate for them. Should we just - // truncate too? -- Zach - $this->clientError(sprintf(_('That\'s too long. Max notice size is %d chars.'), - Notice::maxContent()), - $code = 406, $apidata['content-type']); - return; - } - } - - // Check for commands - $inter = new CommandInterpreter(); - $cmd = $inter->handle_command($user, $status_shortened); - - if ($cmd) { - - if ($this->supported($cmd)) { - $cmd->execute(new Channel()); - } - - // cmd not supported? Twitter just returns your latest status. - // And, it returns your last status whether the cmd was successful - // or not! - $n = $user->getCurrentNotice(); - $apidata['api_arg'] = $n->id; - } else { - - $reply_to = null; - - if ($in_reply_to_status_id) { - - // check whether notice actually exists - $reply = Notice::staticGet($in_reply_to_status_id); - - if ($reply) { - $reply_to = $in_reply_to_status_id; - } else { - $this->clientError(_('Not found'), $code = 404, - $apidata['content-type']); - return; - } - } - - $notice = Notice::saveNew($user->id, - html_entity_decode($status, ENT_NOQUOTES, 'UTF-8'), - $source, 1, $reply_to); - - if (is_string($notice)) { - $this->serverError($notice); - return; - } - - common_broadcast_notice($notice); - $apidata['api_arg'] = $notice->id; - } - - $this->show($args, $apidata); - } - - function mentions($args, $apidata) - { - parent::handle($args); - - $user = $this->get_user($apidata['api_arg'], $apidata); - $this->auth_user = $apidata['user']; - - if (empty($user)) { - $this->clientError(_('No such user!'), 404, - $apidata['content-type']); - return; - } - - $profile = $user->getProfile(); - - $sitename = common_config('site', 'name'); - $title = sprintf(_('%1$s / Updates mentioning %2$s'), - $sitename, $user->nickname); - $taguribase = common_config('integration', 'taguri'); - $id = "tag:$taguribase:Mentions:".$user->id; - $link = common_local_url('replies', - array('nickname' => $user->nickname)); - $subtitle = sprintf(_('%1$s updates that reply to updates from %2$s / %3$s.'), - $sitename, $user->nickname, $profile->getBestName()); - - $page = (int)$this->arg('page', 1); - $count = (int)$this->arg('count', 20); - $max_id = (int)$this->arg('max_id', 0); - $since_id = (int)$this->arg('since_id', 0); - $since = $this->arg('since'); - - $notice = $user->getReplies(($page-1)*$count, - $count, $since_id, $max_id, $since); - - switch($apidata['content-type']) { - case 'xml': - $this->show_xml_timeline($notice); - break; - case 'rss': - $this->show_rss_timeline($notice, $title, $link, $subtitle); - break; - case 'atom': - $selfuri = common_root_url() . - ltrim($_SERVER['QUERY_STRING'], 'p='); - $this->show_atom_timeline($notice, $title, $id, $link, $subtitle, - null, $selfuri); - break; - case 'json': - $this->show_json_timeline($notice); - break; - default: - $this->clientError(_('API method not found!'), $code = 404); - } - - } - - function replies($args, $apidata) - { - call_user_func(array($this, 'mentions'), $args, $apidata); - } - - function show($args, $apidata) - { - parent::handle($args); - - if (!in_array($apidata['content-type'], array('xml', 'json'))) { - $this->clientError(_('API method not found!'), $code = 404); - return; - } - - // 'id' is an undocumented parameter in Twitter's API. Several - // clients make use of it, so we support it too. - - // show.json?id=12345 takes precedence over /show/12345.json - - $this->auth_user = $apidata['user']; - $notice_id = $this->trimmed('id'); - - if (empty($notice_id)) { - $notice_id = $apidata['api_arg']; - } - - $notice = Notice::staticGet((int)$notice_id); - - if ($notice) { - if ($apidata['content-type'] == 'xml') { - $this->show_single_xml_status($notice); - } elseif ($apidata['content-type'] == 'json') { - $this->show_single_json_status($notice); - } - } else { - // XXX: Twitter just sets a 404 header and doens't bother - // to return an err msg - $deleted = Deleted_notice::staticGet($notice_id); - if (!empty($deleted)) { - $this->clientError(_('Status deleted.'), - 410, $apidata['content-type']); - } else { - $this->clientError(_('No status with that ID found.'), - 404, $apidata['content-type']); - } - } - } - - function destroy($args, $apidata) - { - parent::handle($args); - - if (!in_array($apidata['content-type'], array('xml', 'json'))) { - $this->clientError(_('API method not found!'), $code = 404); - return; - } - - // Check for RESTfulness - if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) { - // XXX: Twitter just prints the err msg, no XML / JSON. - $this->clientError(_('This method requires a POST or DELETE.'), - 400, $apidata['content-type']); - return; - } - - $user = $apidata['user']; // Always the auth user - $notice_id = $apidata['api_arg']; - $notice = Notice::staticGet($notice_id); - - if (empty($notice)) { - $this->clientError(_('No status found with that ID.'), - 404, $apidata['content-type']); - return; - } - - if ($user->id == $notice->profile_id) { - $replies = new Reply; - $replies->get('notice_id', $notice_id); - $replies->delete(); - $notice->delete(); - - if ($apidata['content-type'] == 'xml') { - $this->show_single_xml_status($notice); - } elseif ($apidata['content-type'] == 'json') { - $this->show_single_json_status($notice); - } - } else { - $this->clientError(_('You may not delete another user\'s status.'), - 403, $apidata['content-type']); - } - - } - - function friends($args, $apidata) - { - parent::handle($args); - $includeStatuses=! (boolean) $args['lite']; - return $this->subscriptions($apidata, 'subscribed', 'subscriber', false, $includeStatuses); - } - - function friendsIDs($args, $apidata) - { - parent::handle($args); - return $this->subscriptions($apidata, 'subscribed', 'subscriber', true); - } - - function followers($args, $apidata) - { - parent::handle($args); - $includeStatuses=! (boolean) $args['lite']; - return $this->subscriptions($apidata, 'subscriber', 'subscribed', false, $includeStatuses); - } - - function followersIDs($args, $apidata) - { - parent::handle($args); - return $this->subscriptions($apidata, 'subscriber', 'subscribed', true); - } - - function subscriptions($apidata, $other_attr, $user_attr, $onlyIDs=false, $includeStatuses=true) - { - $this->auth_user = $apidata['user']; - $user = $this->get_user($apidata['api_arg'], $apidata); - - if (empty($user)) { - $this->clientError('Not Found', 404, $apidata['content-type']); - return; - } - - $profile = $user->getProfile(); - - $sub = new Subscription(); - $sub->$user_attr = $profile->id; - - $sub->orderBy('created DESC'); - - // Normally, page 100 friends at a time - - if (!$onlyIDs) { - $page = $this->arg('page', 1); - $count = $this->arg('count', 100); - $sub->limit(($page-1)*$count, $count); - } else { - - // If we're just looking at IDs, return - // ALL of them, unless the user specifies a page, - // in which case, return 500 per page. - - $page = $this->arg('page'); - if (!empty($page)) { - if ($page < 1) { - $page = 1; - } - $count = 500; - $sub->limit(($page-1)*$count, $count); - } - } - - $others = array(); - - if ($sub->find()) { - while ($sub->fetch()) { - $others[] = Profile::staticGet($sub->$other_attr); - } - } else { - // user has no followers - } - - $type = $apidata['content-type']; - - $this->init_document($type); - - if ($onlyIDs) { - $this->showIDs($others, $type); - } else { - $this->show_profiles($others, $type, $includeStatuses); - } - - $this->end_document($type); - } - - function show_profiles($profiles, $type, $includeStatuses) - { - switch ($type) { - case 'xml': - $this->elementStart('users', array('type' => 'array')); - foreach ($profiles as $profile) { - $this->show_profile($profile,$type,null,$includeStatuses); - } - $this->elementEnd('users'); - break; - case 'json': - $arrays = array(); - foreach ($profiles as $profile) { - $arrays[] = $this->twitter_user_array($profile, $includeStatuses); - } - print json_encode($arrays); - break; - default: - $this->clientError(_('unsupported file type')); - } - } - - function showIDs($profiles, $type) - { - switch ($type) { - case 'xml': - $this->elementStart('ids'); - foreach ($profiles as $profile) { - $this->element('id', null, $profile->id); - } - $this->elementEnd('ids'); - break; - case 'json': - $ids = array(); - foreach ($profiles as $profile) { - $ids[] = (int)$profile->id; - } - print json_encode($ids); - break; - default: - $this->clientError(_('unsupported file type')); - } - } - - function featured($args, $apidata) - { - parent::handle($args); - $this->serverError(_('API method under construction.'), $code=501); - } - - function supported($cmd) - { - $cmdlist = array('MessageCommand', 'SubCommand', 'UnsubCommand', - 'FavCommand', 'OnCommand', 'OffCommand'); - - if (in_array(get_class($cmd), $cmdlist)) { - return true; - } - - return false; - } - -} diff --git a/actions/twitapistatusnet.php b/actions/twitapistatusnet.php deleted file mode 100644 index 490f11dce..000000000 --- a/actions/twitapistatusnet.php +++ /dev/null @@ -1,175 +0,0 @@ -<?php -/** - * StatusNet, the distributed open-source microblogging tool - * - * StatusNet-only extensions to the Twitter-like API - * - * PHP version 5 - * - * LICENCE: 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 <http://www.gnu.org/licenses/>. - * - * @category Twitter - * @package StatusNet - * @author Evan Prodromou <evan@status.net> - * @copyright 2008 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR.'/lib/twitterapi.php'; - -/** - * StatusNet-specific API methods - * - * This class handles all /statusnet/ API methods. - * - * @category Twitter - * @package StatusNet - * @author Evan Prodromou <evan@status.net> - * @copyright 2008 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/ - */ - -class TwitapistatusnetAction extends TwitterapiAction -{ - /** - * A version stamp for the API - * - * Returns a version number for this version of StatusNet, which - * should make things a bit easier for upgrades. - * URL: http://identi.ca/api/statusnet/version.(xml|json) - * Formats: xml, json - * - * @param array $args Web arguments - * @param array $apidata Twitter API data - * - * @return void - * - * @see ApiAction::process_command() - */ - - function version($args, $apidata) - { - parent::handle($args); - switch ($apidata['content-type']) { - case 'xml': - $this->init_document('xml'); - $this->element('version', null, STATUSNET_VERSION); - $this->end_document('xml'); - break; - case 'json': - $this->init_document('json'); - print '"'.STATUSNET_VERSION.'"'; - $this->end_document('json'); - break; - default: - $this->clientError(_('API method not found!'), $code=404); - } - } - - /** - * Dump of configuration variables - * - * Gives a full dump of configuration variables for this instance - * of StatusNet, minus variables that may be security-sensitive (like - * passwords). - * URL: http://identi.ca/api/statusnet/config.(xml|json) - * Formats: xml, json - * - * @param array $args Web arguments - * @param array $apidata Twitter API data - * - * @return void - * - * @see ApiAction::process_command() - */ - - function config($args, $apidata) - { - static $keys = array('site' => array('name', 'server', 'theme', 'path', 'fancy', 'language', - 'email', 'broughtby', 'broughtbyurl', 'closed', - 'inviteonly', 'private'), - 'license' => array('url', 'title', 'image'), - 'nickname' => array('featured'), - 'throttle' => array('enabled', 'count', 'timespan'), - 'xmpp' => array('enabled', 'server', 'user')); - - parent::handle($args); - - switch ($apidata['content-type']) { - case 'xml': - $this->init_document('xml'); - $this->elementStart('config'); - // XXX: check that all sections and settings are legal XML elements - foreach ($keys as $section => $settings) { - $this->elementStart($section); - foreach ($settings as $setting) { - $value = common_config($section, $setting); - if (is_array($value)) { - $value = implode(',', $value); - } else if ($value === false) { - $value = 'false'; - } else if ($value === true) { - $value = 'true'; - } - $this->element($setting, null, $value); - } - $this->elementEnd($section); - } - $this->elementEnd('config'); - $this->end_document('xml'); - break; - case 'json': - $result = array(); - foreach ($keys as $section => $settings) { - $result[$section] = array(); - foreach ($settings as $setting) { - $result[$section][$setting] = common_config($section, $setting); - } - } - $this->init_document('json'); - $this->show_json_objects($result); - $this->end_document('json'); - break; - default: - $this->clientError(_('API method not found!'), $code=404); - } - } - - /** - * WADL description of the API - * - * Gives a WADL description of the API provided by this version of the - * software. - * - * @param array $args Web arguments - * @param array $apidata Twitter API data - * - * @return void - * - * @see ApiAction::process_command() - */ - - function wadl($args, $apidata) - { - parent::handle($args); - $this->serverError(_('API method under construction.'), 501); - } - -} diff --git a/actions/twitapitags.php b/actions/twitapitags.php deleted file mode 100644 index 0bcc55d37..000000000 --- a/actions/twitapitags.php +++ /dev/null @@ -1,113 +0,0 @@ -<?php -/** - * StatusNet, the distributed open-source microblogging tool - * - * StatusNet extensions to the Twitter-like API for groups - * - * PHP version 5 - * - * LICENCE: 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 <http://www.gnu.org/licenses/>. - * - * @category Twitter - * @package StatusNet - * @author Craig Andrews <candrews@integralblue.com> - * @author Zach Copley <zach@status.net> - * @copyright 2009 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR.'/lib/twitterapi.php'; - -/** - * Group-specific API methods - * - * This class handles StatusNet group API methods. - * - * @category Twitter - * @package StatusNet - * @author Craig Andrews <candrews@integralblue.com> - * @author Zach Copley <zach@status.net> - * @copyright 2009 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - - class TwitapitagsAction extends TwitterapiAction - { - - function timeline($args, $apidata) - { - parent::handle($args); - - common_debug("in tags api action"); - - $this->auth_user = $apidata['user']; - $tag = $apidata['api_arg']; - - if (empty($tag)) { - $this->clientError('Not Found', 404, $apidata['content-type']); - return; - } - - $sitename = common_config('site', 'name'); - $title = sprintf(_("Notices tagged with %s"), $tag); - $taguribase = common_config('integration', 'taguri'); - $id = "tag:$taguribase:TagTimeline:".$tag; - $link = common_local_url('tag', - array('tag' => $tag)); - $subtitle = sprintf(_('Updates tagged with %1$s on %2$s!'), - $tag, $sitename); - - $page = (int)$this->arg('page', 1); - $count = (int)$this->arg('count', 20); - $max_id = (int)$this->arg('max_id', 0); - $since_id = (int)$this->arg('since_id', 0); - $since = $this->arg('since'); - - # XXX: support max_id, since_id, and since arguments - $notice = Notice_tag::getStream($tag, ($page-1)*$count, $count + 1); - - switch($apidata['content-type']) { - case 'xml': - $this->show_xml_timeline($notice); - break; - case 'rss': - $this->show_rss_timeline($notice, $title, $link, $subtitle); - break; - case 'atom': - if (isset($apidata['api_arg'])) { - $selfuri = common_root_url() . - 'api/statusnet/tags/timeline/' . - $apidata['api_arg'] . '.atom'; - } else { - $selfuri = common_root_url() . - 'api/statusnet/tags/timeline.atom'; - } - $this->show_atom_timeline($notice, $title, $id, $link, - $subtitle, null, $selfuri); - break; - case 'json': - $this->show_json_timeline($notice); - break; - default: - $this->clientError(_('API method not found!'), $code = 404); - } - } - -} diff --git a/actions/twitapitrends.php b/actions/twitapitrends.php index 83ab28f35..779405e6d 100644 --- a/actions/twitapitrends.php +++ b/actions/twitapitrends.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/twitterapi.php'; +require_once INSTALLDIR.'/lib/api.php'; /** * Returns the top ten queries that are currently trending @@ -42,10 +42,10 @@ require_once INSTALLDIR.'/lib/twitterapi.php'; * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ * - * @see TwitterapiAction + * @see ApiAction */ -class TwitapitrendsAction extends TwitterapiAction +class TwitapitrendsAction extends ApiAction { var $callback; diff --git a/actions/twitapiusers.php b/actions/twitapiusers.php deleted file mode 100644 index 703fa6754..000000000 --- a/actions/twitapiusers.php +++ /dev/null @@ -1,80 +0,0 @@ -<?php -/* - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once(INSTALLDIR.'/lib/twitterapi.php'); - -class TwitapiusersAction extends TwitterapiAction -{ - - function show($args, $apidata) - { - parent::handle($args); - - if (!in_array($apidata['content-type'], array('xml', 'json'))) { - $this->clientError(_('API method not found!'), $code = 404); - return; - } - - $user = null; - $email = $this->arg('email'); - - // XXX: email field deprecated in Twitter's API - - if ($email) { - $user = User::staticGet('email', $email); - } else { - $user = $this->get_user($apidata['api_arg'], $apidata); - } - - if (empty($user)) { - $this->clientError(_('Not found.'), 404, $apidata['content-type']); - return; - } - - $profile = $user->getProfile(); - - if (!$profile) { - common_server_error(_('User has no profile.')); - return; - } - - $twitter_user = $this->twitter_user_array($user->getProfile(), true); - - if ($apidata['content-type'] == 'xml') { - $this->init_document('xml'); - $this->show_twitter_xml_user($twitter_user); - $this->end_document('xml'); - } elseif ($apidata['content-type'] == 'json') { - $this->init_document('json'); - $this->show_json_objects($twitter_user); - $this->end_document('json'); - } else { - - // This is in case 'show' was called via /account/verify_credentials - // without a format (xml or json). - header('Content-Type: text/html; charset=utf-8'); - print 'Authorized'; - } - - } -} diff --git a/actions/updateprofile.php b/actions/updateprofile.php index d9cc7f7f3..3cec9523c 100644 --- a/actions/updateprofile.php +++ b/actions/updateprofile.php @@ -55,46 +55,13 @@ class UpdateprofileAction extends Action */ function prepare($argarray) { - $version = $req->get_parameter('omb_version'); - if ($version != OMB_VERSION_01) { - $this->clientError(_('Unsupported OMB version'), 400); - return false; - } - # First, check to see if listenee exists - $listenee = $req->get_parameter('omb_listenee'); - $remote = Remote_profile::staticGet('uri', $listenee); - if (!$remote) { - $this->clientError(_('Profile unknown'), 404); - return false; - } - # Second, check to see if they should be able to post updates! - # We see if there are any subscriptions to that remote user with - # the given token. - - $sub = new Subscription(); - $sub->subscribed = $remote->id; - $sub->token = $token->key; - if (!$sub->find(true)) { - $this->clientError(_('You did not send us that profile'), 403); - return false; - } - - $profile = Profile::staticGet('id', $remote->id); - if (!$profile) { - # This one is our fault - $this->serverError(_('Remote profile with no matching profile'), 500); - return false; - } - $nickname = $req->get_parameter('omb_listenee_nickname'); - if ($nickname && !Validate::string($nickname, array('min_length' => 1, - 'max_length' => 64, - 'format' => NICKNAME_FMT))) { - $this->clientError(_('Nickname must have only lowercase letters and numbers and no spaces.')); - return false; - } - $license = $req->get_parameter('omb_listenee_license'); - if ($license && !common_valid_http_url($license)) { - $this->clientError(sprintf(_("Invalid license URL '%s'"), $license)); + parent::prepare($argarray); + $license = $_POST['omb_listenee_license']; + $site_license = common_config('license', 'url'); + if (!common_compatible_license($license, $site_license)) { + $this->clientError(sprintf(_('Listenee stream license ‘%s’ is not '. + 'compatible with site license ‘%s’.'), + $license, $site_license)); return false; } return true; @@ -113,4 +80,4 @@ class UpdateprofileAction extends Action return; } } -}
\ No newline at end of file +} diff --git a/actions/userrss.php b/actions/userrss.php index fa6d588cd..19e610551 100644 --- a/actions/userrss.php +++ b/actions/userrss.php @@ -25,7 +25,6 @@ require_once(INSTALLDIR.'/lib/rssaction.php'); class UserrssAction extends Rss10Action { - var $user = null; var $tag = null; function prepare($args) @@ -39,6 +38,7 @@ class UserrssAction extends Rss10Action $this->clientError(_('No such user.')); return false; } else { + $this->notices = $this->getNotices($this->limit); return true; } } @@ -64,9 +64,8 @@ class UserrssAction extends Rss10Action function getNotices($limit=0) { - $user = $this->user; - + if (is_null($user)) { return null; } diff --git a/classes/File.php b/classes/File.php index 9758cf7f5..e04a9d525 100644 --- a/classes/File.php +++ b/classes/File.php @@ -94,7 +94,13 @@ class File extends Memcached_DataObject $file_redir = File_redirection::staticGet('url', $given_url); if (empty($file_redir)) { $redir_data = File_redirection::where($given_url); - $redir_url = $redir_data['url']; + if (is_array($redir_data)) { + $redir_url = $redir_data['url']; + } elseif (is_string($redir_data)) { + $redir_url = $redir_data; + } else { + throw new ServerException("Can't process url '$given_url'"); + } // TODO: max field length if ($redir_url === $given_url || strlen($redir_url) > 255) { $x = File::saveNew($redir_data, $given_url); diff --git a/classes/File_redirection.php b/classes/File_redirection.php index 76b18f672..79052bf7d 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -79,6 +79,9 @@ class File_redirection extends Memcached_DataObject } } + if(strpos($short_url,'://') === false){ + return $short_url; + } $curlh = File_redirection::_commonCurl($short_url, $redirs); // Don't include body in output curl_setopt($curlh, CURLOPT_NOBODY, true); diff --git a/classes/Notice.php b/classes/Notice.php index 2138e0561..ba2227c0a 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1,5 +1,5 @@ <?php -/* +/** * StatusNet - the distributed open-source microblogging tool * Copyright (C) 2008, 2009, StatusNet, Inc. * @@ -15,9 +15,26 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category Notices + * @package StatusNet + * @author Brenda Wallace <shiny@cpan.org> + * @author Christopher Vollick <psycotica0@gmail.com> + * @author CiaranG <ciaran@ciarang.com> + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@controlezvous.ca> + * @author Gina Haeussge <osd@foosel.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Mike Cochrane <mikec@mikenz.geek.nz> + * @author Robin Millette <millette@controlyourself.ca> + * @author Sarven Capadisli <csarven@controlyourself.ca> + * @author Tom Adams <tom@holizz.com> + * @license GNU Affero General Public License http://www.gnu.org/licenses/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} /** * Table Definition for notice @@ -153,30 +170,30 @@ class Notice extends Memcached_DataObject $final = common_shorten_links($content); if (Notice::contentTooLong($final)) { - common_log(LOG_INFO, 'Rejecting notice that is too long.'); - return _('Problem saving notice. Too long.'); + throw new ClientException(_('Problem saving notice. Too long.')); } if (!$profile) { - common_log(LOG_ERR, 'Problem saving notice. Unknown user.'); - return _('Problem saving notice. Unknown user.'); + throw new ClientException(_('Problem saving notice. Unknown user.')); } if (common_config('throttle', 'enabled') && !Notice::checkEditThrottle($profile_id)) { common_log(LOG_WARNING, 'Excessive posting by profile #' . $profile_id . '; throttled.'); - return _('Too many notices too fast; take a breather and post again in a few minutes.'); + throw new ClientException(_('Too many notices too fast; take a breather '. + 'and post again in a few minutes.')); } if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $final)) { common_log(LOG_WARNING, 'Dupe posting by profile #' . $profile_id . '; throttled.'); - return _('Too many duplicate messages too quickly; take a breather and post again in a few minutes.'); + throw new ClientException(_('Too many duplicate messages too quickly;'. + ' take a breather and post again in a few minutes.')); } - $banned = common_config('profile', 'banned'); + $banned = common_config('profile', 'banned'); if ( in_array($profile_id, $banned) || in_array($profile->nickname, $banned)) { common_log(LOG_WARNING, "Attempted post from banned user: $profile->nickname (user id = $profile_id)."); - return _('You are banned from posting notices on this site.'); + throw new ClientException(_('You are banned from posting notices on this site.')); } $notice = new Notice(); @@ -200,12 +217,12 @@ class Notice extends Memcached_DataObject $notice->created = common_sql_now(); } - $notice->content = $final; - $notice->rendered = common_render_content($final, $notice); - $notice->source = $source; - $notice->uri = $uri; + $notice->content = $final; + $notice->rendered = common_render_content($final, $notice); + $notice->source = $source; + $notice->uri = $uri; - $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final); + $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final); if (!empty($notice->reply_to)) { $reply = Notice::staticGet('id', $notice->reply_to); @@ -222,7 +239,7 @@ class Notice extends Memcached_DataObject if (!$id) { common_log_db_error($notice, 'INSERT', __FILE__); - return _('Problem saving notice.'); + throw new ServerException(_('Problem saving notice.')); } // Update ID-dependent columns: URI, conversation @@ -247,7 +264,7 @@ class Notice extends Memcached_DataObject if ($changed) { if (!$notice->update($orig)) { common_log_db_error($notice, 'UPDATE', __FILE__); - return _('Problem saving notice.'); + throw new ServerException(_('Problem saving notice.')); } } @@ -909,7 +926,8 @@ class Notice extends Memcached_DataObject $qry .= '('.$id.', '.$this->id.', '.$source.", '".$this->created. "') "; $cnt++; if (rand() % NOTICE_INBOX_SOFT_LIMIT == 0) { - Notice_inbox::gc($id); + // FIXME: Causes lag in replicated servers + // Notice_inbox::gc($id); } if ($cnt >= MAX_BOXCARS) { $inbox = new Notice_inbox(); diff --git a/classes/Profile.php b/classes/Profile.php index 7f0d12758..4a069ee84 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -476,4 +476,79 @@ class Profile extends Memcached_DataObject $biolimit = self::maxBio(); return ($biolimit > 0 && !empty($bio) && (mb_strlen($bio) > $biolimit)); } + + function delete() + { + $this->_deleteNotices(); + $this->_deleteSubscriptions(); + $this->_deleteMessages(); + $this->_deleteTags(); + $this->_deleteBlocks(); + + $related = array('Avatar', + 'Reply', + 'Group_member', + ); + + foreach ($related as $cls) { + $inst = new $cls(); + $inst->profile_id = $this->id; + $inst->delete(); + } + + parent::delete(); + } + + function _deleteNotices() + { + $notice = new Notice(); + $notice->profile_id = $this->id; + + if ($notice->find()) { + while ($notice->fetch()) { + $other = clone($notice); + $other->delete(); + } + } + } + + function _deleteSubscriptions() + { + $sub = new Subscription(); + $sub->subscriber = $this->id; + $sub->delete(); + + $subd = new Subscription(); + $subd->subscribed = $this->id; + $subd->delete(); + } + + function _deleteMessages() + { + $msg = new Message(); + $msg->from_profile = $this->id; + $msg->delete(); + + $msg = new Message(); + $msg->to_profile = $this->id; + $msg->delete(); + } + + function _deleteTags() + { + $tag = new Profile_tag(); + $tag->tagged = $this->id; + $tag->delete(); + } + + function _deleteBlocks() + { + $block = new Profile_block(); + $block->blocked = $this->id; + $block->delete(); + + $block = new Group_block(); + $block->blocked = $this->id; + $block->delete(); + } } diff --git a/classes/Session.php b/classes/Session.php index d641edbbe..79a69a96e 100644 --- a/classes/Session.php +++ b/classes/Session.php @@ -85,9 +85,18 @@ class Session extends Memcached_DataObject return $session->insert(); } else { - $session->session_data = $session_data; + if (strcmp($session->session_data, $session_data) == 0) { + self::logdeb("Not writing session '$id'; unchanged"); + return true; + } else { + self::logdeb("Session '$id' data changed; updating"); + + $orig = clone($session); + + $session->session_data = $session_data; - return $session->update(); + return $session->update($orig); + } } } diff --git a/classes/User.php b/classes/User.php index 11cb4f08b..48df0cdd7 100644 --- a/classes/User.php +++ b/classes/User.php @@ -117,11 +117,15 @@ class User extends Memcached_DataObject function allowed_nickname($nickname) { // XXX: should already be validated for size, content, etc. - static $blacklist = array('rss', 'xrds', 'doc', 'main', - 'settings', 'notice', 'user', - 'search', 'avatar', 'tag', 'tags', - 'api', 'message', 'group', 'groups', - 'local'); + + $blacklist = array(); + + //all directory and file names should be blacklisted + $d = dir(INSTALLDIR); + while (false !== ($entry = $d->read())) { + $blacklist[]=$entry; + } + $d->close(); $merged = array_merge($blacklist, common_config('nickname', 'blacklist')); return !in_array($nickname, $merged); } @@ -707,4 +711,77 @@ class User extends Memcached_DataObject return true; } + + /** + * Does this user have the right to do X? + * + * With our role-based authorization, this is merely a lookup for whether the user + * has a particular role. The implementation currently uses a switch statement + * to determine if the user has the pre-defined role to exercise the right. Future + * implementations may allow per-site roles, and different mappings of roles to rights. + * + * @param $right string Name of the right, usually a constant in class Right + * @return boolean whether the user has the right in question + */ + + function hasRight($right) + { + $result = false; + if (Event::handle('UserRightsCheck', array($this, $right, &$result))) { + switch ($right) + { + case Right::deleteOthersNotice: + $result = $this->hasRole('moderator'); + break; + default: + $result = false; + break; + } + } + return $result; + } + + function delete() + { + $profile = $this->getProfile(); + $profile->delete(); + + $related = array('Fave', + 'User_openid', + 'Confirm_address', + 'Remember_me', + 'Foreign_link', + 'Invitation', + ); + + if (common_config('inboxes', 'enabled')) { + $related[] = 'Notice_inbox'; + } + + foreach ($related as $cls) { + $inst = new $cls(); + $inst->user_id = $this->id; + $inst->delete(); + } + + $this->_deleteTags(); + $this->_deleteBlocks(); + + parent::delete(); + } + + function _deleteTags() + { + $tag = new Profile_tag(); + $tag->tagger = $this->id; + $tag->delete(); + } + + function _deleteBlocks() + { + $block = new Profile_block(); + $block->blocker = $this->id; + $block->delete(); + // XXX delete group block? Reset blocker? + } } diff --git a/db/sms_carrier.sql b/db/sms_carrier.sql index 055606f58..0e94df296 100644 --- a/db/sms_carrier.sql +++ b/db/sms_carrier.sql @@ -61,4 +61,5 @@ VALUES (100113, 'T-Mobile Germany', '%s@t-mobile-sms.de', now()), (100114, 'Vodafone Germany', '%s@vodafone-sms.de', now()), (100115, 'E-Plus', '%s@smsmail.eplus.de', now()), - (100116, 'Cellular South', '%s@csouth1.com', now()); + (100116, 'Cellular South', '%s@csouth1.com', now()), + (100117, 'ChinaMobile (139)', '%s@139.com', now()); diff --git a/db/statusnet.sql b/db/statusnet.sql index 221d60ce3..dfcddb643 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -195,18 +195,6 @@ create table nonce ( constraint primary key (consumer_key, ts, nonce) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; -/* One-to-many relationship of user to openid_url */ - -create table user_openid ( - canonical varchar(255) primary key comment 'Canonical true URL', - display varchar(255) not null unique key comment 'URL for viewing, may be different from canonical', - user_id integer not null comment 'user owning this URL' references user (id), - created datetime not null comment 'date this record was created', - modified timestamp comment 'date this record was modified', - - index user_openid_user_id_idx (user_id) -) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; - /* These are used by JanRain OpenID library */ create table oid_associations ( diff --git a/doc-src/bookmarklet b/doc-src/bookmarklet index 6cd2c08f9..e5ded7702 100644 --- a/doc-src/bookmarklet +++ b/doc-src/bookmarklet @@ -2,6 +2,4 @@ A bookmarklet is a small piece of javascript code used as a bookmark. This one w Drag-and-drop the following link to your bookmarks bar or right-click it and add it to your browser favorites to keep it handy. -<MTMarkdownOptions output='raw'> -<a href="javascript:var%20d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),f='http://%%site.server%%/%%site.path%%/index.php?action=newnotice',l=d.location,e=encodeURIComponent,g=f+'&status_textarea=%22'+((e(s))?e(s):e(document.title))+'%22 from '+l.href;function%20a(){if(!w.open(g,'t','toolbar=0,resizable=0,scrollbars=1,status=1,width=800,height=570')){l.href=g;}}a();void(0);">Post to %%site.name%%</a> -</MTMarkdownOptions> +<a href="javascript:var%20d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),f='http://%%site.server%%/%%site.path%%/index.php?action=newnotice',l=d.location,e=encodeURIComponent,g=f+'&status_textarea=%22'+((e(s))?e(s):e(document.title))+'%22 from '+l.href;function%20a(){if(!w.open(g,'t','toolbar=0,resizable=0,scrollbars=1,status=1,width=800,height=570')){l.href=g;}}a();void(0);">Post to %%site.name%%</a> diff --git a/doc-src/im b/doc-src/im index c722a4e2c..631f6d9bb 100644 --- a/doc-src/im +++ b/doc-src/im @@ -37,10 +37,10 @@ currently-implemented commands: * **help**: Show this help. List available Jabber/XMPP commands * **follow <nickname>**: Subscribe to <nickname> * **sub <nickname>**: Same as follow -* **leave <nickname>**: Subscribe to <nickname> +* **leave <nickname>**: Unsubscribe from <nickname> * **unsub <nickname>**: Same as leave * **d <nickname> <text>**: Send direct message to <nickname> with message body <text> * **get <nickname>**: Get last notice from <nickname> * **last <nickname>**: Same as 'get' * **whois <nickname>**: Get Profile info on <nickname> -* **fav <nickname>**: Add user's last notice as a favorite
\ No newline at end of file +* **fav <nickname>**: Add user's last notice as a favorite diff --git a/extlib/Auth/OpenID/BigMath.php b/extlib/Auth/OpenID/BigMath.php index 45104947d..b5fc627a0 100644 --- a/extlib/Auth/OpenID/BigMath.php +++ b/extlib/Auth/OpenID/BigMath.php @@ -376,7 +376,7 @@ function Auth_OpenID_detectMathLibrary($exts) // Try to load dynamic modules. if (!$loaded) { foreach ($extension['modules'] as $module) { - if (@dl($module . "." . PHP_SHLIB_SUFFIX)) { + if (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode') && @dl($module . "." . PHP_SHLIB_SUFFIX)) { $loaded = true; break; } diff --git a/extlib/Auth/Yadis/XML.php b/extlib/Auth/Yadis/XML.php index 4854f12bb..7232d6cbd 100644 --- a/extlib/Auth/Yadis/XML.php +++ b/extlib/Auth/Yadis/XML.php @@ -349,7 +349,7 @@ function &Auth_Yadis_getXMLParser() foreach ($extensions as $name => $params) { if (!extension_loaded($name)) { foreach ($params['libname'] as $libname) { - if (@dl($libname)) { + if (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode') && @dl($libname)) { $classname = $params['classname']; } } diff --git a/extlib/OAuth.php b/extlib/OAuth.php index fd4853554..648627b57 100644 --- a/extlib/OAuth.php +++ b/extlib/OAuth.php @@ -327,7 +327,7 @@ class OAuthRequest {/*{{{*/ public function get_normalized_http_url() {/*{{{*/ $parts = parse_url($this->http_url); - $port = @$parts['port']; + $port = isset($parts['port']) ? $parts['port'] : null; $scheme = $parts['scheme']; $host = $parts['host']; $path = @$parts['path']; diff --git a/extlib/PEAR.php b/extlib/PEAR.php index 4c24c6006..fcefa964a 100644 --- a/extlib/PEAR.php +++ b/extlib/PEAR.php @@ -746,7 +746,7 @@ class PEAR { if (!extension_loaded($ext)) { // if either returns true dl() will produce a FATAL error, stop that - if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) { + if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1) || !function_exists('dl')) { return false; } if (OS_WINDOWS) { @@ -18,18 +18,19 @@ * * @category StatusNet * @package StatusNet + * @author Brenda Wallace <shiny@cpan.org> + * @author Christopher Vollick <psycotica0@gmail.com> + * @author CiaranG <ciaran@ciarang.com> + * @author Craig Andrews <candrews@integralblue.com> + * @author Evan Prodromou <evan@controlezvous.ca> + * @author Gina Haeussge <osd@foosel.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Mike Cochrane <mikec@mikenz.geek.nz> + * @author Robin Millette <millette@controlyourself.ca> + * @author Sarven Capadisli <csarven@controlyourself.ca> + * @author Tom Adams <tom@holizz.com> + * * @license GNU Affero General Public License http://www.gnu.org/licenses/ - * @author Brenda Wallace <shiny@cpan.org> - * @author Christopher Vollick <psycotica0@gmail.com> - * @author CiaranG <ciaran@ciarang.com> - * @author Craig Andrews <candrews@integralblue.com> - * @author Evan Prodromou <evan@status.net> - * @author Gina Haeussge <osd@foosel.net> - * @author Jeffery To <jeffery.to@gmail.com> - * @author Mike Cochrane <mikec@mikenz.geek.nz> - * @author Robin Millette <millette@controlyourself.ca> - * @author Sarven Capadisli <csarven@status.net> - * @author Tom Adams <tom@holizz.com> */ define('INSTALLDIR', dirname(__FILE__)); @@ -48,12 +49,23 @@ function getPath($req) ) { return $req['p']; } else if (array_key_exists('PATH_INFO', $_SERVER)) { - return $_SERVER['PATH_INFO']; + $path = $_SERVER['PATH_INFO']; + $script = $_SERVER['SCRIPT_NAME']; + if (substr($path, 0, mb_strlen($script)) == $script) { + return substr($path, mb_strlen($script)); + } else { + return $path; + } } else { return null; } } +/** + * logs and then displays error messages + * + * @return void + */ function handleError($error) { if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { diff --git a/install.php b/install.php index 81241315e..69f025a6a 100644 --- a/install.php +++ b/install.php @@ -1,3 +1,4 @@ + <?php /** * StatusNet - the distributed open-source microblogging tool @@ -31,6 +32,8 @@ * @author Sarven Capadisli <csarven@status.net> * @author Tom Adams <tom@holizz.com> * @license GNU Affero General Public License http://www.gnu.org/licenses/ + * @version 0.9.x + * @link http://status.net */ define('INSTALLDIR', dirname(__FILE__)); @@ -242,7 +245,7 @@ function main() */ function haveExternalLibrary($external_library) { - if (isset($external_library['include']) && ! @include_once $external_library['include'] ) { + if (isset($external_library['include']) && !haveIncludeFile($external_library['include'])) { return false; } if (isset($external_library['check_function']) && ! function_exists($external_library['check_function'])) { @@ -254,6 +257,15 @@ function haveExternalLibrary($external_library) return true; } +// Attempt to include a PHP file and report if it worked, while +// suppressing the annoying warning messages on failure. +function haveIncludeFile($filename) { + $old = error_reporting(error_reporting() & ~E_WARNING); + $ok = include_once($filename); + error_reporting($old); + return $ok; +} + /** * Check if all is ready for installation * @@ -326,12 +338,19 @@ function checkPrereqs() */ function checkExtension($name) { - if (!extension_loaded($name)) { - if (!@dl($name.'.so')) { - return false; + if (extension_loaded($name)) { + return true; + } elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) { + // dl will throw a fatal error if it's disabled or we're in safe mode. + // More fun, it may not even exist under some SAPIs in 5.3.0 or later... + $soname = $name . '.' . PHP_SHLIB_SUFFIX; + if (PHP_SHLIB_SUFFIX == 'dll') { + $soname = "php_" . $soname; } + return @dl($soname); + } else { + return false; } - return true; } /** @@ -388,7 +407,7 @@ E_O_T; E_O_T; foreach ($present_libraries as $library) { echo '<li>'; - if ($library['url']) { + if (isset($library['url'])) { echo '<a href=">'.$library['url'].'">'.htmlentities($library['name']).'</a>'; } else { echo htmlentities($library['name']); @@ -476,12 +495,7 @@ E_O_T; function updateStatus($status, $error=false) { - echo '<li'; - - if ($error) { - echo ' class="error"'; - } - echo ">$status</li>"; + echo '<li' . ($error ? ' class="error"': '' ) . ">$status</li>"; } function handlePost() diff --git a/lib/action.php b/lib/action.php index 8056cb9ec..1b2f73752 100644 --- a/lib/action.php +++ b/lib/action.php @@ -120,16 +120,15 @@ class Action extends HTMLOutputter // lawsuit { // XXX: attributes (profile?) $this->elementStart('head'); - if (Event::handle('StartHeadChildren', array($this))) { + if (Event::handle('StartShowHeadElements', array($this))) { $this->showTitle(); $this->showShortcutIcon(); $this->showStylesheets(); - $this->showScripts(); $this->showOpenSearch(); $this->showFeeds(); $this->showDescription(); $this->extraHead(); - Event::handle('EndHeadChildren', array($this)); + Event::handle('EndShowHeadElements', array($this)); } $this->elementEnd('head'); } @@ -355,6 +354,7 @@ class Action extends HTMLOutputter // lawsuit Event::handle('EndShowFooter', array($this)); } $this->elementEnd('div'); + $this->showScripts(); $this->elementEnd('body'); } @@ -528,7 +528,10 @@ class Action extends HTMLOutputter // lawsuit $this->showContentBlock(); Event::handle('EndShowContentBlock', array($this)); } - $this->showAside(); + if (Event::handle('StartShowAside', array($this))) { + $this->showAside(); + Event::handle('EndShowAside', array($this)); + } $this->elementEnd('div'); } @@ -879,6 +882,7 @@ class Action extends HTMLOutputter // lawsuit */ function handle($argarray=null) { + header('Vary: Accept-Encoding,Cookie'); $lm = $this->lastModified(); $etag = $this->etag(); if ($etag) { diff --git a/lib/twitterapi.php b/lib/api.php index d199e4dee..7a63a4a78 100644 --- a/lib/twitterapi.php +++ b/lib/api.php @@ -1,9 +1,12 @@ <?php -/* - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. +/** + * StatusNet, the distributed open-source microblogging tool * - * This program is free software: you can redistribute it and/or modify + * Base API action + * + * PHP version 5 + * + * LICENCE: 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. @@ -15,17 +18,49 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Toby Inkster <mail@tobyinkster.co.uk> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { +if (!defined('STATUSNET')) { exit(1); } -class TwitterapiAction extends Action -{ - - var $auth_user; +/** + * Contains most of the Twitter-compatible API output functions. + * + * @category API + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author Jeffery To <jeffery.to@gmail.com> + * @author Toby Inkster <mail@tobyinkster.co.uk> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ +class ApiAction extends Action +{ + var $format = null; + var $user = null; + var $page = null; + var $count = null; + var $max_id = null; + var $since_id = null; + var $since = null; + /** * Initialization. * @@ -37,6 +72,14 @@ class TwitterapiAction extends Action function prepare($args) { parent::prepare($args); + + $this->format = $this->arg('format'); + $this->page = (int)$this->arg('page', 1); + $this->count = (int)$this->arg('count', 20); + $this->max_id = (int)$this->arg('max_id', 0); + $this->since_id = (int)$this->arg('since_id', 0); + $this->since = $this->arg('since'); + return true; } @@ -73,7 +116,7 @@ class TwitterapiAction extends Action return parent::element($tag, $attrs, $content); } - function twitter_user_array($profile, $get_notice=false) + function twitterUserArray($profile, $get_notice=false) { $twitter_user = array(); @@ -100,7 +143,7 @@ class TwitterapiAction extends Action $twitter_user['friends_count'] = $profile->subscriptionCount(); - $twitter_user['created_at'] = $this->date_twitter($profile->created); + $twitter_user['created_at'] = $this->dateTwitter($profile->created); $twitter_user['favourites_count'] = $profile->faveCount(); // British spelling! @@ -146,24 +189,24 @@ class TwitterapiAction extends Action $notice = $profile->getCurrentNotice(); if ($notice) { # don't get user! - $twitter_user['status'] = $this->twitter_status_array($notice, false); + $twitter_user['status'] = $this->twitterStatusArray($notice, false); } } return $twitter_user; } - function twitter_status_array($notice, $include_user=true) + function twitterStatusArray($notice, $include_user=true) { $profile = $notice->getProfile(); $twitter_status = array(); $twitter_status['text'] = $notice->content; $twitter_status['truncated'] = false; # Not possible on StatusNet - $twitter_status['created_at'] = $this->date_twitter($notice->created); + $twitter_status['created_at'] = $this->dateTwitter($notice->created); $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ? intval($notice->reply_to) : null; - $twitter_status['source'] = $this->source_link($notice->source); + $twitter_status['source'] = $this->sourceLink($notice->source); $twitter_status['id'] = intval($notice->id); $replier_profile = null; @@ -206,14 +249,14 @@ class TwitterapiAction extends Action if ($include_user) { # Don't get notice (recursive!) - $twitter_user = $this->twitter_user_array($profile, false); + $twitter_user = $this->twitterUserArray($profile, false); $twitter_status['user'] = $twitter_user; } return $twitter_status; } - function twitter_group_array($group) + function twitterGroupArray($group) { $twitter_group=array(); $twitter_group['id']=$group->id; @@ -228,12 +271,12 @@ class TwitterapiAction extends Action $twitter_group['homepage']=$group->homepage; $twitter_group['description']=$group->description; $twitter_group['location']=$group->location; - $twitter_group['created']=$this->date_twitter($group->created); - $twitter_group['modified']=$this->date_twitter($group->modified); + $twitter_group['created']=$this->dateTwitter($group->created); + $twitter_group['modified']=$this->dateTwitter($group->modified); return $twitter_group; } - function twitter_rss_group_array($group) + function twitterRssGroupArray($group) { $entry = array(); $entry['content']=$group->description; @@ -251,7 +294,7 @@ class TwitterapiAction extends Action return $entry; } - function twitter_rss_entry_array($notice) + function twitterRssEntryArray($notice) { $profile = $notice->getProfile(); $entry = array(); @@ -288,23 +331,6 @@ class TwitterapiAction extends Action $entry['enclosures'] = $enclosures; } -/* - // Enclosure - $attachments = $notice->attachments(); - if($attachments){ - $entry['enclosures']=array(); - foreach($attachments as $attachment){ - if ($attachment->isEnclosure()) { - $enclosure=array(); - $enclosure['url']=$attachment->url; - $enclosure['mimetype']=$attachment->mimetype; - $enclosure['size']=$attachment->size; - $entry['enclosures'][]=$enclosure; - } - } - } -*/ - // Tags/Categories $tag = new Notice_tag(); $tag->notice_id = $notice->id; @@ -324,65 +350,20 @@ class TwitterapiAction extends Action return $entry; } - function twitter_rss_dmsg_array($message) - { - - $entry = array(); - - $entry['title'] = sprintf('Message from %s to %s', - $message->getFrom()->nickname, $message->getTo()->nickname); - - $entry['content'] = common_xml_safe_str(trim($message->content)); - $entry['link'] = common_local_url('showmessage', array('message' => $message->id)); - $entry['published'] = common_date_iso8601($message->created); - - $taguribase = common_config('integration', 'taguri'); - - $entry['id'] = "tag:$taguribase,:$entry[link]"; - $entry['updated'] = $entry['published']; - $entry['author'] = $message->getFrom()->getBestName(); - - # RSS Item specific - $entry['description'] = $entry['content']; - $entry['pubDate'] = common_date_rfc2822($message->created); - $entry['guid'] = $entry['link']; - - return $entry; - } - - function twitter_dmsg_array($message) - { - $twitter_dm = array(); - - $from_profile = $message->getFrom(); - $to_profile = $message->getTo(); - - $twitter_dm['id'] = $message->id; - $twitter_dm['sender_id'] = $message->from_profile; - $twitter_dm['text'] = trim($message->content); - $twitter_dm['recipient_id'] = $message->to_profile; - $twitter_dm['created_at'] = $this->date_twitter($message->created); - $twitter_dm['sender_screen_name'] = $from_profile->nickname; - $twitter_dm['recipient_screen_name'] = $to_profile->nickname; - $twitter_dm['sender'] = $this->twitter_user_array($from_profile, false); - $twitter_dm['recipient'] = $this->twitter_user_array($to_profile, false); - - return $twitter_dm; - } - function twitter_relationship_array($source, $target) + function twitterRelationshipArray($source, $target) { $relationship = array(); $relationship['source'] = - $this->relationship_details_array($source, $target); + $this->relationshipDetailsArray($source, $target); $relationship['target'] = - $this->relationship_details_array($target, $source); + $this->relationshipDetailsArray($target, $source); return array('relationship' => $relationship); } - function relationship_details_array($source, $target) + function relationshipDetailsArray($source, $target) { $details = array(); @@ -409,14 +390,14 @@ class TwitterapiAction extends Action return $details; } - function show_twitter_xml_relationship($relationship) + function showTwitterXmlRelationship($relationship) { $this->elementStart('relationship'); foreach($relationship as $element => $value) { if ($element == 'source' || $element == 'target') { $this->elementStart($element); - $this->show_xml_relationship_details($value); + $this->showXmlRelationshipDetails($value); $this->elementEnd($element); } } @@ -424,26 +405,26 @@ class TwitterapiAction extends Action $this->elementEnd('relationship'); } - function show_xml_relationship_details($details) + function showXmlRelationshipDetails($details) { foreach($details as $element => $value) { $this->element($element, null, $value); } } - function show_twitter_xml_status($twitter_status) + function showTwitterXmlStatus($twitter_status) { $this->elementStart('status'); foreach($twitter_status as $element => $value) { switch ($element) { case 'user': - $this->show_twitter_xml_user($twitter_status['user']); + $this->showTwitterXmlUser($twitter_status['user']); break; case 'text': $this->element($element, null, common_xml_safe_str($value)); break; case 'attachments': - $this->show_xml_attachments($twitter_status['attachments']); + $this->showXmlAttachments($twitter_status['attachments']); break; default: $this->element($element, null, $value); @@ -452,7 +433,7 @@ class TwitterapiAction extends Action $this->elementEnd('status'); } - function show_twitter_xml_group($twitter_group) + function showTwitterXmlGroup($twitter_group) { $this->elementStart('group'); foreach($twitter_group as $element => $value) { @@ -461,12 +442,12 @@ class TwitterapiAction extends Action $this->elementEnd('group'); } - function show_twitter_xml_user($twitter_user, $role='user') + function showTwitterXmlUser($twitter_user, $role='user') { $this->elementStart($role); foreach($twitter_user as $element => $value) { if ($element == 'status') { - $this->show_twitter_xml_status($twitter_user['status']); + $this->showTwitterXmlStatus($twitter_user['status']); } else { $this->element($element, null, $value); } @@ -474,7 +455,7 @@ class TwitterapiAction extends Action $this->elementEnd($role); } - function show_xml_attachments($attachments) { + function showXmlAttachments($attachments) { if (!empty($attachments)) { $this->elementStart('attachments', array('type' => 'array')); foreach ($attachments as $attachment) { @@ -488,7 +469,7 @@ class TwitterapiAction extends Action } } - function show_twitter_rss_item($entry) + function showTwitterRssItem($entry) { $this->elementStart('item'); $this->element('title', null, $entry['title']); @@ -502,7 +483,7 @@ class TwitterapiAction extends Action $enclosure = $entry['enclosures'][0]; $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null); } - + if(array_key_exists('tags', $entry)){ foreach($entry['tags'] as $tag){ $this->element('category', null,$tag); @@ -512,93 +493,59 @@ class TwitterapiAction extends Action $this->elementEnd('item'); } - function show_json_objects($objects) + function showJsonObjects($objects) { print(json_encode($objects)); } - function show_single_xml_status($notice) + function showSingleXmlStatus($notice) { - $this->init_document('xml'); - $twitter_status = $this->twitter_status_array($notice); - $this->show_twitter_xml_status($twitter_status); - $this->end_document('xml'); + $this->initDocument('xml'); + $twitter_status = $this->twitterStatusArray($notice); + $this->showTwitterXmlStatus($twitter_status); + $this->endDocument('xml'); } function show_single_json_status($notice) { - $this->init_document('json'); - $status = $this->twitter_status_array($notice); - $this->show_json_objects($status); - $this->end_document('json'); - } - - function show_single_xml_dmsg($message) - { - $this->init_document('xml'); - $dmsg = $this->twitter_dmsg_array($message); - $this->show_twitter_xml_dmsg($dmsg); - $this->end_document('xml'); + $this->initDocument('json'); + $status = $this->twitterStatusArray($notice); + $this->showJsonObjects($status); + $this->endDocument('json'); } - function show_single_json_dmsg($message) - { - $this->init_document('json'); - $dmsg = $this->twitter_dmsg_array($message); - $this->show_json_objects($dmsg); - $this->end_document('json'); - } - - function show_twitter_xml_dmsg($twitter_dm) - { - $this->elementStart('direct_message'); - foreach($twitter_dm as $element => $value) { - switch ($element) { - case 'sender': - case 'recipient': - $this->show_twitter_xml_user($value, $element); - break; - case 'text': - $this->element($element, null, common_xml_safe_str($value)); - break; - default: - $this->element($element, null, $value); - } - } - $this->elementEnd('direct_message'); - } - function show_xml_timeline($notice) + function showXmlTimeline($notice) { - $this->init_document('xml'); + $this->initDocument('xml'); $this->elementStart('statuses', array('type' => 'array')); if (is_array($notice)) { foreach ($notice as $n) { - $twitter_status = $this->twitter_status_array($n); - $this->show_twitter_xml_status($twitter_status); + $twitter_status = $this->twitterStatusArray($n); + $this->showTwitterXmlStatus($twitter_status); } } else { while ($notice->fetch()) { - $twitter_status = $this->twitter_status_array($notice); - $this->show_twitter_xml_status($twitter_status); + $twitter_status = $this->twitterStatusArray($notice); + $this->showTwitterXmlStatus($twitter_status); } } $this->elementEnd('statuses'); - $this->end_document('xml'); + $this->endDocument('xml'); } - function show_rss_timeline($notice, $title, $link, $subtitle, $suplink=null) + function showRssTimeline($notice, $title, $link, $subtitle, $suplink=null) { - $this->init_document('rss'); + $this->initDocument('rss'); $this->element('title', null, $title); $this->element('link', null, $link); if (!is_null($suplink)) { - # For FriendFeed's SUP protocol + // For FriendFeed's SUP protocol $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom', 'rel' => 'http://api.friendfeed.com/2008/03#sup', 'href' => $suplink, @@ -610,23 +557,23 @@ class TwitterapiAction extends Action if (is_array($notice)) { foreach ($notice as $n) { - $entry = $this->twitter_rss_entry_array($n); - $this->show_twitter_rss_item($entry); + $entry = $this->twitterRssEntryArray($n); + $this->showTwitterRssItem($entry); } } else { while ($notice->fetch()) { - $entry = $this->twitter_rss_entry_array($notice); - $this->show_twitter_rss_item($entry); + $entry = $this->twitterRssEntryArray($notice); + $this->showTwitterRssItem($entry); } } - $this->end_twitter_rss(); + $this->endTwitterRss(); } - function show_atom_timeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null) + function showAtomTimeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null) { - $this->init_document('atom'); + $this->initDocument('atom'); $this->element('title', null, $title); $this->element('id', null, $id); @@ -657,14 +604,14 @@ class TwitterapiAction extends Action } } - $this->end_document('atom'); + $this->endDocument('atom'); } - function show_rss_groups($group, $title, $link, $subtitle) + function showRssGroups($group, $title, $link, $subtitle) { - $this->init_document('rss'); + $this->initDocument('rss'); $this->element('title', null, $title); $this->element('link', null, $link); @@ -674,23 +621,138 @@ class TwitterapiAction extends Action if (is_array($group)) { foreach ($group as $g) { - $twitter_group = $this->twitter_rss_group_array($g); - $this->show_twitter_rss_item($twitter_group); + $twitter_group = $this->twitterRssGroupArray($g); + $this->showTwitterRssItem($twitter_group); } } else { while ($group->fetch()) { - $twitter_group = $this->twitter_rss_group_array($group); - $this->show_twitter_rss_item($twitter_group); + $twitter_group = $this->twitterRssGroupArray($group); + $this->showTwitterRssItem($twitter_group); } } - $this->end_twitter_rss(); + $this->endTwitterRss(); } - function show_atom_groups($group, $title, $id, $link, $subtitle=null, $selfuri=null) + + function showTwitterAtomEntry($entry) { + $this->elementStart('entry'); + $this->element('title', null, $entry['title']); + $this->element('content', array('type' => 'html'), $entry['content']); + $this->element('id', null, $entry['id']); + $this->element('published', null, $entry['published']); + $this->element('updated', null, $entry['updated']); + $this->element('link', array('type' => 'text/html', + 'href' => $entry['link'], + 'rel' => 'alternate')); + $this->element('link', array('type' => $entry['avatar-type'], + 'href' => $entry['avatar'], + 'rel' => 'image')); + $this->elementStart('author'); + + $this->element('name', null, $entry['author-name']); + $this->element('uri', null, $entry['author-uri']); + + $this->elementEnd('author'); + $this->elementEnd('entry'); + } - $this->init_document('atom'); + function showXmlDirectMessage($dm) + { + $this->elementStart('direct_message'); + foreach($dm as $element => $value) { + switch ($element) { + case 'sender': + case 'recipient': + $this->showTwitterXmlUser($value, $element); + break; + case 'text': + $this->element($element, null, common_xml_safe_str($value)); + break; + default: + $this->element($element, null, $value); + break; + } + } + $this->elementEnd('direct_message'); + } + + function directMessageArray($message) + { + $dmsg = array(); + + $from_profile = $message->getFrom(); + $to_profile = $message->getTo(); + + $dmsg['id'] = $message->id; + $dmsg['sender_id'] = $message->from_profile; + $dmsg['text'] = trim($message->content); + $dmsg['recipient_id'] = $message->to_profile; + $dmsg['created_at'] = $this->dateTwitter($message->created); + $dmsg['sender_screen_name'] = $from_profile->nickname; + $dmsg['recipient_screen_name'] = $to_profile->nickname; + $dmsg['sender'] = $this->twitterUserArray($from_profile, false); + $dmsg['recipient'] = $this->twitterUserArray($to_profile, false); + + return $dmsg; + } + + function rssDirectMessageArray($message) + { + $entry = array(); + + $from = $message->getFrom(); + + $entry['title'] = sprintf('Message from %s to %s', + $from->nickname, $message->getTo()->nickname); + + $entry['content'] = common_xml_safe_str($message->rendered); + $entry['link'] = common_local_url('showmessage', array('message' => $message->id)); + $entry['published'] = common_date_iso8601($message->created); + + $taguribase = common_config('integration', 'taguri'); + + $entry['id'] = "tag:$taguribase:$entry[link]"; + $entry['updated'] = $entry['published']; + + $entry['author-name'] = $from->getBestName(); + $entry['author-uri'] = $from->homepage; + + $avatar = $from->getAvatar(AVATAR_STREAM_SIZE); + + $entry['avatar'] = (!empty($avatar)) ? $avatar->url : Avatar::defaultImage(AVATAR_STREAM_SIZE); + $entry['avatar-type'] = (!empty($avatar)) ? $avatar->mediatype : 'image/png'; + + // RSS item specific + + $entry['description'] = $entry['content']; + $entry['pubDate'] = common_date_rfc2822($message->created); + $entry['guid'] = $entry['link']; + + return $entry; + } + + function showSingleXmlDirectMessage($message) + { + $this->initDocument('xml'); + $dmsg = $this->directMessageArray($message); + $this->showXmlDirectMessage($dmsg); + $this->endDocument('xml'); + } + + function showSingleJsonDirectMessage($message) + { + $this->initDocument('json'); + $dmsg = $this->directMessageArray($message); + $this->showJsonObjects($dmsg); + $this->endDocument('json'); + } + + function showAtomGroups($group, $title, $id, $link, $subtitle=null, $selfuri=null) + { + + $this->initDocument('atom'); $this->element('title', null, $title); $this->element('id', null, $id); @@ -714,168 +776,151 @@ class TwitterapiAction extends Action } } - $this->end_document('atom'); + $this->endDocument('atom'); } - function show_json_timeline($notice) + function showJsonTimeline($notice) { - $this->init_document('json'); + $this->initDocument('json'); $statuses = array(); if (is_array($notice)) { foreach ($notice as $n) { - $twitter_status = $this->twitter_status_array($n); + $twitter_status = $this->twitterStatusArray($n); array_push($statuses, $twitter_status); } } else { while ($notice->fetch()) { - $twitter_status = $this->twitter_status_array($notice); + $twitter_status = $this->twitterStatusArray($notice); array_push($statuses, $twitter_status); } } - $this->show_json_objects($statuses); + $this->showJsonObjects($statuses); - $this->end_document('json'); + $this->endDocument('json'); } - function show_json_groups($group) + function showJsonGroups($group) { - $this->init_document('json'); + $this->initDocument('json'); $groups = array(); if (is_array($group)) { foreach ($group as $g) { - $twitter_group = $this->twitter_group_array($g); + $twitter_group = $this->twitterGroupArray($g); array_push($groups, $twitter_group); } } else { while ($group->fetch()) { - $twitter_group = $this->twitter_group_array($group); + $twitter_group = $this->twitterGroupArray($group); array_push($groups, $twitter_group); } } - $this->show_json_objects($groups); + $this->showJsonObjects($groups); - $this->end_document('json'); + $this->endDocument('json'); } - function show_xml_groups($group) + function showXmlGroups($group) { - $this->init_document('xml'); + $this->initDocument('xml'); $this->elementStart('groups', array('type' => 'array')); if (is_array($group)) { foreach ($group as $g) { - $twitter_group = $this->twitter_group_array($g); - $this->show_twitter_xml_group($twitter_group); + $twitter_group = $this->twitterGroupArray($g); + $this->showTwitterXmlGroup($twitter_group); } } else { while ($group->fetch()) { - $twitter_group = $this->twitter_group_array($group); - $this->show_twitter_xml_group($twitter_group); + $twitter_group = $this->twitterGroupArray($group); + $this->showTwitterXmlGroup($twitter_group); } } $this->elementEnd('groups'); - $this->end_document('xml'); + $this->endDocument('xml'); } - function show_twitter_xml_users($user) + function showTwitterXmlUsers($user) { - $this->init_document('xml'); + $this->initDocument('xml'); $this->elementStart('users', array('type' => 'array')); if (is_array($user)) { - foreach ($group as $g) { - $twitter_user = $this->twitter_user_array($g); - $this->show_twitter_xml_user($twitter_user,'user'); + foreach ($user as $u) { + $twitter_user = $this->twitterUserArray($u); + $this->showTwitterXmlUser($twitter_user); } } else { while ($user->fetch()) { - $twitter_user = $this->twitter_user_array($user); - $this->show_twitter_xml_user($twitter_user); + $twitter_user = $this->twitterUserArray($user); + $this->showTwitterXmlUser($twitter_user); } } $this->elementEnd('users'); - $this->end_document('xml'); + $this->endDocument('xml'); } - function show_json_users($user) + function showJsonUsers($user) { - $this->init_document('json'); + $this->initDocument('json'); $users = array(); if (is_array($user)) { foreach ($user as $u) { - $twitter_user = $this->twitter_user_array($u); + $twitter_user = $this->twitterUserArray($u); array_push($users, $twitter_user); } } else { while ($user->fetch()) { - $twitter_user = $this->twitter_user_array($user); + $twitter_user = $this->twitterUserArray($user); array_push($users, $twitter_user); } } - $this->show_json_objects($users); + $this->showJsonObjects($users); - $this->end_document('json'); + $this->endDocument('json'); } - function show_single_json_group($group) + function showSingleJsonGroup($group) { - $this->init_document('json'); - $twitter_group = $this->twitter_group_array($group); - $this->show_json_objects($twitter_group); - $this->end_document('json'); + $this->initDocument('json'); + $twitter_group = $this->twitterGroupArray($group); + $this->showJsonObjects($twitter_group); + $this->endDocument('json'); } - function show_single_xml_group($group) + function showSingleXmlGroup($group) { - $this->init_document('xml'); - $twitter_group = $this->twitter_group_array($group); - $this->show_twitter_xml_group($twitter_group); - $this->end_document('xml'); + $this->initDocument('xml'); + $twitter_group = $this->twitterGroupArray($group); + $this->showTwitterXmlGroup($twitter_group); + $this->endDocument('xml'); } - // Anyone know what date format this is? - // Twitter's dates look like this: "Mon Jul 14 23:52:38 +0000 2008" -- Zach - function date_twitter($dt) + function dateTwitter($dt) { - $t = strtotime($dt); - return date("D M d H:i:s O Y", $t); + $dateStr = date('d F Y H:i:s', strtotime($dt)); + $d = new DateTime($dateStr, new DateTimeZone('UTC')); + $d->setTimezone(new DateTimeZone(common_timezone())); + return $d->format('D M d H:i:s O Y'); } - // XXX: Candidate for a general utility method somewhere? - function count_subscriptions($profile) - { - - $count = 0; - $sub = new Subscription(); - $sub->subscribed = $profile->id; - - $count = $sub->find(); - - if ($count > 0) { - return $count - 1; - } else { - return 0; - } - } - - function init_document($type='xml') + function initDocument($type='xml') { switch ($type) { case 'xml': @@ -893,11 +938,11 @@ class TwitterapiAction extends Action break; case 'rss': header("Content-Type: application/rss+xml; charset=utf-8"); - $this->init_twitter_rss(); + $this->initTwitterRss(); break; case 'atom': header('Content-Type: application/atom+xml; charset=utf-8'); - $this->init_twitter_atom(); + $this->initTwitterAtom(); break; default: $this->clientError(_('Not a supported data format.')); @@ -907,7 +952,7 @@ class TwitterapiAction extends Action return; } - function end_document($type='xml') + function endDocument($type='xml') { switch ($type) { case 'xml': @@ -922,10 +967,10 @@ class TwitterapiAction extends Action } break; case 'rss': - $this->end_twitter_rss(); + $this->endTwitterRss(); break; case 'atom': - $this->end_twitter_rss(); + $this->endTwitterRss(); break; default: $this->clientError(_('Not a supported data format.')); @@ -934,56 +979,69 @@ class TwitterapiAction extends Action return; } - function clientError($msg, $code = 400, $content_type = 'json') + function clientError($msg, $code = 400, $format = 'xml') { + $action = $this->trimmed('action'); - static $status = array(400 => 'Bad Request', - 401 => 'Unauthorized', - 402 => 'Payment Required', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Proxy Authentication Required', - 408 => 'Request Timeout', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition Failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Long', - 415 => 'Unsupported Media Type', - 416 => 'Requested Range Not Satisfiable', - 417 => 'Expectation Failed'); + common_debug("User error '$code' on '$action': $msg", __FILE__); + + if (!array_key_exists($code, ClientErrorAction::$status)) { + $code = 400; + } + + $status_string = ClientErrorAction::$status[$code]; + header('HTTP/1.1 '.$code.' '.$status_string); + + if ($format == 'xml') { + $this->initDocument('xml'); + $this->elementStart('hash'); + $this->element('error', null, $msg); + $this->element('request', null, $_SERVER['REQUEST_URI']); + $this->elementEnd('hash'); + $this->endDocument('xml'); + } elseif ($format == 'json'){ + $this->initDocument('json'); + $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); + print(json_encode($error_array)); + $this->endDocument('json'); + } else { + + // If user didn't request a useful format, throw a regular client error + throw new ClientException($msg, $code); + } + } + + function serverError($msg, $code = 500, $content_type = 'json') + { $action = $this->trimmed('action'); - common_debug("User error '$code' on '$action': $msg", __FILE__); + common_debug("Server error '$code' on '$action': $msg", __FILE__); - if (!array_key_exists($code, $status)) { + if (!array_key_exists($code, ServerErrorAction::$status)) { $code = 400; } - $status_string = $status[$code]; + $status_string = ServerErrorAction::$status[$code]; + header('HTTP/1.1 '.$code.' '.$status_string); if ($content_type == 'xml') { - $this->init_document('xml'); + $this->initDocument('xml'); $this->elementStart('hash'); $this->element('error', null, $msg); $this->element('request', null, $_SERVER['REQUEST_URI']); $this->elementEnd('hash'); - $this->end_document('xml'); + $this->endDocument('xml'); } else { - $this->init_document('json'); + $this->initDocument('json'); $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); print(json_encode($error_array)); - $this->end_document('json'); + $this->endDocument('json'); } - } - function init_twitter_rss() + function initTwitterRss() { $this->startXML(); $this->elementStart('rss', array('version' => '2.0', 'xmlns:atom'=>'http://www.w3.org/2005/Atom')); @@ -991,14 +1049,14 @@ class TwitterapiAction extends Action Event::handle('StartApiRss', array($this)); } - function end_twitter_rss() + function endTwitterRss() { $this->elementEnd('channel'); $this->elementEnd('rss'); $this->endXML(); } - function init_twitter_atom() + function initTwitterAtom() { $this->startXML(); // FIXME: don't hardcode the language here! @@ -1008,21 +1066,21 @@ class TwitterapiAction extends Action Event::handle('StartApiAtom', array($this)); } - function end_twitter_atom() + function endTwitterAtom() { $this->elementEnd('feed'); $this->endXML(); } - function show_profile($profile, $content_type='xml', $notice=null, $includeStatuses=true) + function showProfile($profile, $content_type='xml', $notice=null, $includeStatuses=true) { - $profile_array = $this->twitter_user_array($profile, $includeStatuses); + $profile_array = $this->twitterUserArray($profile, $includeStatuses); switch ($content_type) { case 'xml': - $this->show_twitter_xml_user($profile_array); + $this->showTwitterXmlUser($profile_array); break; case 'json': - $this->show_json_objects($profile_array); + $this->showJsonObjects($profile_array); break; default: $this->clientError(_('Not a supported data format.')); @@ -1031,7 +1089,7 @@ class TwitterapiAction extends Action return; } - function get_user($id, $apidata=null) + function getTargetUser($id) { if (empty($id)) { @@ -1052,7 +1110,7 @@ class TwitterapiAction extends Action return User::staticGet('nickname', $nickname); } else { // Fall back to trying the currently authenticated user - return $apidata['user']; + return $this->auth_user; } } else if (is_numeric($id)) { @@ -1063,10 +1121,9 @@ class TwitterapiAction extends Action } } - function get_group($id, $apidata=null) + function getTargetGroup($id) { if (empty($id)) { - if (is_numeric($this->arg('id'))) { return User_group::staticGet($this->arg('id')); } else if ($this->arg('id')) { @@ -1091,21 +1148,7 @@ class TwitterapiAction extends Action } } - function get_profile($id) - { - if (is_numeric($id)) { - return Profile::staticGet($id); - } else { - $user = User::staticGet('nickname', $id); - if ($user) { - return $user->getProfile(); - } else { - return null; - } - } - } - - function source_link($source) + function sourceLink($source) { $source_name = _($source); switch ($source) { diff --git a/lib/apiauth.php b/lib/apiauth.php new file mode 100644 index 000000000..2f2e44a26 --- /dev/null +++ b/lib/apiauth.php @@ -0,0 +1,203 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Base class for API actions that require authentication + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Adrian Lang <mail@adrianlang.de> + * @author Brenda Wallace <shiny@cpan.org> + * @author Craig Andrews <candrews@integralblue.com> + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author mEDI <medi@milaro.net> + * @author Sarven Capadisli <csarven@status.net> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Actions extending this class will require auth + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiAuthAction extends ApiAction +{ + + var $auth_user = null; + + /** + * Take arguments for running, and output basic auth header if needed + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + if ($this->requiresAuth()) { + $this->checkBasicAuthUser(); + } + + return true; + } + + /** + * Does this API resource require authentication? + * + * @return boolean true + */ + + function requiresAuth() + { + return true; + } + + /** + * Check for a user specified via HTTP basic auth. If there isn't + * one, try to get one by outputting the basic auth header. + * + * @return boolean true or false + */ + + function checkBasicAuthUser() + { + $this->basicAuthProcessHeader(); + + $realm = common_config('site', 'name') . ' API'; + + if (!isset($this->auth_user)) { + header('WWW-Authenticate: Basic realm="' . $realm . '"'); + + // show error if the user clicks 'cancel' + + $this->showBasicAuthError(); + exit; + + } else { + $nickname = $this->auth_user; + $password = $this->auth_pw; + $this->auth_user = common_check_user($nickname, $password); + + if (empty($this->auth_user)) { + + // basic authentication failed + + list($proxy, $ip) = common_client_ip(); + common_log( + LOG_WARNING, + 'Failed API auth attempt, nickname = ' . + "$nickname, proxy = $proxy, ip = $ip." + ); + $this->showBasicAuthError(); + exit; + } + } + return true; + } + + /** + * Read the HTTP headers and set the auth user. Decodes HTTP_AUTHORIZATION + * param to support basic auth when PHP is running in CGI mode. + * + * @return void + */ + + function basicAuthProcessHeader() + { + if (isset($_SERVER['AUTHORIZATION']) + || isset($_SERVER['HTTP_AUTHORIZATION']) + ) { + $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION']) + ? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION']; + } + + if (isset($_SERVER['PHP_AUTH_USER'])) { + $this->auth_user = $_SERVER['PHP_AUTH_USER']; + $this->auth_pw = $_SERVER['PHP_AUTH_PW']; + } elseif (isset($authorization_header) + && strstr(substr($authorization_header, 0, 5), 'Basic')) { + + // decode the HTTP_AUTHORIZATION header on php-cgi server self + // on fcgid server the header name is AUTHORIZATION + + $auth_hash = base64_decode(substr($authorization_header, 6)); + list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash); + + // set all to null on a empty basic auth request + + if ($this->auth_user == "") { + $this->auth_user = null; + $this->auth_pw = null; + } + } else { + $this->auth_user = null; + $this->auth_pw = null; + } + } + + /** + * Output an authentication error message. Use XML or JSON if one + * of those formats is specified, otherwise output plain text + * + * @return void + */ + + function showBasicAuthError() + { + header('HTTP/1.1 401 Unauthorized'); + $msg = 'Could not authenticate you.'; + + if ($this->format == 'xml') { + header('Content-Type: application/xml; charset=utf-8'); + $this->startXML(); + $this->elementStart('hash'); + $this->element('error', null, $msg); + $this->element('request', null, $_SERVER['REQUEST_URI']); + $this->elementEnd('hash'); + $this->endXML(); + } elseif ($this->format == 'json') { + header('Content-Type: application/json; charset=utf-8'); + $error_array = array('error' => $msg, + 'request' => $_SERVER['REQUEST_URI']); + print(json_encode($error_array)); + } else { + header('Content-type: text/plain'); + print "$msg\n"; + } + } + +} diff --git a/lib/apibareauth.php b/lib/apibareauth.php new file mode 100644 index 000000000..2d29c1ddd --- /dev/null +++ b/lib/apibareauth.php @@ -0,0 +1,109 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Base class for API actions that require "bare auth". Bare auth means + * authentication is required only if the action is called without an argument + * or query param specifying user id. + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Adrian Lang <mail@adrianlang.de> + * @author Brenda Wallace <shiny@cpan.org> + * @author Craig Andrews <candrews@integralblue.com> + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author mEDI <medi@milaro.net> + * @author Sarven Capadisli <csarven@status.net> + * @author Zach Copley <zach@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/apiauth.php'; + +/** + * Actions extending this class will require auth unless a target + * user ID has been specified + * + * @category API + * @package StatusNet + * @author Adrian Lang <mail@adrianlang.de> + * @author Brenda Wallace <shiny@cpan.org> + * @author Craig Andrews <candrews@integralblue.com> + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author mEDI <medi@milaro.net> + * @author Sarven Capadisli <csarven@status.net> + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiBareAuthAction extends ApiAuthAction +{ + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + return true; + } + + /** + * Does this API resource require authentication? + * + * @return boolean true or false + */ + + function requiresAuth() + { + // If the site is "private", all API methods except statusnet/config + // need authentication + + if (common_config('site', 'private')) { + return true; + } + + // check whether a user has been specified somehow + + $id = $this->arg('id'); + $user_id = $this->arg('user_id'); + $screen_name = $this->arg('screen_name'); + + if (empty($id) && empty($user_id) && empty($screen_name)) { + return true; + } + + return false; + } + +}
\ No newline at end of file diff --git a/lib/clienterroraction.php b/lib/clienterroraction.php index 7d007a756..1b98a1064 100644 --- a/lib/clienterroraction.php +++ b/lib/clienterroraction.php @@ -46,28 +46,28 @@ require_once INSTALLDIR.'/lib/error.php'; */ class ClientErrorAction extends ErrorAction { + static $status = array(400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed'); + function __construct($message='Error', $code=400) { parent::__construct($message, $code); - - $this->status = array(400 => 'Bad Request', - 401 => 'Unauthorized', - 402 => 'Payment Required', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Proxy Authentication Required', - 408 => 'Request Timeout', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition Failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Long', - 415 => 'Unsupported Media Type', - 416 => 'Requested Range Not Satisfiable', - 417 => 'Expectation Failed'); $this->default = 400; } @@ -91,9 +91,4 @@ class ClientErrorAction extends ErrorAction $this->showPage(); } - - function title() - { - return $this->status[$this->code]; - } } diff --git a/lib/commandinterpreter.php b/lib/commandinterpreter.php index 6e4340e5d..60fc4c3c4 100644 --- a/lib/commandinterpreter.php +++ b/lib/commandinterpreter.php @@ -28,7 +28,7 @@ class CommandInterpreter # XXX: localise $text = preg_replace('/\s+/', ' ', trim($text)); - list($cmd, $arg) = explode(' ', $text, 2); + list($cmd, $arg) = $this->split_arg($text); # We try to support all the same commands as Twitter, see # http://getsatisfaction.com/twitter/topics/what_are_the_twitter_commands @@ -43,7 +43,7 @@ class CommandInterpreter return new HelpCommand($user); case 'on': if ($arg) { - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -54,7 +54,7 @@ class CommandInterpreter } case 'off': if ($arg) { - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -74,7 +74,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -84,7 +84,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -95,7 +95,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -106,7 +106,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -117,7 +117,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -128,7 +128,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if (!$extra) { return null; } else { @@ -138,7 +138,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -148,7 +148,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -158,7 +158,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -173,7 +173,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($other, $extra) = explode(' ', $arg, 2); + list($other, $extra) = $this->split_arg($arg); if ($extra) { return null; } else { @@ -183,7 +183,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($word, $extra) = explode(' ', $arg, 2); + list($word, $extra) = $this->split_arg($arg); if ($extra) { return null; } else if ($word == 'off') { @@ -195,7 +195,7 @@ class CommandInterpreter if (!$arg) { return null; } - list($word, $extra) = explode(' ', $arg, 2); + list($word, $extra) = $this->split_arg($arg); if ($extra) { return null; } else if ($word == 'all') { @@ -213,5 +213,17 @@ class CommandInterpreter return false; } } + + /** + * Split arguments without triggering a PHP notice warning + */ + function split_arg($text) + { + $pieces = explode(' ', $text, 2); + if (count($pieces) == 1) { + $pieces[] = null; + } + return $pieces; + } } diff --git a/lib/common.php b/lib/common.php index f7c31c792..e29456ed4 100644 --- a/lib/common.php +++ b/lib/common.php @@ -53,6 +53,7 @@ require_once('DB/DataObject/Cast.php'); # for dates if (!function_exists('gettext')) { require_once("php-gettext/gettext.inc"); } + require_once(INSTALLDIR.'/lib/language.php'); // This gets included before the config file, so that admin code and plugins @@ -93,214 +94,17 @@ if (isset($path)) { null; } -// default configuration, overwritten in config.php +require_once(INSTALLDIR.'/lib/default.php'); + +// Set config values initially to default values + +$config = $default; -$config = - array('site' => - array('name' => 'Just another StatusNet microblog', - 'server' => $_server, - 'theme' => 'default', - 'path' => $_path, - 'logfile' => null, - 'logo' => null, - 'logdebug' => false, - 'fancy' => false, - 'locale_path' => INSTALLDIR.'/locale', - 'language' => 'en_US', - 'languages' => get_all_languages(), - 'email' => - array_key_exists('SERVER_ADMIN', $_SERVER) ? $_SERVER['SERVER_ADMIN'] : null, - 'broughtby' => null, - 'timezone' => 'UTC', - 'broughtbyurl' => null, - 'closed' => false, - 'inviteonly' => false, - 'private' => false, - 'ssl' => 'never', - 'sslserver' => null, - 'shorturllength' => 30, - 'dupelimit' => 60, # default for same person saying the same thing - 'textlimit' => 140, - ), - 'syslog' => - array('appname' => 'statusnet', # for syslog - 'priority' => 'debug', # XXX: currently ignored - 'facility' => LOG_USER), - 'queue' => - array('enabled' => false, - 'subsystem' => 'db', # default to database, or 'stomp' - 'stomp_server' => null, - 'queue_basename' => 'statusnet', - 'stomp_username' => null, - 'stomp_password' => null, - ), - 'license' => - array('url' => 'http://creativecommons.org/licenses/by/3.0/', - 'title' => 'Creative Commons Attribution 3.0', - 'image' => 'http://i.creativecommons.org/l/by/3.0/80x15.png'), - 'mail' => - array('backend' => 'mail', - 'params' => null), - 'nickname' => - array('blacklist' => array(), - 'featured' => array()), - 'profile' => - array('banned' => array(), - 'biolimit' => null), - 'avatar' => - array('server' => null, - 'dir' => INSTALLDIR . '/avatar/', - 'path' => $_path . '/avatar/'), - 'background' => - array('server' => null, - 'dir' => INSTALLDIR . '/background/', - 'path' => $_path . '/background/'), - 'public' => - array('localonly' => true, - 'blacklist' => array(), - 'autosource' => array()), - 'theme' => - array('server' => null, - 'dir' => null, - 'path'=> null), - 'throttle' => - array('enabled' => false, // whether to throttle edits; false by default - 'count' => 20, // number of allowed messages in timespan - 'timespan' => 600), // timespan for throttling - 'xmpp' => - array('enabled' => false, - 'server' => 'INVALID SERVER', - 'port' => 5222, - 'user' => 'update', - 'encryption' => true, - 'resource' => 'uniquename', - 'password' => 'blahblahblah', - 'host' => null, # only set if != server - 'debug' => false, # print extra debug info - 'public' => array()), # JIDs of users who want to receive the public stream - 'invite' => - array('enabled' => true), - 'sphinx' => - array('enabled' => false, - 'server' => 'localhost', - 'port' => 3312), - 'tag' => - array('dropoff' => 864000.0), - 'popular' => - array('dropoff' => 864000.0), - 'daemon' => - array('piddir' => '/var/run', - 'user' => false, - 'group' => false), - 'emailpost' => - array('enabled' => true), - 'sms' => - array('enabled' => true), - 'twitter' => - array('enabled' => true), - 'twitterbridge' => - array('enabled' => false), - 'integration' => - array('source' => 'StatusNet', # source attribute for Twitter - 'taguri' => $_server.',2009'), # base for tag URIs - 'twitter' => - array('consumer_key' => null, - 'consumer_secret' => null), - 'memcached' => - array('enabled' => false, - 'server' => 'localhost', - 'base' => null, - 'port' => 11211), - 'ping' => - array('notify' => array()), - 'inboxes' => - array('enabled' => true), # on by default for new sites - 'newuser' => - array('default' => null, - 'welcome' => null), - 'snapshot' => - array('run' => 'web', - 'frequency' => 10000, - 'reporturl' => 'http://status.net/stats/report'), - 'attachments' => - array('server' => null, - 'dir' => INSTALLDIR . '/file/', - 'path' => $_path . '/file/', - 'supported' => array('image/png', - 'image/jpeg', - 'image/gif', - 'image/svg+xml', - 'audio/mpeg', - 'audio/x-speex', - 'application/ogg', - 'application/pdf', - 'application/vnd.oasis.opendocument.text', - 'application/vnd.oasis.opendocument.text-template', - 'application/vnd.oasis.opendocument.graphics', - 'application/vnd.oasis.opendocument.graphics-template', - 'application/vnd.oasis.opendocument.presentation', - 'application/vnd.oasis.opendocument.presentation-template', - 'application/vnd.oasis.opendocument.spreadsheet', - 'application/vnd.oasis.opendocument.spreadsheet-template', - 'application/vnd.oasis.opendocument.chart', - 'application/vnd.oasis.opendocument.chart-template', - 'application/vnd.oasis.opendocument.image', - 'application/vnd.oasis.opendocument.image-template', - 'application/vnd.oasis.opendocument.formula', - 'application/vnd.oasis.opendocument.formula-template', - 'application/vnd.oasis.opendocument.text-master', - 'application/vnd.oasis.opendocument.text-web', - 'application/x-zip', - 'application/zip', - 'text/plain', - 'video/mpeg', - 'video/mp4', - 'video/quicktime', - 'video/mpeg'), - 'file_quota' => 5000000, - 'user_quota' => 50000000, - 'monthly_quota' => 15000000, - 'uploads' => true, - 'filecommand' => '/usr/bin/file', - ), - 'group' => - array('maxaliases' => 3, - 'desclimit' => null), - 'oohembed' => array('endpoint' => 'http://oohembed.com/oohembed/'), - 'search' => - array('type' => 'fulltext'), - 'sessions' => - array('handle' => false, // whether to handle sessions ourselves - 'debug' => false), // debugging output for sessions - 'design' => - array('backgroundcolor' => null, // null -> 'use theme default' - 'contentcolor' => null, - 'sidebarcolor' => null, - 'textcolor' => null, - 'linkcolor' => null, - 'backgroundimage' => null, - 'disposition' => null), - 'notice' => - array('contentlimit' => null), - 'message' => - array('contentlimit' => null), - 'http' => - array('client' => 'curl'), // XXX: should this be the default? - ); +// default configuration, overwritten in config.php $config['db'] = &PEAR::getStaticProperty('DB_DataObject','options'); -$config['db'] = - array('database' => 'YOU HAVE TO SET THIS IN config.php', - 'schema_location' => INSTALLDIR . '/classes', - 'class_location' => INSTALLDIR . '/classes', - 'require_prefix' => 'classes/', - 'class_prefix' => '', - 'mirror' => null, - 'utf8' => true, - 'db_driver' => 'DB', # XXX: JanRain libs only work with DB - 'quote_identifiers' => false, - 'type' => 'mysql' ); +$config['db'] = $default['db']; // Backward compatibility @@ -427,6 +231,12 @@ require_once INSTALLDIR.'/lib/serverexception.php'; Config::loadSettings(); +// XXX: if plugins should check the schema at runtime, do that here. + +if ($config['db']['schemacheck'] == 'runtime') { + Event::handle('CheckSchema'); +} + // XXX: other formats here define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER); diff --git a/lib/default.php b/lib/default.php new file mode 100644 index 000000000..329b041e9 --- /dev/null +++ b/lib/default.php @@ -0,0 +1,232 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Default settings for core configuration + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category Config + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @copyright 2008-9 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/ + */ + +$default = + array('site' => + array('name' => 'Just another StatusNet microblog', + 'server' => $_server, + 'theme' => 'default', + 'path' => $_path, + 'logfile' => null, + 'logo' => null, + 'logdebug' => false, + 'fancy' => false, + 'locale_path' => INSTALLDIR.'/locale', + 'language' => 'en_US', + 'languages' => get_all_languages(), + 'email' => + array_key_exists('SERVER_ADMIN', $_SERVER) ? $_SERVER['SERVER_ADMIN'] : null, + 'broughtby' => null, + 'timezone' => 'UTC', + 'broughtbyurl' => null, + 'closed' => false, + 'inviteonly' => false, + 'private' => false, + 'ssl' => 'never', + 'sslserver' => null, + 'shorturllength' => 30, + 'dupelimit' => 60, # default for same person saying the same thing + 'textlimit' => 140, + ), + 'db' => + array('database' => 'YOU HAVE TO SET THIS IN config.php', + 'schema_location' => INSTALLDIR . '/classes', + 'class_location' => INSTALLDIR . '/classes', + 'require_prefix' => 'classes/', + 'class_prefix' => '', + 'mirror' => null, + 'utf8' => true, + 'db_driver' => 'DB', # XXX: JanRain libs only work with DB + 'quote_identifiers' => false, + 'type' => 'mysql', + 'schemacheck' => 'runtime'), // 'runtime' or 'script' + 'syslog' => + array('appname' => 'statusnet', # for syslog + 'priority' => 'debug', # XXX: currently ignored + 'facility' => LOG_USER), + 'queue' => + array('enabled' => false, + 'subsystem' => 'db', # default to database, or 'stomp' + 'stomp_server' => null, + 'queue_basename' => 'statusnet', + 'stomp_username' => null, + 'stomp_password' => null, + ), + 'license' => + array('url' => 'http://creativecommons.org/licenses/by/3.0/', + 'title' => 'Creative Commons Attribution 3.0', + 'image' => 'http://i.creativecommons.org/l/by/3.0/80x15.png'), + 'mail' => + array('backend' => 'mail', + 'params' => null), + 'nickname' => + array('blacklist' => array(), + 'featured' => array()), + 'profile' => + array('banned' => array(), + 'biolimit' => null), + 'avatar' => + array('server' => null, + 'dir' => INSTALLDIR . '/avatar/', + 'path' => $_path . '/avatar/'), + 'background' => + array('server' => null, + 'dir' => INSTALLDIR . '/background/', + 'path' => $_path . '/background/'), + 'public' => + array('localonly' => true, + 'blacklist' => array(), + 'autosource' => array()), + 'theme' => + array('server' => null, + 'dir' => null, + 'path'=> null), + 'throttle' => + array('enabled' => false, // whether to throttle edits; false by default + 'count' => 20, // number of allowed messages in timespan + 'timespan' => 600), // timespan for throttling + 'xmpp' => + array('enabled' => false, + 'server' => 'INVALID SERVER', + 'port' => 5222, + 'user' => 'update', + 'encryption' => true, + 'resource' => 'uniquename', + 'password' => 'blahblahblah', + 'host' => null, # only set if != server + 'debug' => false, # print extra debug info + 'public' => array()), # JIDs of users who want to receive the public stream + 'invite' => + array('enabled' => true), + 'sphinx' => + array('enabled' => false, + 'server' => 'localhost', + 'port' => 3312), + 'tag' => + array('dropoff' => 864000.0), + 'popular' => + array('dropoff' => 864000.0), + 'daemon' => + array('piddir' => '/var/run', + 'user' => false, + 'group' => false), + 'emailpost' => + array('enabled' => true), + 'sms' => + array('enabled' => true), + 'twitterbridge' => + array('enabled' => false), + 'integration' => + array('source' => 'StatusNet', # source attribute for Twitter + 'taguri' => $_server.',2009'), # base for tag URIs + 'twitter' => + array('enabled' => true, + 'consumer_key' => null, + 'consumer_secret' => null), + 'memcached' => + array('enabled' => false, + 'server' => 'localhost', + 'base' => null, + 'port' => 11211), + 'ping' => + array('notify' => array()), + 'inboxes' => + array('enabled' => true), # on by default for new sites + 'newuser' => + array('default' => null, + 'welcome' => null), + 'snapshot' => + array('run' => 'web', + 'frequency' => 10000, + 'reporturl' => 'http://status.net/stats/report'), + 'attachments' => + array('server' => null, + 'dir' => INSTALLDIR . '/file/', + 'path' => $_path . '/file/', + 'supported' => array('image/png', + 'image/jpeg', + 'image/gif', + 'image/svg+xml', + 'audio/mpeg', + 'audio/x-speex', + 'application/ogg', + 'application/pdf', + 'application/vnd.oasis.opendocument.text', + 'application/vnd.oasis.opendocument.text-template', + 'application/vnd.oasis.opendocument.graphics', + 'application/vnd.oasis.opendocument.graphics-template', + 'application/vnd.oasis.opendocument.presentation', + 'application/vnd.oasis.opendocument.presentation-template', + 'application/vnd.oasis.opendocument.spreadsheet', + 'application/vnd.oasis.opendocument.spreadsheet-template', + 'application/vnd.oasis.opendocument.chart', + 'application/vnd.oasis.opendocument.chart-template', + 'application/vnd.oasis.opendocument.image', + 'application/vnd.oasis.opendocument.image-template', + 'application/vnd.oasis.opendocument.formula', + 'application/vnd.oasis.opendocument.formula-template', + 'application/vnd.oasis.opendocument.text-master', + 'application/vnd.oasis.opendocument.text-web', + 'application/x-zip', + 'application/zip', + 'text/plain', + 'video/mpeg', + 'video/mp4', + 'video/quicktime', + 'video/mpeg'), + 'file_quota' => 5000000, + 'user_quota' => 50000000, + 'monthly_quota' => 15000000, + 'uploads' => true, + 'filecommand' => '/usr/bin/file', + ), + 'group' => + array('maxaliases' => 3, + 'desclimit' => null), + 'oohembed' => array('endpoint' => 'http://oohembed.com/oohembed/'), + 'search' => + array('type' => 'fulltext'), + 'sessions' => + array('handle' => false, // whether to handle sessions ourselves + 'debug' => false), // debugging output for sessions + 'design' => + array('backgroundcolor' => null, // null -> 'use theme default' + 'contentcolor' => null, + 'sidebarcolor' => null, + 'textcolor' => null, + 'linkcolor' => null, + 'backgroundimage' => null, + 'disposition' => null), + 'notice' => + array('contentlimit' => null), + 'message' => + array('contentlimit' => null), + 'http' => + array('client' => 'curl'), // XXX: should this be the default? + ); diff --git a/lib/deleteaction.php b/lib/deleteaction.php deleted file mode 100644 index f702820c6..000000000 --- a/lib/deleteaction.php +++ /dev/null @@ -1,74 +0,0 @@ -<?php -/** - * StatusNet, the distributed open-source microblogging tool - * - * Base class for deleting things - * - * PHP version 5 - * - * LICENCE: 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 <http://www.gnu.org/licenses/>. - * - * @category Personal - * @package StatusNet - * @author Evan Prodromou <evan@status.net> - * @author Sarven Capadisli <csarven@status.net> - * @copyright 2008 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -class DeleteAction extends Action -{ - var $user = null; - var $notice = null; - var $profile = null; - var $user_profile = null; - - function prepare($args) - { - parent::prepare($args); - - $this->user = common_current_user(); - $notice_id = $this->trimmed('notice'); - $this->notice = Notice::staticGet($notice_id); - - if (!$this->notice) { - common_user_error(_('No such notice.')); - exit; - } - - $this->profile = $this->notice->getProfile(); - $this->user_profile = $this->user->getProfile(); - - return true; - } - - function handle($args) - { - parent::handle($args); - - if (!common_logged_in()) { - common_user_error(_('Not logged in.')); - exit; - } else if ($this->notice->profile_id != $this->user_profile->id) { - common_user_error(_('Can\'t delete this notice.')); - exit; - } - } - -} diff --git a/lib/designsettings.php b/lib/designsettings.php index fdc05562e..820d534f2 100644 --- a/lib/designsettings.php +++ b/lib/designsettings.php @@ -325,7 +325,6 @@ class DesignSettingsAction extends AccountSettingsAction parent::showScripts(); $this->script('js/farbtastic/farbtastic.js'); - $this->script('js/farbtastic/farbtastic.go.js'); $this->script('js/userdesign.go.js'); $this->autofocus('design_background-image_file'); diff --git a/lib/error.php b/lib/error.php index 0c521db08..6a9b76be1 100644 --- a/lib/error.php +++ b/lib/error.php @@ -44,9 +44,10 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { */ class ErrorAction extends Action { + static $status = array(); + var $code = null; var $message = null; - var $status = null; var $default = null; function __construct($message, $code, $output='php://output', $indent=true) @@ -88,9 +89,10 @@ class ErrorAction extends Action * * @return page title */ + function title() { - return $this->message; + return self::$status[$this->code]; } function isReadOnly($args) diff --git a/lib/facebookaction.php b/lib/facebookaction.php index 411f79594..3f3a8d3b0 100644 --- a/lib/facebookaction.php +++ b/lib/facebookaction.php @@ -468,11 +468,11 @@ class FacebookAction extends Action $replyto = $this->trimmed('inreplyto'); - $notice = Notice::saveNew($user->id, $content, - 'web', 1, ($replyto == 'false') ? null : $replyto); - - if (is_string($notice)) { - $this->showPage($notice); + try { + $notice = Notice::saveNew($user->id, $content, + 'web', 1, ($replyto == 'false') ? null : $replyto); + } catch (Exception $e) { + $this->showPage($e->getMessage()); return; } diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index aa01f6b1d..ce83295fb 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -109,11 +109,13 @@ class HTMLOutputter extends XMLOutputter header('Content-Type: '.$type); $this->extraHeaders(); - if( ! substr($type,0,strlen('text/html'))=='text/html' ){ - // Browsers don't like it when <?xml it output for non-xhtml documents + if (preg_match("/.*\/.*xml/", $type)) { + // Required for XML documents $this->xw->startDocument('1.0', 'UTF-8'); } - $this->xw->writeDTD('html'); + $this->xw->writeDTD('html', + '-//W3C//DTD XHTML 1.0 Strict//EN', + 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'); $language = $this->getLanguage(); @@ -425,16 +427,12 @@ class HTMLOutputter extends XMLOutputter function autofocus($id) { $this->elementStart('script', array('type' => 'text/javascript')); - $this->raw(' - <!-- - $(document).ready(function() { - var el = $("#' . $id . '"); - if (el.length) { - el.focus(); - } - }); - --> - '); + $this->raw('/*<![CDATA[*/'. + ' $(document).ready(function() {'. + ' var el = $("#' . $id . '");'. + ' if (el.length) { el.focus(); }'. + ' });'. + ' /*]]>*/'); $this->elementEnd('script'); } } diff --git a/lib/mail.php b/lib/mail.php index df585406c..5bf4d7425 100644 --- a/lib/mail.php +++ b/lib/mail.php @@ -551,9 +551,9 @@ function mail_notify_fave($other, $user, $notice) common_init_locale($other->language); - $subject = sprintf(_('%s added your notice as a favorite'), $bestname); + $subject = sprintf(_('%s (@%s) added your notice as a favorite'), $bestname, $user->nickname); - $body = sprintf(_("%1\$s just added your notice from %2\$s". + $body = sprintf(_("%1\$s (@%7\$s) just added your notice from %2\$s". " as one of their favorites.\n\n" . "The URL of your notice is:\n\n" . "%3\$s\n\n" . @@ -570,7 +570,8 @@ function mail_notify_fave($other, $user, $notice) $notice->content, common_local_url('showfavorites', array('nickname' => $user->nickname)), - common_config('site', 'name')); + common_config('site', 'name'), + $user->nickname); common_init_locale(); mail_to_user($other, $subject, $body); @@ -607,9 +608,9 @@ function mail_notify_attn($user, $notice) $conversationUrl = null; } - $subject = sprintf(_('%s sent a notice to your attention'), $bestname); + $subject = sprintf(_('%s (@%s) sent a notice to your attention'), $bestname, $sender->nickname); - $body = sprintf(_("%1\$s just sent a notice to your attention (an '@-reply') on %2\$s.\n\n". + $body = sprintf(_("%1\$s (@%9\$s) just sent a notice to your attention (an '@-reply') on %2\$s.\n\n". "The notice is here:\n\n". "\t%3\$s\n\n" . "It reads:\n\n". @@ -629,10 +630,11 @@ function mail_notify_attn($user, $notice) $notice->content,//%4 $conversationUrl,//%5 common_local_url('newnotice', - array('replyto' => $sender->nickname)),//%6 + array('replyto' => $sender->nickname, 'inreplyto' => $notice->id)),//%6 common_local_url('replies', array('nickname' => $user->nickname)),//%7 - common_local_url('emailsettings'));//%8 + common_local_url('emailsettings'), //%8 + $sender->nickname); //%9 common_init_locale(); mail_to_user($user, $subject, $body); diff --git a/lib/noticeform.php b/lib/noticeform.php index cee46709e..9864d15eb 100644 --- a/lib/noticeform.php +++ b/lib/noticeform.php @@ -70,6 +70,12 @@ class NoticeForm extends Form var $user = null; /** + * The notice being replied to + */ + + var $inreplyto = null; + + /** * Constructor * * @param HTMLOutputter $out output channel @@ -77,12 +83,13 @@ class NoticeForm extends Form * @param string $content content to pre-fill */ - function __construct($out=null, $action=null, $content=null, $user=null) + function __construct($out=null, $action=null, $content=null, $user=null, $inreplyto=null) { parent::__construct($out); $this->action = $action; $this->content = $content; + $this->inreplyto = $inreplyto; if ($user) { $this->user = $user; @@ -135,40 +142,44 @@ class NoticeForm extends Form function formData() { - $this->out->element('label', array('for' => 'notice_data-text'), - sprintf(_('What\'s up, %s?'), $this->user->nickname)); - // XXX: vary by defined max size - $this->out->element('textarea', array('id' => 'notice_data-text', - 'cols' => 35, - 'rows' => 4, - 'name' => 'status_textarea'), - ($this->content) ? $this->content : ''); - - $contentLimit = Notice::maxContent(); - - $this->out->element('script', array('type' => 'text/javascript'), - 'maxLength = ' . $contentLimit . ';'); - - if ($contentLimit > 0) { - $this->out->elementStart('dl', 'form_note'); - $this->out->element('dt', null, _('Available characters')); - $this->out->element('dd', array('id' => 'notice_text-count'), - $contentLimit); - $this->out->elementEnd('dl'); - } - - if (common_config('attachments', 'uploads')) { - $this->out->element('label', array('for' => 'notice_data-attach'),_('Attach')); - $this->out->element('input', array('id' => 'notice_data-attach', - 'type' => 'file', - 'name' => 'attach', - 'title' => _('Attach a file'))); - $this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota')); - } - if ($this->action) { - $this->out->hidden('notice_return-to', $this->action, 'returnto'); + if (Event::handle('StartShowNoticeFormData', array($this))) { + $this->out->element('label', array('for' => 'notice_data-text'), + sprintf(_('What\'s up, %s?'), $this->user->nickname)); + // XXX: vary by defined max size + $this->out->element('textarea', array('id' => 'notice_data-text', + 'cols' => 35, + 'rows' => 4, + 'name' => 'status_textarea'), + ($this->content) ? $this->content : ''); + + $contentLimit = Notice::maxContent(); + + $this->out->element('script', array('type' => 'text/javascript'), + 'maxLength = ' . $contentLimit . ';'); + + if ($contentLimit > 0) { + $this->out->elementStart('dl', 'form_note'); + $this->out->element('dt', null, _('Available characters')); + $this->out->element('dd', array('id' => 'notice_text-count'), + $contentLimit); + $this->out->elementEnd('dl'); + } + + if (common_config('attachments', 'uploads')) { + $this->out->element('label', array('for' => 'notice_data-attach'),_('Attach')); + $this->out->element('input', array('id' => 'notice_data-attach', + 'type' => 'file', + 'name' => 'attach', + 'title' => _('Attach a file'))); + $this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota')); + } + if ($this->action) { + $this->out->hidden('notice_return-to', $this->action, 'returnto'); + } + $this->out->hidden('notice_in-reply-to', $this->inreplyto, 'inreplyto'); + + Event::handle('StartShowNoticeFormData', array($this)); } - $this->out->hidden('notice_in-reply-to', $this->action, 'inreplyto'); } /** diff --git a/lib/noticelist.php b/lib/noticelist.php index ec85e4a5c..6c296f82a 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -178,9 +178,12 @@ class NoticeListItem extends Widget function show() { $this->showStart(); - $this->showNotice(); - $this->showNoticeInfo(); - $this->showNoticeOptions(); + if (Event::handle('StartShowNoticeItem', array($this))) { + $this->showNotice(); + $this->showNoticeInfo(); + $this->showNoticeOptions(); + Event::handle('EndShowNoticeItem', array($this)); + } $this->showEnd(); } @@ -261,7 +264,7 @@ class NoticeListItem extends Widget $attrs = array('href' => $this->profile->profileurl, 'class' => 'url'); if (!empty($this->profile->fullname)) { - $attrs['title'] = $this->profile->fullname . ' (' . $this->profile->nickname . ') '; + $attrs['title'] = $this->profile->fullname . ' (' . $this->profile->nickname . ')'; } $this->out->elementStart('a', $attrs); $this->showAvatar(); @@ -418,9 +421,17 @@ class NoticeListItem extends Widget function showContext() { - // XXX: also show context if there are replies to this notice - if (!empty($this->notice->conversation) - && $this->notice->conversation != $this->notice->id) { + $hasConversation = false; + if( !empty($this->notice->conversation) + && $this->notice->conversation != $this->notice->id){ + $hasConversation = true; + }else{ + $conversation = Notice::conversationStream($this->notice->id, 1, 1); + if($conversation->N > 0){ + $hasConversation = true; + } + } + if ($hasConversation){ $convurl = common_local_url('conversation', array('id' => $this->notice->conversation)); $this->out->element('a', array('href' => $convurl.'#notice-'.$this->notice->id, @@ -442,7 +453,7 @@ class NoticeListItem extends Widget { if (common_logged_in()) { $reply_url = common_local_url('newnotice', - array('replyto' => $this->profile->nickname)); + array('replyto' => $this->profile->nickname, 'inreplyto' => $this->notice->id)); $this->out->elementStart('a', array('href' => $reply_url, 'class' => 'notice_reply', 'title' => _('Reply to this notice'))); @@ -461,7 +472,10 @@ class NoticeListItem extends Widget function showDeleteLink() { $user = common_current_user(); - if ($user && $this->notice->profile_id == $user->id) { + + if (!empty($user) && + ($this->notice->profile_id == $user->id || $user->hasRight(Right::deleteOthersNotice))) { + $deleteurl = common_local_url('deletenotice', array('notice' => $this->notice->id)); $this->out->element('a', array('href' => $deleteurl, diff --git a/lib/oauthstore.php b/lib/oauthstore.php index e69a00f55..d617a7df7 100644 --- a/lib/oauthstore.php +++ b/lib/oauthstore.php @@ -156,7 +156,6 @@ class StatusNetOAuthDataStore extends OAuthDataStore return $this->new_access_token($consumer); } - /** * Revoke specified OAuth token * @@ -363,9 +362,7 @@ class StatusNetOAuthDataStore extends OAuthDataStore false, null, $omb_notice->getIdentifierURI()); - if (is_string($notice)) { - throw new Exception($notice); - } + common_broadcast_notice($notice, true); } diff --git a/lib/right.php b/lib/right.php new file mode 100644 index 000000000..4e0096d46 --- /dev/null +++ b/lib/right.php @@ -0,0 +1,50 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Class for user rights + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category Authorization + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * class for rights + * + * Mostly for holding the rights constants + * + * @category Authorization + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class Right +{ + const deleteOthersNotice = 'deleteothersnotice'; +} + diff --git a/lib/router.php b/lib/router.php index f122c2b64..a5b6a9a30 100644 --- a/lib/router.php +++ b/lib/router.php @@ -168,6 +168,10 @@ class Router $m->connect('notice/new?replyto=:replyto', array('action' => 'newnotice'), array('replyto' => '[A-Za-z0-9_-]+')); + $m->connect('notice/new?replyto=:replyto&inreplyto=:inreplyto', + array('action' => 'newnotice'), + array('replyto' => '[A-Za-z0-9_-]+'), + array('inreplyto' => '[0-9]+')); $m->connect('notice/:notice/file', array('action' => 'file'), @@ -233,6 +237,10 @@ class Router array('nickname' => '[a-zA-Z0-9]+')); } + $m->connect('group/:nickname/foaf', + array('action' => 'foafgroup'), + array('nickname' => '[a-zA-Z0-9]+')); + $m->connect('group/:nickname/blocked', array('action' => 'blockedfromgroup'), array('nickname' => '[a-zA-Z0-9]+')); @@ -258,22 +266,100 @@ class Router // statuses API - $m->connect('api/statuses/:method', - array('action' => 'api', - 'apiaction' => 'statuses'), - array('method' => '(public_timeline|friends_timeline|user_timeline|update|replies|mentions|show|friends|followers|featured)(\.(atom|rss|xml|json))?')); - - $m->connect('api/statuses/:method/:argument', - array('action' => 'api', - 'apiaction' => 'statuses'), - array('method' => '(|user_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)')); + $m->connect('api/statuses/public_timeline.:format', + array('action' => 'ApiTimelinePublic', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/friends_timeline.:format', + array('action' => 'ApiTimelineFriends', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/friends_timeline/:id.:format', + array('action' => 'ApiTimelineFriends', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + $m->connect('api/statuses/home_timeline.:format', + array('action' => 'ApiTimelineFriends', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/home_timeline/:id.:format', + array('action' => 'ApiTimelineFriends', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/user_timeline.:format', + array('action' => 'ApiTimelineUser', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/user_timeline/:id.:format', + array('action' => 'ApiTimelineUser', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/mentions.:format', + array('action' => 'ApiTimelineMentions', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/mentions/:id.:format', + array('action' => 'ApiTimelineMentions', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/replies.:format', + array('action' => 'ApiTimelineMentions', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/replies/:id.:format', + array('action' => 'ApiTimelineMentions', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/friends.:format', + array('action' => 'ApiUserFriends', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/friends/:id.:format', + array('action' => 'ApiUserFriends', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/followers.:format', + array('action' => 'ApiUserFollowers', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/followers/:id.:format', + array('action' => 'ApiUserFollowers', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/show.:format', + array('action' => 'ApiStatusesShow', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/show/:id.:format', + array('action' => 'ApiStatusesShow', + 'id' => '[0-9]+', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/update.:format', + array('action' => 'ApiStatusesUpdate', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/destroy.:format', + array('action' => 'ApiStatusesDestroy', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/destroy/:id.:format', + array('action' => 'ApiStatusesDestroy', + 'id' => '[0-9]+', + 'format' => '(xml|json)')); // users - $m->connect('api/users/:method/:argument', - array('action' => 'api', - 'apiaction' => 'users'), - array('method' => 'show(\.(xml|json))?')); + $m->connect('api/users/show/:id.:format', + array('action' => 'ApiUserShow', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); $m->connect('api/users/:method', array('action' => 'api', @@ -282,93 +368,99 @@ class Router // direct messages - foreach (array('xml', 'json') as $e) { - $m->connect('api/direct_messages/new.'.$e, - array('action' => 'api', - 'apiaction' => 'direct_messages', - 'method' => 'create.'.$e)); - } - foreach (array('xml', 'json', 'rss', 'atom') as $e) { - $m->connect('api/direct_messages.'.$e, - array('action' => 'api', - 'apiaction' => 'direct_messages', - 'method' => 'direct_messages.'.$e)); - } + $m->connect('api/direct_messages.:format', + array('action' => 'ApiDirectMessage', + 'format' => '(xml|json|rss|atom)')); - foreach (array('xml', 'json', 'rss', 'atom') as $e) { - $m->connect('api/direct_messages/sent.'.$e, - array('action' => 'api', - 'apiaction' => 'direct_messages', - 'method' => 'sent.'.$e)); - } + $m->connect('api/direct_messages/sent.:format', + array('action' => 'ApiDirectMessage', + 'format' => '(xml|json|rss|atom)', + 'sent' => true)); - $m->connect('api/direct_messages/destroy/:argument', - array('action' => 'api', - 'apiaction' => 'direct_messages')); + $m->connect('api/direct_messages/new.:format', + array('action' => 'ApiDirectMessageNew', + 'format' => '(xml|json)')); // friendships - $m->connect('api/friendships/:method/:argument', - array('action' => 'api', - 'apiaction' => 'friendships'), - array('method' => '(create|destroy)')); + $m->connect('api/friendships/show.:format', + array('action' => 'ApiFriendshipsShow', + 'format' => '(xml|json)')); - $m->connect('api/friendships/:method', - array('action' => 'api', - 'apiaction' => 'friendships'), - array('method' => '(show|exists)(\.(xml|json))')); + $m->connect('api/friendships/exists.:format', + array('action' => 'ApiFriendshipsExists', + 'format' => '(xml|json)')); + + $m->connect('api/friendships/create.:format', + array('action' => 'ApiFriendshipsCreate', + 'format' => '(xml|json)')); + + $m->connect('api/friendships/destroy.:format', + array('action' => 'ApiFriendshipsDestroy', + 'format' => '(xml|json)')); + + $m->connect('api/friendships/create/:id.:format', + array('action' => 'ApiFriendshipsCreate', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); + + $m->connect('api/friendships/destroy/:id.:format', + array('action' => 'ApiFriendshipsDestroy', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); // Social graph - $m->connect('api/friends/ids/:argument', - array('action' => 'api', - 'apiaction' => 'statuses', - 'method' => 'friendsIDs')); - - foreach (array('xml', 'json') as $e) { - $m->connect('api/friends/ids.'.$e, - array('action' => 'api', - 'apiaction' => 'statuses', - 'method' => 'friendsIDs.'.$e)); - } + $m->connect('api/friends/ids/:id.:format', + array('action' => 'apiFriends', + 'ids_only' => true)); - $m->connect('api/followers/ids/:argument', - array('action' => 'api', - 'apiaction' => 'statuses', - 'method' => 'followersIDs')); - - foreach (array('xml', 'json') as $e) { - $m->connect('api/followers/ids.'.$e, - array('action' => 'api', - 'apiaction' => 'statuses', - 'method' => 'followersIDs.'.$e)); - } + $m->connect('api/followers/ids/:id.:format', + array('action' => 'apiFollowers', + 'ids_only' => true)); + + $m->connect('api/friends/ids.:format', + array('action' => 'apiFriends', + 'ids_only' => true)); + + $m->connect('api/followers/ids.:format', + array('action' => 'apiFollowers', + 'ids_only' => true)); // account - $m->connect('api/account/:method', - array('action' => 'api', - 'apiaction' => 'account')); + $m->connect('api/account/verify_credentials.:format', + array('action' => 'ApiAccountVerifyCredentials')); + + // special case where verify_credentials is called w/out a format + + $m->connect('api/account/verify_credentials', + array('action' => 'ApiAccountVerifyCredentials')); + + $m->connect('api/account/rate_limit_status.:format', + array('action' => 'ApiAccountRateLimitStatus')); // favorites - $m->connect('api/favorites/:method/:argument', - array('action' => 'api', - 'apiaction' => 'favorites', - array('method' => '(create|destroy)'))); + $m->connect('api/favorites.:format', + array('action' => 'ApiTimelineFavorites', + 'format' => '(xml|json|rss|atom)')); - $m->connect('api/favorites/:argument', - array('action' => 'api', - 'apiaction' => 'favorites', - 'method' => 'favorites')); - - foreach (array('xml', 'json', 'rss', 'atom') as $e) { - $m->connect('api/favorites.'.$e, - array('action' => 'api', - 'apiaction' => 'favorites', - 'method' => 'favorites.'.$e)); - } + $m->connect('api/favorites/:id.:format', + array('action' => 'ApiTimelineFavorites', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xmljson|rss|atom)')); + + $m->connect('api/favorites/create/:id.:format', + array('action' => 'ApiFavoriteCreate', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); + + $m->connect('api/favorites/destroy/:id.:format', + array('action' => 'ApiFavoriteDestroy', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); // notifications @@ -378,71 +470,112 @@ class Router // blocks - $m->connect('api/blocks/:method/:argument', - array('action' => 'api', - 'apiaction' => 'blocks')); + $m->connect('api/blocks/create/:id.:format', + array('action' => 'ApiBlockCreate', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); + $m->connect('api/blocks/destroy/:id.:format', + array('action' => 'ApiBlockDestroy', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); // help - $m->connect('api/help/:method', - array('action' => 'api', - 'apiaction' => 'help')); + $m->connect('api/help/test.:format', + array('action' => 'ApiHelpTest', + 'format' => '(xml|json)')); // statusnet - $m->connect('api/statusnet/:method', - array('action' => 'api', - 'apiaction' => 'statusnet')); + $m->connect('api/statusnet/version.:format', + array('action' => 'ApiStatusnetVersion', + 'format' => '(xml|json)')); + + $m->connect('api/statusnet/config.:format', + array('action' => 'ApiStatusnetConfig', + 'format' => '(xml|json)')); // For older methods, we provide "laconica" base action - $m->connect('api/laconica/:method', - array('action' => 'api', - 'apiaction' => 'statusnet')); + $m->connect('api/laconica/version.:format', + array('action' => 'ApiStatusnetVersion', + 'format' => '(xml|json)')); + + $m->connect('api/laconica/config.:format', + array('action' => 'ApiStatusnetConfig', + 'format' => '(xml|json)')); // Groups and tags are newer than 0.8.1 so no backward-compatibility // necessary // Groups //'list' has to be handled differently, as php will not allow a method to be named 'list' - $m->connect('api/statusnet/groups/list/:argument', - array('action' => 'api', - 'method' => 'list_groups', - 'apiaction' => 'groups')); - - foreach (array('xml', 'json', 'rss', 'atom') as $e) { - $m->connect('api/statusnet/groups/list.' . $e, - array('action' => 'api', - 'method' => 'list_groups.' . $e, - 'apiaction' => 'groups')); - } - - $m->connect('api/statusnet/groups/:method', - array('action' => 'api', - 'apiaction' => 'statuses'), - array('method' => '(list_all|)(\.(atom|rss|xml|json))?')); - - $m->connect('api/statuses/:method/:argument', - array('action' => 'api', - 'apiaction' => 'statuses'), - array('method' => '(|user_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)')); - - $m->connect('api/statusnet/groups/:method/:argument', - array('action' => 'api', - 'apiaction' => 'groups')); - - $m->connect('api/statusnet/groups/:method', - array('action' => 'api', - 'apiaction' => 'groups')); + $m->connect('api/statusnet/groups/timeline/:id.:format', + array('action' => 'ApiTimelineGroup', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xmljson|rss|atom)')); + + $m->connect('api/statusnet/groups/show.:format', + array('action' => 'ApiGroupShow', + 'format' => '(xml|json)')); + + $m->connect('api/statusnet/groups/show/:id.:format', + array('action' => 'ApiGroupShow', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); + + $m->connect('api/statusnet/groups/join.:format', + array('action' => 'ApiGroupJoin', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); + + $m->connect('api/statusnet/groups/join/:id.:format', + array('action' => 'ApiGroupJoin', + 'format' => '(xml|json)')); + + $m->connect('api/statusnet/groups/leave.:format', + array('action' => 'ApiGroupLeave', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); + + $m->connect('api/statusnet/groups/leave/:id.:format', + array('action' => 'ApiGroupLeave', + 'format' => '(xml|json)')); + + $m->connect('api/statusnet/groups/is_member.:format', + array('action' => 'ApiGroupIsMember', + 'format' => '(xml|json)')); + + $m->connect('api/statusnet/groups/list.:format', + array('action' => 'ApiGroupList', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statusnet/groups/list/:id.:format', + array('action' => 'ApiGroupList', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statusnet/groups/list_all.:format', + array('action' => 'ApiGroupListAll', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statusnet/groups/membership.:format', + array('action' => 'ApiGroupMembership', + 'format' => '(xml|json)')); + + $m->connect('api/statusnet/groups/membership/:id.:format', + array('action' => 'ApiGroupMembership', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); + + $m->connect('api/statusnet/groups/create.:format', + array('action' => 'ApiGroupCreate', + 'format' => '(xml|json)')); // Tags - $m->connect('api/statusnet/tags/:method/:argument', - array('action' => 'api', - 'apiaction' => 'tags')); - - $m->connect('api/statusnet/tags/:method', - array('action' => 'api', - 'apiaction' => 'tags')); + $m->connect('api/statusnet/tags/timeline/:tag.:format', + array('action' => 'ApiTimelineTag', + 'format' => '(xmljson|rss|atom)')); // search $m->connect('api/search.atom', array('action' => 'twitapisearchatom')); diff --git a/lib/rssaction.php b/lib/rssaction.php index dd0f1005a..faf6bec7d 100644 --- a/lib/rssaction.php +++ b/lib/rssaction.php @@ -78,25 +78,12 @@ class Rss10Action extends Action function prepare($args) { parent::prepare($args); + $this->limit = (int) $this->trimmed('limit'); + if ($this->limit == 0) { $this->limit = DEFAULT_RSS_LIMIT; } - return true; - } - - /** - * Handle a request - * - * @param array $args Arguments from $_REQUEST - * - * @return void - */ - - function handle($args) - { - // Parent handling, including cache check - parent::handle($args); if (common_config('site', 'private')) { if (!isset($_SERVER['PHP_AUTH_USER'])) { @@ -122,8 +109,21 @@ class Rss10Action extends Action } } - // Get the list of notices - $this->notices = $this->getNotices($this->limit); + return true; + } + + /** + * Handle a request + * + * @param array $args Arguments from $_REQUEST + * + * @return void + */ + + function handle($args) + { + // Parent handling, including cache check + parent::handle($args); $this->showRss(); } @@ -140,7 +140,7 @@ class Rss10Action extends Action } /** - * Get the notices to output in this stream + * Get the notices to output in this stream. * * @return array an array of Notice objects sorted in reverse chron */ diff --git a/lib/schema.php b/lib/schema.php new file mode 100644 index 000000000..1e0c1f3e9 --- /dev/null +++ b/lib/schema.php @@ -0,0 +1,680 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Database schema utilities + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category Database + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Class representing the database schema + * + * A class representing the database schema. Can be used to + * manipulate the schema -- especially for plugins and upgrade + * utilities. + * + * @category Database + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class Schema +{ + static $_single = null; + protected $conn = null; + + /** + * Constructor. Only run once for singleton object. + */ + + protected function __construct() + { + // XXX: there should be an easier way to do this. + $user = new User(); + + $this->conn = $user->getDatabaseConnection(); + + $user->free(); + + unset($user); + } + + /** + * Main public entry point. Use this to get + * the singleton object. + * + * @return Schema the (single) Schema object + */ + + static function get() + { + if (empty(self::$_single)) { + self::$_single = new Schema(); + } + return self::$_single; + } + + /** + * Returns a TableDef object for the table + * in the schema with the given name. + * + * Throws an exception if the table is not found. + * + * @param string $name Name of the table to get + * + * @return TableDef tabledef for that table. + */ + + public function getTableDef($name) + { + $res =& $this->conn->query('DESCRIBE ' . $name); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + $td = new TableDef(); + + $td->name = $name; + $td->columns = array(); + + $row = array(); + + while ($res->fetchInto($row, DB_FETCHMODE_ASSOC)) { + + $cd = new ColumnDef(); + + $cd->name = $row['Field']; + + $packed = $row['Type']; + + if (preg_match('/^(\w+)\((\d+)\)$/', $packed, $match)) { + $cd->type = $match[1]; + $cd->size = $match[2]; + } else { + $cd->type = $packed; + } + + $cd->nullable = ($row['Null'] == 'YES') ? true : false; + $cd->key = $row['Key']; + $cd->default = $row['Default']; + $cd->extra = $row['Extra']; + + $td->columns[] = $cd; + } + + return $td; + } + + /** + * Gets a ColumnDef object for a single column. + * + * Throws an exception if the table is not found. + * + * @param string $table name of the table + * @param string $column name of the column + * + * @return ColumnDef definition of the column or null + * if not found. + */ + + public function getColumnDef($table, $column) + { + $td = $this->getTableDef($table); + + foreach ($td->columns as $cd) { + if ($cd->name == $column) { + return $cd; + } + } + + return null; + } + + /** + * Creates a table with the given names and columns. + * + * @param string $name Name of the table + * @param array $columns Array of ColumnDef objects + * for new table. + * + * @return boolean success flag + */ + + public function createTable($name, $columns) + { + $uniques = array(); + $primary = array(); + $indices = array(); + + $sql = "CREATE TABLE $name (\n"; + + for ($i = 0; $i < count($columns); $i++) { + + $cd =& $columns[$i]; + + if ($i > 0) { + $sql .= ",\n"; + } + + $sql .= $this->_columnSql($cd); + + switch ($cd->key) { + case 'UNI': + $uniques[] = $cd->name; + break; + case 'PRI': + $primary[] = $cd->name; + break; + case 'MUL': + $indices[] = $cd->name; + break; + } + } + + if (count($primary) > 0) { // it really should be... + $sql .= ",\nconstraint primary key (" . implode(',', $primary) . ")"; + } + + foreach ($uniques as $u) { + $sql .= ",\nunique index {$name}_{$u}_idx ($u)"; + } + + foreach ($indices as $i) { + $sql .= ",\nindex {$name}_{$i}_idx ($i)"; + } + + $sql .= "); "; + + $res =& $this->conn->query($sql); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Drops a table from the schema + * + * Throws an exception if the table is not found. + * + * @param string $name Name of the table to drop + * + * @return boolean success flag + */ + + public function dropTable($name) + { + $res =& $this->conn->query("DROP TABLE $name"); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Adds an index to a table. + * + * If no name is provided, a name will be made up based + * on the table name and column names. + * + * Throws an exception on database error, esp. if the table + * does not exist. + * + * @param string $table Name of the table + * @param array $columnNames Name of columns to index + * @param string $name (Optional) name of the index + * + * @return boolean success flag + */ + + public function createIndex($table, $columnNames, $name=null) + { + if (!is_array($columnNames)) { + $columnNames = array($columnNames); + } + + if (empty($name)) { + $name = "$table_".implode("_", $columnNames)."_idx"; + } + + $res =& $this->conn->query("ALTER TABLE $table ". + "ADD INDEX $name (". + implode(",", $columnNames).")"); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Drops a named index from a table. + * + * @param string $table name of the table the index is on. + * @param string $name name of the index + * + * @return boolean success flag + */ + + public function dropIndex($table, $name) + { + $res =& $this->conn->query("ALTER TABLE $table DROP INDEX $name"); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Adds a column to a table + * + * @param string $table name of the table + * @param ColumnDef $columndef Definition of the new + * column. + * + * @return boolean success flag + */ + + public function addColumn($table, $columndef) + { + $sql = "ALTER TABLE $table ADD COLUMN " . $this->_columnSql($columndef); + + $res =& $this->conn->query($sql); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Modifies a column in the schema. + * + * The name must match an existing column and table. + * + * @param string $table name of the table + * @param ColumnDef $columndef new definition of the column. + * + * @return boolean success flag + */ + + public function modifyColumn($table, $columndef) + { + $sql = "ALTER TABLE $table MODIFY COLUMN " . + $this->_columnSql($columndef); + + $res =& $this->conn->query($sql); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Drops a column from a table + * + * The name must match an existing column. + * + * @param string $table name of the table + * @param string $columnName name of the column to drop + * + * @return boolean success flag + */ + + public function dropColumn($table, $columnName) + { + $sql = "ALTER TABLE $table DROP COLUMN $columnName"; + + $res =& $this->conn->query($sql); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Ensures that a table exists with the given + * name and the given column definitions. + * + * If the table does not yet exist, it will + * create the table. If it does exist, it will + * alter the table to match the column definitions. + * + * @param string $tableName name of the table + * @param array $columns array of ColumnDef + * objects for the table + * + * @return boolean success flag + */ + + public function ensureTable($tableName, $columns) + { + // XXX: DB engine portability -> toilet + + try { + $td = $this->getTableDef($tableName); + } catch (Exception $e) { + if (preg_match('/no such table/', $e->getMessage())) { + return $this->createTable($tableName, $columns); + } else { + throw $e; + } + } + + $cur = $this->_names($td->columns); + $new = $this->_names($columns); + + $toadd = array_diff($new, $cur); + $todrop = array_diff($cur, $new); + $same = array_intersect($new, $cur); + $tomod = array(); + + foreach ($same as $m) { + $curCol = $this->_byName($td->columns, $m); + $newCol = $this->_byName($columns, $m); + + if (!$newCol->equals($curCol)) { + $tomod[] = $newCol->name; + } + } + + if (count($toadd) + count($todrop) + count($tomod) == 0) { + // nothing to do + return true; + } + + // For efficiency, we want this all in one + // query, instead of using our methods. + + $phrase = array(); + + foreach ($toadd as $columnName) { + $cd = $this->_byName($columns, $columnName); + + $phrase[] = 'ADD COLUMN ' . $this->_columnSql($cd); + } + + foreach ($todrop as $columnName) { + $phrase[] = 'DROP COLUMN ' . $columnName; + } + + foreach ($tomod as $columnName) { + $cd = $this->_byName($columns, $columnName); + + $phrase[] = 'MODIFY COLUMN ' . $this->_columnSql($cd); + } + + $sql = 'ALTER TABLE ' . $tableName . ' ' . implode(', ', $phrase); + + $res =& $this->conn->query($sql); + + if (PEAR::isError($res)) { + throw new Exception($res->getMessage()); + } + + return true; + } + + /** + * Returns the array of names from an array of + * ColumnDef objects. + * + * @param array $cds array of ColumnDef objects + * + * @return array strings for name values + */ + + private function _names($cds) + { + $names = array(); + + foreach ($cds as $cd) { + $names[] = $cd->name; + } + + return $names; + } + + /** + * Get a ColumnDef from an array matching + * name. + * + * @param array $cds Array of ColumnDef objects + * @param string $name Name of the column + * + * @return ColumnDef matching item or null if no match. + */ + + private function _byName($cds, $name) + { + foreach ($cds as $cd) { + if ($cd->name == $name) { + return $cd; + } + } + + return null; + } + + /** + * Return the proper SQL for creating or + * altering a column. + * + * Appropriate for use in CREATE TABLE or + * ALTER TABLE statements. + * + * @param ColumnDef $cd column to create + * + * @return string correct SQL for that column + */ + + private function _columnSql($cd) + { + $sql = "{$cd->name} "; + + if (!empty($cd->size)) { + $sql .= "{$cd->type}({$cd->size}) "; + } else { + $sql .= "{$cd->type} "; + } + + if (!empty($cd->default)) { + $sql .= "default {$cd->default} "; + } else { + $sql .= ($cd->nullable) ? "null " : "not null "; + } + + return $sql; + } +} + +/** + * A class encapsulating the structure of a table. + * + * @category Database + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class TableDef +{ + /** name of the table */ + public $name; + /** array of ColumnDef objects for the columns. */ + public $columns; +} + +/** + * A class encapsulating the structure of a column in a table. + * + * @category Database + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ColumnDef +{ + /** name of the column. */ + public $name; + /** type of column, e.g. 'int', 'varchar' */ + public $type; + /** size of the column. */ + public $size; + /** boolean flag; can it be null? */ + public $nullable; + /** + * type of key: null = no key; 'PRI' => primary; + * 'UNI' => unique key; 'MUL' => multiple values. + */ + public $key; + /** default value if any. */ + public $default; + /** 'extra' stuff. Returned by MySQL, largely + * unused. */ + public $extra; + + /** + * Constructor. + * + * @param string $name name of the column + * @param string $type type of the column + * @param int $size size of the column + * @param boolean $nullable can this be null? + * @param string $key type of key + * @param value $default default value + * @param value $extra unused + */ + + function __construct($name=null, $type=null, $size=null, + $nullable=true, $key=null, $default=null, + $extra=null) + { + $this->name = strtolower($name); + $this->type = strtolower($type); + $this->size = $size+0; + $this->nullable = $nullable; + $this->key = $key; + $this->default = $default; + $this->extra = $extra; + } + + /** + * Compares this columndef with another to see + * if they're functionally equivalent. + * + * @param ColumnDef $other column to compare + * + * @return boolean true if equivalent, otherwise false. + */ + + function equals($other) + { + return ($this->name == $other->name && + $this->_typeMatch($other) && + $this->_defaultMatch($other) && + $this->_nullMatch($other) && + $this->key == $other->key); + } + + /** + * Does the type of this column match the + * type of the other column? + * + * Checks the type and size of a column. Tries + * to ignore differences between synonymous + * data types, like 'integer' and 'int'. + * + * @param ColumnDef $other other column to check + * + * @return boolean true if they're about equivalent + */ + + private function _typeMatch($other) + { + switch ($this->type) { + case 'integer': + case 'int': + return ($other->type == 'integer' || + $other->type == 'int'); + break; + default: + return ($this->type == $other->type && + $this->size == $other->size); + } + } + + /** + * Does the default behaviour of this column match + * the other? + * + * @param ColumnDef $other other column to check + * + * @return boolean true if defaults are effectively the same. + */ + + private function _defaultMatch($other) + { + return ((is_null($this->default) && is_null($other->default)) || + ($this->default == $other->default)); + } + + /** + * Does the null behaviour of this column match + * the other? + * + * @param ColumnDef $other other column to check + * + * @return boolean true if these columns 'null' the same. + */ + + private function _nullMatch($other) + { + return ((!is_null($this->default) && !is_null($other->default) && + $this->default == $other->default) || + ($this->nullable == $other->nullable)); + } +} diff --git a/lib/searchaction.php b/lib/searchaction.php index 0d9f85a8f..130b28ff5 100644 --- a/lib/searchaction.php +++ b/lib/searchaction.php @@ -135,16 +135,21 @@ class SearchAction extends Action } function searchSuggestions($q) { - $qe = urlencode($q); - $message = sprintf(_(<<<E_O_T + $message = _(<<<E_O_T * Make sure all words are spelled correctly. * Try different keywords. * Try more general keywords. * Try fewer keywords. +E_O_T +); + if (!common_config('site', 'private')) { + $qe = urlencode($q); + $message .= sprintf(_(<<<E_O_T + You can also try your search on other engines: -* [Twingly](http://www.twingly.com/search?q=%s&content=microblog&site=identi.ca) +* [Twingly](http://www.twingly.com/search?q=%s&content=microblog&site=%%%%site.server%%%%) * [Tweet scan](http://www.tweetscan.com/indexi.php?s=%s) * [Google](http://www.google.com/search?q=site%%3A%%%%site.server%%%%+%s) * [Yahoo](http://search.yahoo.com/search?p=site%%3A%%%%site.server%%%%+%s) @@ -152,6 +157,7 @@ You can also try your search on other engines: E_O_T ), $qe, $qe, $qe, $qe, $qe); + } $this->elementStart('dl', array('id' => 'help_search', 'class' => 'help')); $this->element('dt', null, _('Search help')); $this->elementStart('dd', 'instructions'); diff --git a/lib/servererroraction.php b/lib/servererroraction.php index c6400605e..0993a63bc 100644 --- a/lib/servererroraction.php +++ b/lib/servererroraction.php @@ -55,17 +55,17 @@ require_once INSTALLDIR.'/lib/error.php'; class ServerErrorAction extends ErrorAction { + static $status = array(500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported'); + function __construct($message='Error', $code=500) { parent::__construct($message, $code); - $this->status = array(500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Bad Gateway', - 503 => 'Service Unavailable', - 504 => 'Gateway Timeout', - 505 => 'HTTP Version Not Supported'); - $this->default = 500; // Server errors must be logged. @@ -93,9 +93,4 @@ class ServerErrorAction extends ErrorAction $this->showPage(); } - - function title() - { - return $this->status[$this->code]; - } } diff --git a/lib/unqueuemanager.php b/lib/unqueuemanager.php index c5dc29d38..6cfe5bcbd 100644 --- a/lib/unqueuemanager.php +++ b/lib/unqueuemanager.php @@ -72,8 +72,13 @@ class UnQueueManager require_once(INSTALLDIR.'/lib/jabber.php'); jabber_broadcast_notice($notice); break; + case 'plugin': + Event::handle('HandleQueuedNotice', array(&$notice)); + break; default: - throw ServerException("UnQueueManager: Unknown queue: $type"); + if (Event::handle('UnqueueHandleNotice', array(&$notice, $queue))) { + throw ServerException("UnQueueManager: Unknown queue: $queue"); + } } } diff --git a/lib/util.php b/lib/util.php index b74dc619c..be10647fc 100644 --- a/lib/util.php +++ b/lib/util.php @@ -391,10 +391,10 @@ function common_render_content($text, $notice) { $r = common_render_text($text); $id = $notice->profile_id; - $r = preg_replace('/(^|\s+)@([A-Za-z0-9]{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r); + $r = preg_replace('/(^|[\s\.\,\:\;]+)@([A-Za-z0-9]{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r); $r = preg_replace('/^T ([A-Z0-9]{1,64}) /e', "'T '.common_at_link($id, '\\1').' '", $r); - $r = preg_replace('/(^|\s+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r); - $r = preg_replace('/(^|\s)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r); + $r = preg_replace('/(^|[\s\.\,\:\;]+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r); + $r = preg_replace('/(^|[\s\.\,\:\;]+)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r); return $r; } @@ -442,9 +442,9 @@ function common_replace_urls_callback($text, $callback, $notice_id = null) { ')'. '(?:'. '(?:\:\d+)?'. //:port - '(?:/[\pN\pL$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"]*)?'. // /path - '(?:\?[\pN\pL\$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"\/]*)?'. // ?query string - '(?:\#[\pN\pL$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"\/\?\#]*)?'. // #fragment + '(?:/[\pN\pL$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"@]*)?'. // /path + '(?:\?[\pN\pL\$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"@\/]*)?'. // ?query string + '(?:\#[\pN\pL$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\*\$\+\'\"\@/\?\#]*)?'. // #fragment ')(?<![\?\.\,\#\,])'. ')'. '#ixu'; @@ -493,7 +493,7 @@ function callback_helper($matches, $callback, $notice_id) { }while($original_url!=$url); if(empty($notice_id)){ - $result = call_user_func_array($callback,$url); + $result = call_user_func_array($callback, array($url)); }else{ $result = call_user_func_array($callback, array(array($url,$notice_id)) ); } @@ -522,21 +522,22 @@ function common_linkify($url) { if(strpos($url, '@') !== false && strpos($url, ':') === false) { //url is an email address without the mailto: protocol - return XMLStringer::estring('a', array('href' => "mailto:$url", 'rel' => 'external'), $url); - } + $canon = "mailto:$url"; + $longurl = "mailto:$url"; + }else{ - $canon = File_redirection::_canonUrl($url); + $canon = File_redirection::_canonUrl($url); - $longurl_data = File_redirection::where($url); - if (is_array($longurl_data)) { - $longurl = $longurl_data['url']; - } elseif (is_string($longurl_data)) { - $longurl = $longurl_data; - } else { - throw new ServerException("Can't linkify url '$url'"); + $longurl_data = File_redirection::where($canon); + if (is_array($longurl_data)) { + $longurl = $longurl_data['url']; + } elseif (is_string($longurl_data)) { + $longurl = $longurl_data; + } else { + throw new ServerException("Can't linkify url '$url'"); + } } - - $attrs = array('href' => $canon, 'rel' => 'external'); + $attrs = array('href' => $canon, 'title' => $longurl, 'rel' => 'external'); $is_attachment = false; $attachment_id = null; @@ -552,12 +553,13 @@ function common_linkify($url) { } if (!empty($f)) { - if (isset($f->filename)) { + if ($f->isEnclosure()) { $is_attachment = true; $attachment_id = $f->id; - } else { // if it has OEmbed info, it's an attachment, too + } else { $foe = File_oembed::staticGet('file_id', $f->id); if (!empty($foe)) { + // if it has OEmbed info, it's an attachment, too $is_attachment = true; $attachment_id = $f->id; @@ -897,7 +899,8 @@ function common_enqueue_notice($notice) 'twitter', 'facebook', 'ping'); - static $allTransports = array('sms'); + + static $allTransports = array('sms', 'plugin'); $transports = $allTransports; @@ -915,11 +918,16 @@ function common_enqueue_notice($notice) } } - $qm = QueueManager::get(); + if (Event::handle('StartEnqueueNotice', array($notice, &$transports))) { - foreach ($transports as $transport) - { - $qm->enqueue($notice, $transport); + $qm = QueueManager::get(); + + foreach ($transports as $transport) + { + $qm->enqueue($notice, $transport); + } + + Event::handle('EndEnqueueNotice', array($notice, $transports)); } return true; @@ -990,7 +998,7 @@ function common_set_returnto($url) function common_get_returnto() { common_ensure_session(); - return $_SESSION['returnto']; + return (array_key_exists('returnto', $_SESSION)) ? $_SESSION['returnto'] : null; } function common_timestamp() @@ -1374,18 +1382,19 @@ function common_shorten_url($long_url) $svc = $user->urlshorteningservice; } global $_shorteners; - if(! $_shorteners[$svc]){ + if (!isset($_shorteners[$svc])) { //the user selected service doesn't exist, so default to ur1.ca $svc = 'ur1.ca'; } + if (!isset($_shorteners[$svc])) { + // no shortener plugins installed. + return $long_url; + } $reflectionObj = new ReflectionClass($_shorteners[$svc]['callInfo'][0]); - $short_url_service = $reflectionObj->newInstanceArgs($_shorteners[$svc]['callInfo'][1]); + $short_url_service = $reflectionObj->newInstanceArgs($_shorteners[$svc]['callInfo'][1]); $short_url = $short_url_service->shorten($long_url); - if(substr($short_url,0,7)=='http://'){ - $short_url = substr($short_url,7); - } return $short_url; } diff --git a/plugins/Autocomplete/Autocomplete.js b/plugins/Autocomplete/Autocomplete.js index dfadea004..3eff685a8 100644 --- a/plugins/Autocomplete/Autocomplete.js +++ b/plugins/Autocomplete/Autocomplete.js @@ -1,38 +1,37 @@ $(document).ready(function(){ - $.getJSON($('address .url')[0].href+'/api/statuses/friends.json?user_id=' + current_user['id'] + '&lite=true&callback=?', - function(friends){ - $('#notice_data-text').autocomplete(friends, { + $('#notice_data-text').autocomplete($('address .url')[0].href+'/plugins/Autocomplete/autocomplete.json', { multiple: true, multipleSeparator: " ", minChars: 1, formatItem: function(row, i, max){ - return '@' + row.screen_name + ' (' + row.name + ')'; + row = eval("(" + row + ")"); + switch(row.type) + { + case 'user': + return row.nickname + ' (' + row.fullname + ')'; + case 'group': + return row.nickname + ' (' + row.fullname + ')'; + } }, formatMatch: function(row, i, max){ - return '@' + row.screen_name; + row = eval("(" + row + ")"); + switch(row.type) + { + case 'user': + return row.nickname; + case 'group': + return row.nickname; + } }, formatResult: function(row){ - return '@' + row.screen_name; + row = eval("(" + row + ")"); + switch(row.type) + { + case 'user': + return '@' + row.nickname; + case 'group': + return '!' + row.nickname; + } } }); - } - ); - $.getJSON($('address .url')[0].href+'/api/statusnet/groups/list.json?user_id=' + current_user['id'] + '&callback=?', - function(groups){ - $('#notice_data-text').autocomplete(groups, { - multiple: true, - multipleSeparator: " ", - minChars: 1, - formatItem: function(row, i, max){ - return '!' + row.nickname + ' (' + row.fullname + ')'; - }, - formatMatch: function(row, i, max){ - return '!' + row.nickname; - }, - formatResult: function(row){ - return '!' + row.nickname; - } - }); - } - ); }); diff --git a/plugins/Autocomplete/AutocompletePlugin.php b/plugins/Autocomplete/AutocompletePlugin.php index b75397270..baaec73c1 100644 --- a/plugins/Autocomplete/AutocompletePlugin.php +++ b/plugins/Autocomplete/AutocompletePlugin.php @@ -31,6 +31,8 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +require_once(INSTALLDIR.'/plugins/Autocomplete/autocomplete.php'); + class AutocompletePlugin extends Plugin { function __construct() @@ -40,13 +42,6 @@ class AutocompletePlugin extends Plugin function onEndShowScripts($action){ if (common_logged_in()) { - $current_user = common_current_user(); - $js_string = <<<EOT -<script type="text/javascript"> -var current_user = { id: '$current_user->id' }; -</script> -EOT; - $action->raw($js_string); $action->script('plugins/Autocomplete/jquery-autocomplete/jquery.autocomplete.pack.js'); $action->script('plugins/Autocomplete/Autocomplete.js'); } @@ -59,5 +54,12 @@ EOT; } } + function onRouterInitialized($m) + { + if (common_logged_in()) { + $m->connect('plugins/Autocomplete/autocomplete.json', array('action'=>'autocomplete')); + } + } + } ?> diff --git a/plugins/Autocomplete/autocomplete.php b/plugins/Autocomplete/autocomplete.php new file mode 100644 index 000000000..aa57b3915 --- /dev/null +++ b/plugins/Autocomplete/autocomplete.php @@ -0,0 +1,136 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * List users for autocompletion + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * List users for autocompletion + * + * This is the form for adding a new g + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class AutocompleteAction extends Action +{ + private $result; + + /** + * Last-modified date for page + * + * When was the content of this page last modified? Based on notice, + * profile, avatar. + * + * @return int last-modified date as unix timestamp + */ + function lastModified() + { + $max=0; + foreach($this->users as $user){ + $max = max($max,strtotime($user->modified),strtotime($user->profile->modified)); + } + foreach($this->groups as $group){ + $max = max($max,strtotime($group->modified)); + } + return $max; + } + + /** + * An entity tag for this page + * + * Shows the ETag for the page, based on the notice ID and timestamps + * for the notice, profile, and avatar. It's weak, since we change + * the date text "one hour ago", etc. + * + * @return string etag + */ + function etag() + { + return '"' . implode(':', array($this->arg('action'), + crc32($this->arg('q')), //the actual string can have funny characters in we don't want showing up in the etag + $this->arg('limit'), + $this->lastModified())) . '"'; + } + + function prepare($args) + { + parent::prepare($args); + $this->groups=array(); + $this->users=array(); + $q = $this->arg('q'); + $limit = $this->arg('limit'); + if($limit > 200) $limit=200; //prevent DOS attacks + if(substr($q,0,1)=='@'){ + //user search + $q=substr($q,1); + $user = new User(); + $user->limit($limit); + $user->whereAdd('nickname like \'' . trim($user->escape($q), '\'') . '%\''); + $user->find(); + while($user->fetch()) { + $profile = Profile::staticGet($user->id); + $user->profile=$profile; + $this->users[]=$user; + } + } + if(substr($q,0,1)=='!'){ + //group search + $q=substr($q,1); + $group = new User_group(); + $group->limit($limit); + $group->whereAdd('nickname like \'' . trim($group->escape($q), '\'') . '%\''); + $group->find(); + while($group->fetch()) { + $this->groups[]=$group; + } + } + return true; + } + + function handle($args) + { + parent::handle($args); + $results = array(); + foreach($this->users as $user){ + $results[]=array('nickname' => $user->nickname, 'fullname'=> $user->profile->fullname, 'type'=>'user'); + } + foreach($this->groups as $group){ + $results[]=array('nickname' => $group->nickname, 'fullname'=> $group->fullname, 'type'=>'group'); + } + foreach($results as $result) { + print json_encode($result) . "\n"; + } + } +} diff --git a/plugins/Autocomplete/readme.txt b/plugins/Autocomplete/readme.txt index 3272aa1ee..1db4c6565 100644 --- a/plugins/Autocomplete/readme.txt +++ b/plugins/Autocomplete/readme.txt @@ -1,5 +1,7 @@ Autocomplete allows users to autocomplete screen names in @ replies. When an "@" is typed into the notice text area, an autocomplete box is displayed populated with the user's friends' screen names. +Note: This plugin doesn't work if the site is in Private mode, i.e. when $config['site']['private'] is set to true. + Installation ============ Add "addPlugin('Autocomplete');" to the bottom of your config.php diff --git a/plugins/FBConnect/FBCLoginGroupNav.php b/plugins/FBConnect/FBCLoginGroupNav.php index 6e12c2040..81b2520a4 100644 --- a/plugins/FBConnect/FBCLoginGroupNav.php +++ b/plugins/FBConnect/FBCLoginGroupNav.php @@ -78,16 +78,20 @@ class FBCLoginGroupNav extends Widget // action => array('prompt', 'title') $menu = array(); - $menu['login'] = array(_('Login'), - _('Login with a username and password')); - - if (!(common_config('site','closed') || common_config('site','inviteonly'))) { - $menu['register'] = array(_('Register'), - _('Sign up for a new account')); + if (!common_config('site','openidonly')) { + $menu['login'] = array(_('Login'), + _('Login with a username and password')); + + if (!(common_config('site','closed') || common_config('site','inviteonly'))) { + $menu['register'] = array(_('Register'), + _('Sign up for a new account')); + } } - $menu['openidlogin'] = array(_('OpenID'), - _('Login or register with OpenID')); + if (common_config('openid', 'enabled')) { + $menu['openidlogin'] = array(_('OpenID'), + _('Login or register with OpenID')); + } $menu['FBConnectLogin'] = array(_('Facebook'), _('Login or register using Facebook')); diff --git a/plugins/FBConnect/FBCSettingsNav.php b/plugins/FBConnect/FBCSettingsNav.php index 29724d6bd..ed02371e2 100644 --- a/plugins/FBConnect/FBCSettingsNav.php +++ b/plugins/FBConnect/FBCSettingsNav.php @@ -77,32 +77,34 @@ class FBCSettingsNav extends Widget $this->action->elementStart('dd'); # action => array('prompt', 'title') - $menu = - array('imsettings' => - array(_('IM'), - _('Updates by instant messenger (IM)')), - 'smssettings' => - array(_('SMS'), - _('Updates by SMS')), - 'twittersettings' => - array(_('Twitter'), - _('Twitter integration options')), - 'FBConnectSettings' => - array(_('Facebook'), - _('Facebook Connect settings'))); + $menu = array(); + if (common_config('xmpp', 'enabled')) { + $menu['imsettings'] = + array(_('IM'), + _('Updates by instant messenger (IM)')); + } + if (common_config('sms', 'enabled')) { + $menu['smssettings'] = + array(_('SMS'), + _('Updates by SMS')); + } + if (common_config('twitter', 'enabled')) { + $menu['twittersettings'] = + array(_('Twitter'), + _('Twitter integration options')); + } + $menu['FBConnectSettings'] = + array(_('Facebook'), + _('Facebook Connect settings')); $action_name = $this->action->trimmed('action'); $this->action->elementStart('ul', array('class' => 'nav')); foreach ($menu as $menuaction => $menudesc) { - if ($menuaction == 'imsettings' && - !common_config('xmpp', 'enabled')) { - continue; - } $this->action->menuItem(common_local_url($menuaction), - $menudesc[0], - $menudesc[1], - $action_name === $menuaction); + $menudesc[0], + $menudesc[1], + $action_name === $menuaction); } $this->action->elementEnd('ul'); diff --git a/plugins/FBConnect/FBConnectPlugin.php b/plugins/FBConnect/FBConnectPlugin.php index 593b49b4e..ff74aade4 100644 --- a/plugins/FBConnect/FBConnectPlugin.php +++ b/plugins/FBConnect/FBConnectPlugin.php @@ -232,6 +232,14 @@ class FBConnectPlugin extends Plugin { $user = common_current_user(); + $connect = 'FBConnectSettings'; + if (common_config('xmpp', 'enabled')) { + $connect = 'imsettings'; + } else if (common_config('sms', 'enabled')) { + $connect = 'smssettings'; + } else if (common_config('twitter', 'enabled')) { + $connect = 'twittersettings'; + } if (!empty($user)) { @@ -266,13 +274,8 @@ class FBConnectPlugin extends Plugin _('Home'), _('Personal profile and friends timeline'), false, 'nav_home'); $action->menuItem(common_local_url('profilesettings'), _('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account'); - if (common_config('xmpp', 'enabled')) { - $action->menuItem(common_local_url('imsettings'), - _('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect'); - } else { - $action->menuItem(common_local_url('smssettings'), - _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect'); - } + $action->menuItem(common_local_url($connect), + _('Connect'), _('Connect to services'), false, 'nav_connect'); if (common_config('invite', 'enabled')) { $action->menuItem(common_local_url('invite'), _('Invite'), @@ -300,18 +303,30 @@ class FBConnectPlugin extends Plugin } } else { - if (!common_config('site', 'closed')) { - $action->menuItem(common_local_url('register'), - _('Register'), _('Create an account'), false, 'nav_register'); + if (!common_config('site', 'openidonly')) { + if (!common_config('site', 'closed')) { + $action->menuItem(common_local_url('register'), + _('Register'), _('Create an account'), false, 'nav_register'); + } + $action->menuItem(common_local_url('login'), + _('Login'), _('Login to the site'), false, 'nav_login'); + } else { + $this->menuItem(common_local_url('openidlogin'), + _('OpenID'), _('Login with OpenID'), false, 'nav_openid'); } - $action->menuItem(common_local_url('login'), - _('Login'), _('Login to the site'), false, 'nav_login'); } $action->menuItem(common_local_url('doc', array('title' => 'help')), _('Help'), _('Help me!'), false, 'nav_help'); - $action->menuItem(common_local_url('peoplesearch'), - _('Search'), _('Search for people or text'), false, 'nav_search'); + if ($user || !common_config('site', 'private')) { + $action->menuItem(common_local_url('peoplesearch'), + _('Search'), _('Search for people or text'), false, 'nav_search'); + } + + // We are replacing the primary nav entirely; give other + // plugins a chance to handle it here. + + Event::handle('EndPrimaryNav', array($action)); return false; } diff --git a/plugins/InfiniteScroll/InfiniteScrollPlugin.php b/plugins/InfiniteScroll/InfiniteScrollPlugin.php index c955298cb..5928c007f 100644 --- a/plugins/InfiniteScroll/InfiniteScrollPlugin.php +++ b/plugins/InfiniteScroll/InfiniteScrollPlugin.php @@ -40,7 +40,7 @@ class InfiniteScrollPlugin extends Plugin function onEndShowScripts($action) { - $action->script('plugins/InfiniteScroll/jquery.infinitescroll.min.js'); + $action->script('plugins/InfiniteScroll/jquery.infinitescroll.js'); $action->script('plugins/InfiniteScroll/infinitescroll.js'); } } diff --git a/plugins/InfiniteScroll/infinitescroll.js b/plugins/InfiniteScroll/infinitescroll.js index 6513072d0..ae4d53d09 100644 --- a/plugins/InfiniteScroll/infinitescroll.js +++ b/plugins/InfiniteScroll/infinitescroll.js @@ -1,6 +1,7 @@ jQuery(document).ready(function($){ $('notices_primary').infinitescroll({ debug: true, + infiniteScroll : false, nextSelector : "li.nav_next a", loadingImg : $('address .url')[0].href+'plugins/InfiniteScroll/ajax-loader.gif', text : "<em>Loading the next set of posts...</em>", @@ -12,4 +13,3 @@ jQuery(document).ready(function($){ NoticeAttachments(); }); }); - diff --git a/plugins/InfiniteScroll/jquery.infinitescroll.js b/plugins/InfiniteScroll/jquery.infinitescroll.js index 670686b0e..ec31bb086 100644 --- a/plugins/InfiniteScroll/jquery.infinitescroll.js +++ b/plugins/InfiniteScroll/jquery.infinitescroll.js @@ -92,14 +92,14 @@ if (props.isDuringAjax || props.isInvalidPage || props.isDone) return; - if ( !isNearBottom(opts,props) ) return; + if ( opts.infiniteScroll && !isNearBottom(opts,props) ) return; // we dont want to fire the ajax multiple times props.isDuringAjax = true; // show the loading message and hide the previous/next links props.loadingMsg.appendTo( opts.contentSelector ).show(); - $( opts.navSelector ).hide(); + if(opts.infiniteScroll) $( opts.navSelector ).hide(); // increment the URL bit. e.g. /page/3/ props.currPage++; @@ -205,10 +205,19 @@ } }); - // bind scroll handler to element (if its a local scroll) or window - $(opts.localMode ? this : window) - .bind('scroll.infscr', function(){ infscrSetup(path,opts,props,callback); } ) - .trigger('scroll.infscr'); // trigger the event, in case it's a short page + if(opts.infiniteScroll){ + // bind scroll handler to element (if its a local scroll) or window + $(opts.localMode ? this : window) + .bind('scroll.infscr', function(){ infscrSetup(path,opts,props,callback); } ) + .trigger('scroll.infscr'); // trigger the event, in case it's a short page + }else{ + $(opts.nextSelector).click( + function(){ + infscrSetup(path,opts,props,callback); + return false; + } + ); + } return this; @@ -222,6 +231,7 @@ $.infinitescroll = { defaults : { debug : false, + infiniteScroll : true, preload : false, nextSelector : "div.navigation a:first", loadingImg : "http://www.infinite-scroll.com/loading.gif", diff --git a/plugins/Meteor/meteorupdater.js b/plugins/Meteor/meteorupdater.js index 2e688336f..9ce68775b 100644 --- a/plugins/Meteor/meteorupdater.js +++ b/plugins/Meteor/meteorupdater.js @@ -1,5 +1,6 @@ -// update the local timeline from a Meteor server -// +// Update the local timeline from a Meteor server +// XXX: If @a is subscribed to @b, @a should get @b's notices in @a's Personal timeline. +// Do Replies timeline. var MeteorUpdater = function() { diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php index 91bddf381..a933a1155 100644 --- a/plugins/OpenID/OpenIDPlugin.php +++ b/plugins/OpenID/OpenIDPlugin.php @@ -222,4 +222,19 @@ class OpenIDPlugin extends Plugin return true; } + + function onCheckSchema() { + $schema = Schema::get(); + $schema->ensureTable('user_openid', + array(new ColumnDef('canonical', 'varchar', + '255', false, 'PRI'), + new ColumnDef('display', 'varchar', + '255', false), + new ColumnDef('user_id', 'integer', + null, false, 'MUL'), + new ColumnDef('created', 'datetime', + null, false), + new ColumnDef('modified', 'timestamp'))); + return true; + } } diff --git a/plugins/Orbited/OrbitedPlugin.php b/plugins/Orbited/OrbitedPlugin.php new file mode 100644 index 000000000..ba87b266a --- /dev/null +++ b/plugins/Orbited/OrbitedPlugin.php @@ -0,0 +1,154 @@ +<?php +/** + * Laconica, the distributed open-source microblogging tool + * + * Plugin to do "real time" updates using Orbited + STOMP + * + * PHP version 5 + * + * LICENCE: 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 <http://www.gnu.org/licenses/>. + * + * @category Plugin + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @copyright 2009 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +if (!defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/plugins/Realtime/RealtimePlugin.php'; + +/** + * Plugin to do realtime updates using Orbited + STOMP + * + * This plugin pushes data to a STOMP server which is then served to the + * browser by the Orbited server. + * + * @category Plugin + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +class OrbitedPlugin extends RealtimePlugin +{ + public $webserver = null; + public $webport = null; + public $channelbase = null; + public $stompserver = null; + public $stompport = null; + public $username = null; + public $password = null; + public $webuser = null; + public $webpass = null; + + protected $con = null; + + function onStartShowHeadElements($action) + { + // See http://orbited.org/wiki/Deployment#Cross-SubdomainDeployment + $action->element('script', null, ' document.domain = document.domain; '); + } + + function _getScripts() + { + $scripts = parent::_getScripts(); + + $port = (is_null($this->webport)) ? 8000 : $this->webport; + + $server = (is_null($this->webserver)) ? common_config('site', 'server') : $this->webserver; + + $root = 'http://'.$server.(($port == 80) ? '':':'.$port); + + $scripts[] = $root.'/static/Orbited.js'; + $scripts[] = common_path('plugins/Orbited/orbitedextra.js'); + $scripts[] = $root.'/static/protocols/stomp/stomp.js'; + $scripts[] = common_path('plugins/Orbited/orbitedupdater.js'); + + return $scripts; + } + + function _updateInitialize($timeline, $user_id) + { + $script = parent::_updateInitialize($timeline, $user_id); + + $server = $this->_getStompServer(); + $port = $this->_getStompPort(); + + return $script." OrbitedUpdater.init(\"$server\", $port, ". + "\"{$timeline}\", \"{$this->webuser}\", \"{$this->webpass}\");"; + } + + function _connect() + { + require_once(INSTALLDIR.'/extlib/Stomp.php'); + + $url = $this->_getStompUrl(); + + $this->con = new Stomp($url); + + if ($this->con->connect($this->username, $this->password)) { + $this->log(LOG_INFO, "Connected."); + } else { + $this->log(LOG_ERR, 'Failed to connect to queue server'); + throw new ServerException('Failed to connect to queue server'); + } + } + + function _publish($channel, $message) + { + $result = $this->con->send($channel, + json_encode($message)); + + return $result; + // TODO: parse and deal with result + } + + function _disconnect() + { + $this->con->disconnect(); + } + + function _pathToChannel($path) + { + if (!empty($this->channelbase)) { + array_unshift($path, $this->channelbase); + } + return '/' . implode('/', $path); + } + + function _getStompServer() + { + return (!is_null($this->stompserver)) ? $this->stompserver : + (!is_null($this->webserver)) ? $this->webserver : + common_config('site', 'server'); + } + + function _getStompPort() + { + return (!is_null($this->stompport)) ? $this->stompport : 61613; + } + + function _getStompUrl() + { + $server = $this->_getStompServer(); + $port = $this->_getStompPort(); + return "tcp://$server:$port/"; + } +} diff --git a/plugins/Orbited/orbitedextra.js b/plugins/Orbited/orbitedextra.js new file mode 100644 index 000000000..47e5c0c80 --- /dev/null +++ b/plugins/Orbited/orbitedextra.js @@ -0,0 +1,2 @@ +TCPSocket = Orbited.TCPSocket; + diff --git a/plugins/Orbited/orbitedupdater.js b/plugins/Orbited/orbitedupdater.js new file mode 100644 index 000000000..8c5ab3b73 --- /dev/null +++ b/plugins/Orbited/orbitedupdater.js @@ -0,0 +1,24 @@ +// Update the local timeline from a Orbited server + +var OrbitedUpdater = function() +{ + return { + + init: function(server, port, timeline, username, password) + { + // set up stomp client. + stomp = new STOMPClient(); + + stomp.onmessageframe = function(frame) { + RealtimeUpdate.receive(JSON.parse(frame.body)); + }; + + stomp.onconnectedframe = function() { + stomp.subscribe(timeline); + } + + stomp.connect(server, port, username, password); + } + } +}(); + diff --git a/plugins/PiwikAnalyticsPlugin.php b/plugins/PiwikAnalyticsPlugin.php index 85a24c132..54faa0bdb 100644 --- a/plugins/PiwikAnalyticsPlugin.php +++ b/plugins/PiwikAnalyticsPlugin.php @@ -38,30 +38,24 @@ if (!defined('STATUSNET')) { * This plugin will spoot out the correct JavaScript spell to invoke * Piwik Analytics on a page. * - * To use this plugin please add the following three lines to your config.php + * To use this plugin add the following to your config.php * - * require_once('plugins/PiwikAnalyticsPlugin.php'); - * $pa = new PiwikAnalyticsPlugin("example.com/piwik/","id"); + * addPlugin('PiwikAnalytics', array('piwikroot' => 'example.com/piwik/', + * 'piwikId' => 'id')); * - * exchange example.com/piwik/ with the url to your piwik installation and - * make sure you don't forget the final / - * exchange id with the ID your statusnet installation has in your Piwik analytics + * Replace 'example.com/piwik/' with the URL to your Piwik installation and + * make sure you don't forget the final /. + * Replace 'id' with the ID your statusnet installation has in your Piwik + * analytics setup - for example '8'. * - * @category Plugin - * @package StatusNet - * @author Tobias Diekershoff <tobias.diekershoff@gmx.net> - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - * - * @see Event */ class PiwikAnalyticsPlugin extends Plugin { /** the base of your Piwik installation */ - var $piwikroot = null; + public $piwikroot = null; /** the Piwik Id of your statusnet installation */ - var $piwikId = null; + public $piwikId = null; /** * constructor @@ -73,7 +67,7 @@ class PiwikAnalyticsPlugin extends Plugin function __construct($root=null, $id=null) { $this->piwikroot = $root; - $this->piwikid = $id; + $this->piwikId = $id; parent::__construct(); } @@ -96,7 +90,7 @@ document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/ja </script> <script type="text/javascript"> try { - var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", 4); + var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", {$this->piwikId}); piwikTracker.trackPageView(); piwikTracker.enableLinkTracking(); } catch( err ) {} @@ -108,4 +102,4 @@ ENDOFPIWIK; $action->raw($piwikCode); return true; } -}
\ No newline at end of file +} diff --git a/plugins/PubSubHubBub/PubSubHubBubPlugin.php b/plugins/PubSubHubBub/PubSubHubBubPlugin.php index 013a234d7..e1e82e352 100644 --- a/plugins/PubSubHubBub/PubSubHubBubPlugin.php +++ b/plugins/PubSubHubBub/PubSubHubBubPlugin.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -define('DEFAULT_HUB','http://2pubsubhubbub.appspot.com'); +define('DEFAULT_HUB','http://pubsubhubbub.appspot.com'); require_once(INSTALLDIR.'/plugins/PubSubHubBub/publisher.php'); @@ -59,7 +59,7 @@ class PubSubHubBubPlugin extends Plugin $action->element('atom:link',array('rel'=>'hub','href'=>$this->hub),null); } - function onEndNoticeSave($notice){ + function onHandleQueuedNotice($notice){ $publisher = new Publisher($this->hub); $feeds = array(); diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index 82eca3d08..0c7c1240c 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -50,6 +50,11 @@ class RealtimePlugin extends Plugin protected $favorurl = null; protected $deleteurl = null; + /** + * When it's time to initialize the plugin, calculate and + * pass the URLs we need. + */ + function onInitializePlugin() { $this->replyurl = common_local_url('newnotice'); @@ -57,29 +62,26 @@ class RealtimePlugin extends Plugin // FIXME: need to find a better way to pass this pattern in $this->deleteurl = common_local_url('deletenotice', array('notice' => '0000000000')); + return true; } function onEndShowScripts($action) { - $path = null; + $timeline = $this->_getTimeline($action); - switch ($action->trimmed('action')) { - case 'public': - $path = array('public'); - break; - case 'tag': - $tag = $action->trimmed('tag'); - if (!empty($tag)) { - $path = array('tag', $tag); - } else { - return true; - } - break; - default: + // If there's not a timeline on this page, + // just return true + + if (empty($timeline)) { return true; } - $timeline = $this->_pathToChannel($path); + $base = $action->selfUrl(); + if (mb_strstr($base, '?')) { + $url = $base . '&realtime=1'; + } else { + $url = $base . '?realtime=1'; + } $scripts = $this->_getScripts(); @@ -95,10 +97,22 @@ class RealtimePlugin extends Plugin $user_id = 0; } + if ($action->boolean('realtime')) { + $realtimeUI = ' RealtimeUpdate.initPopupWindow();'; + } + else { + $iconurl = common_path('plugins/Realtime/icon_external.gif'); + $realtimeUI = ' RealtimeUpdate.addPopup("'.$url.'", "'.$timeline.'", "'. $iconurl .'");'; + } + $action->elementStart('script', array('type' => 'text/javascript')); - $action->raw("$(document).ready(function() { "); - $action->raw($this->_updateInitialize($timeline, $user_id)); - $action->raw(" });"); + + $script = ' $(document).ready(function() { '. + $realtimeUI. + $this->_updateInitialize($timeline, $user_id). + '}); '; + $action->raw($script); + $action->elementEnd('script'); return true; @@ -108,13 +122,23 @@ class RealtimePlugin extends Plugin { $paths = array(); - // XXX: Add other timelines; this is just for the public one + // Add to the author's timeline + + $user = User::staticGet('id', $notice->profile_id); + + if (!empty($user)) { + $paths[] = array('showstream', $user->nickname); + } + + // Add to the public timeline if ($notice->is_local || ($notice->is_local == 0 && !common_config('public', 'localonly'))) { $paths[] = array('public'); } + // Add to the tags timeline + $tags = $this->getNoticeTags($notice); if (!empty($tags)) { @@ -123,6 +147,46 @@ class RealtimePlugin extends Plugin } } + // Add to inbox timelines + // XXX: do a join + + $inbox = new Notice_inbox(); + $inbox->notice_id = $notice->id; + + if ($inbox->find()) { + while ($inbox->fetch()) { + $user = User::staticGet('id', $inbox->user_id); + $paths[] = array('all', $user->nickname); + } + } + + // Add to the replies timeline + + $reply = new Reply(); + $reply->notice_id = $notice->id; + + if ($reply->find()) { + while ($reply->fetch()) { + $user = User::staticGet('id', $reply->profile_id); + if (!empty($user)) { + $paths[] = array('replies', $user->nickname); + } + } + } + + // Add to the group timeline + // XXX: join + + $gi = new Group_inbox(); + $gi->notice_id = $notice->id; + + if ($gi->find()) { + while ($gi->fetch()) { + $ug = User_group::staticGet('id', $gi->group_id); + $paths[] = array('showgroup', $ug->nickname); + } + } + if (count($paths) > 0) { $json = $this->noticeAsJson($notice); @@ -140,18 +204,49 @@ class RealtimePlugin extends Plugin return true; } + function onStartShowBody($action) + { + $realtime = $action->boolean('realtime'); + if (!$realtime) { + return true; + } + + $action->elementStart('body', + (common_current_user()) ? array('id' => $action->trimmed('action'), + 'class' => 'user_in') + : array('id' => $action->trimmed('action'))); + + // XXX hack to deal with JS that tries to get the + // root url from page output + + $action->elementStart('address'); + $action->element('a', array('class' => 'url', + 'href' => common_local_url('public')), + ''); + $action->elementEnd('address'); + + if (common_logged_in()) { + $action->showNoticeForm(); + } + + $action->showContentBlock(); + $action->showScripts(); + $action->elementEnd('body'); + return false; // No default processing + } + function noticeAsJson($notice) { // FIXME: this code should be abstracted to a neutral third // party, like Notice::asJson(). I'm not sure of the ethics // of refactoring from within a plugin, so I'm just abusing - // the TwitterApiAction method. Don't do this unless you're me! + // the ApiAction method. Don't do this unless you're me! - require_once(INSTALLDIR.'/lib/twitterapi.php'); + require_once(INSTALLDIR.'/lib/api.php'); - $act = new TwitterApiAction('/dev/null'); + $act = new ApiAction('/dev/null'); - $arr = $act->twitter_status_array($notice, true); + $arr = $act->twitterStatusArray($notice, true); $arr['url'] = $notice->bestUrl(); $arr['html'] = htmlspecialchars($notice->rendered); $arr['source'] = htmlspecialchars($arr['source']); @@ -224,4 +319,41 @@ class RealtimePlugin extends Plugin { return ''; } + + function _getTimeline($action) + { + $path = null; + $timeline = null; + + $action_name = $action->trimmed('action'); + + switch ($action_name) { + case 'public': + $path = array('public'); + break; + case 'tag': + $tag = $action->trimmed('tag'); + if (!empty($tag)) { + $path = array('tag', $tag); + } + break; + case 'showstream': + case 'all': + case 'replies': + case 'showgroup': + $nickname = common_canonical_nickname($action->trimmed('nickname')); + if (!empty($nickname)) { + $path = array($action_name, $nickname); + } + break; + default: + break; + } + + if (!empty($path)) { + $timeline = $this->_pathToChannel($path); + } + + return $timeline; + } } diff --git a/plugins/Realtime/icon_external.gif b/plugins/Realtime/icon_external.gif Binary files differnew file mode 100644 index 000000000..c4118d53b --- /dev/null +++ b/plugins/Realtime/icon_external.gif diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index d55db5859..a75f17d8c 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -1,8 +1,8 @@ // add a notice encoded as JSON into the current timeline // +// TODO: i18n RealtimeUpdate = { - _userid: 0, _replyurl: '', _favorurl: '', @@ -10,27 +10,42 @@ RealtimeUpdate = { init: function(userid, replyurl, favorurl, deleteurl) { - RealtimeUpdate._userid = userid; - RealtimeUpdate._replyurl = replyurl; - RealtimeUpdate._favorurl = favorurl; - RealtimeUpdate._deleteurl = deleteurl; + RealtimeUpdate._userid = userid; + RealtimeUpdate._replyurl = replyurl; + RealtimeUpdate._favorurl = favorurl; + RealtimeUpdate._deleteurl = deleteurl; + + $(window).blur(function() { + $('#notices_primary .notice').css({ + 'border-top-color':$('#notices_primary .notice:last').css('border-top-color'), + 'border-top-style':'dotted' + }); + + $('#notices_primary .notice:first').css({ + 'border-top-color':'#AAAAAA', + 'border-top-style':'solid' + }); + + return false; + }); }, receive: function(data) { - id = data.id; - - // Don't add it if it already exists - - if ($("#notice-"+id).length > 0) { - return; - } - - var noticeItem = RealtimeUpdate.makeNoticeItem(data); - $("#notices_primary .notices").prepend(noticeItem, true); - $("#notices_primary .notice:first").css({display:"none"}); - $("#notices_primary .notice:first").fadeIn(1000); - NoticeReply(); + setTimeout(function() { + id = data.id; + + // Don't add it if it already exists + if ($("#notice-"+id).length > 0) { + return; + } + + var noticeItem = RealtimeUpdate.makeNoticeItem(data); + $("#notices_primary .notices").prepend(noticeItem); + $("#notices_primary .notice:first").css({display:"none"}); + $("#notices_primary .notice:first").fadeIn(1000); + NoticeReply(); + }, 500); }, makeNoticeItem: function(data) @@ -50,30 +65,19 @@ RealtimeUpdate = { "<p class=\"entry-content\">"+html+"</p>"+ "</div>"+ "<div class=\"entry-content\">"+ - "<dl class=\"timestamp\">"+ - "<dt>Published</dt>"+ - "<dd>"+ - "<a rel=\"bookmark\" href=\""+data['url']+"\" >"+ + "<a class=\"timestamp\" rel=\"bookmark\" href=\""+data['url']+"\" >"+ "<abbr class=\"published\" title=\""+data['created_at']+"\">a few seconds ago</abbr>"+ "</a> "+ - "</dd>"+ - "</dl>"+ - "<dl class=\"device\">"+ - "<dt>From</dt> "+ - "<dd>"+source+"</dd>"+ // may have a link, I think - "</dl>"; - + "<span class=\"source\">"+ + "from "+ + "<span class=\"device\">"+source+"</span>"+ // may have a link + "</span>"; if (data['in_reply_to_status_id']) { - ni = ni+" <dl class=\"response\">"+ - "<dt>To</dt>"+ - "<dd>"+ - "<a href=\""+data['in_reply_to_status_url']+"\" rel=\"in-reply-to\">in reply to</a>"+ - "</dd>"+ - "</dl>"; + ni = ni+" <a class=\"response\" href=\""+data['in_reply_to_status_url']+"\">in context</a>"; } ni = ni+"</div>"+ - "<div class=\"notice-options\">"; + "<div class=\"notice-options\">"; if (RealtimeUpdate._userid != 0) { var input = $("form#form_notice fieldset input#token"); @@ -95,12 +99,12 @@ RealtimeUpdate = { var ff; ff = "<form id=\"favor-"+id+"\" class=\"form_favor\" method=\"post\" action=\""+RealtimeUpdate._favorurl+"\">"+ - "<fieldset>"+ - "<legend>Favor this notice</legend>"+ // XXX: i18n + "<fieldset>"+ + "<legend>Favor this notice</legend>"+ "<input name=\"token-"+id+"\" type=\"hidden\" id=\"token-"+id+"\" value=\""+session_key+"\"/>"+ "<input name=\"notice\" type=\"hidden\" id=\"notice-n"+id+"\" value=\""+id+"\"/>"+ "<input type=\"submit\" id=\"favor-submit-"+id+"\" name=\"favor-submit-"+id+"\" class=\"submit\" value=\"Favor\" title=\"Favor this notice\"/>"+ - "</fieldset>"+ + "</fieldset>"+ "</form>"; return ff; }, @@ -108,28 +112,71 @@ RealtimeUpdate = { makeReplyLink: function(id, nickname) { var rl; - rl = "<dl class=\"notice_reply\">"+ - "<dt>Reply to this notice</dt>"+ - "<dd>"+ - "<a href=\""+RealtimeUpdate._replyurl+"?replyto="+nickname+"\" title=\"Reply to this notice\">Reply <span class=\"notice_id\">"+id+"</span>"+ - "</a>"+ - "</dd>"+ - "</dl>"; + rl = "<a class=\"notice_reply\" href=\""+RealtimeUpdate._replyurl+"?replyto="+nickname+"\" title=\"Reply to this notice\">Reply <span class=\"notice_id\">"+id+"</span></a>"; return rl; - }, + }, makeDeleteLink: function(id) { var dl, delurl; delurl = RealtimeUpdate._deleteurl.replace("0000000000", id); - dl = "<dl class=\"notice_delete\">"+ - "<dt>Delete this notice</dt>"+ - "<dd>"+ - "<a href=\""+delurl+"\" title=\"Delete this notice\">Delete</a>"+ - "</dd>"+ - "</dl>"; + dl = "<a class=\"notice_delete\" href=\""+delurl+"\" title=\"Delete this notice\">Delete</a>"; return dl; }, + + addPopup: function(url, timeline, iconurl) + { + $('#notices_primary').css({'position':'relative'}); + $('#notices_primary').prepend('<button id="realtime_timeline" title="Pop up in a window">Pop up</button>'); + + $('#realtime_timeline').css({ + 'margin':'0 0 11px 0', + 'background':'transparent url('+ iconurl + ') no-repeat 0% 30%', + 'padding':'0 0 0 20px', + 'display':'block', + 'position':'absolute', + 'top':'-20px', + 'right':'0', + 'border':'none', + 'cursor':'pointer', + 'color':$("a").css("color"), + 'font-weight':'bold', + 'font-size':'1em' + }); + + $('#realtime_timeline').click(function() { + window.open(url, + timeline, + 'toolbar=no,resizable=yes,scrollbars=yes,status=yes'); + + return false; + }); + }, + + initPopupWindow: function() + { + window.resizeTo(500, 550); + $('address').hide(); + $('#content').css({'width':'93.5%'}); + + $('#form_notice').css({ + 'margin':'18px 0 18px 1.795%', + 'width':'93%', + 'max-width':'451px' + }); + + $('#form_notice label[for=notice_data-text], h1').css({'display': 'none'}); + + $('.notices li:first-child').css({'border-top-color':'transparent'}); + + $('#form_notice label[for="notice_data-attach"], #form_notice #notice_data-attach').css({'top':'0'}); + + $('#form_notice #notice_data-attach').css({ + 'left':'auto', + 'right':'0' + }); + } } + diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index b49e2e119..afc3f55ba 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -23,7 +23,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1 -function update_twitter_user($twitter_id, $screen_name) +function updateTwitter_user($twitter_id, $screen_name) { $uri = 'http://twitter.com/' . $screen_name; $fuser = new Foreign_user(); @@ -115,7 +115,7 @@ function save_twitter_user($twitter_id, $screen_name) // Only update if Twitter screen name has changed if ($fuser->nickname != $screen_name) { - $result = update_twitter_user($twitter_id, $screen_name); + $result = updateTwitter_user($twitter_id, $screen_name); common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' . "$fuser->id to $screen_name, was $fuser->nickname"); diff --git a/plugins/TwitterBridge/twitterbasicauthclient.php b/plugins/TwitterBridge/twitterbasicauthclient.php index fd331fbdc..1040d72fb 100644 --- a/plugins/TwitterBridge/twitterbasicauthclient.php +++ b/plugins/TwitterBridge/twitterbasicauthclient.php @@ -36,8 +36,14 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { * * @category Integration * @package StatusNet - * @author Zach Copley <zach@status.net> - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @author Adrian Lang <mail@adrianlang.de> + * @author Brenda Wallace <shiny@cpan.org> + * @author Craig Andrews <candrews@integralblue.com> + * @author Dan Moore <dan@moore.cx> + * @author Evan Prodromou <evan@status.net> + * @author mEDI <medi@milaro.net> + * @author Sarven Capadisli <csarven@status.net> + * @author Zach Copley <zach@status.net> * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ * */ diff --git a/plugins/recaptcha/README b/plugins/recaptcha/README index ce23a2695..b996f96cc 100644 --- a/plugins/recaptcha/README +++ b/plugins/recaptcha/README @@ -6,7 +6,7 @@ Use: 1. Get an API key from http://recaptcha.net 2. In config.php add: -include_once('plugins/recaptcha.php'); +include_once('plugins/recaptcha/recaptcha.php'); $captcha = new recaptcha(publickey, privatekey, showErrors); Changelog diff --git a/scripts/checkschema.php b/scripts/checkschema.php new file mode 100644 index 000000000..bf52abe15 --- /dev/null +++ b/scripts/checkschema.php @@ -0,0 +1,30 @@ +#!/usr/bin/env php +<?php +/* + * StatusNet - a distributed open-source microblogging tool + * Copyright (C) 2008, 2009, StatusNet, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$helptext = <<<END_OF_CHECKSCHEMA_HELP +Gives plugins a chance to update the database schema. + +END_OF_CHECKSCHEMA_HELP; + +require_once INSTALLDIR.'/scripts/commandline.inc'; + +Event::handle('CheckSchema'); diff --git a/scripts/createsim.php b/scripts/createsim.php index 71ed3bf72..1266a9700 100644 --- a/scripts/createsim.php +++ b/scripts/createsim.php @@ -101,7 +101,7 @@ function newSub($i) $to = User::staticGet('nickname', $tunic); - if (empty($from)) { + if (empty($to)) { throw new Exception("Can't find user '$tunic'."); } diff --git a/scripts/deleteuser.php b/scripts/deleteuser.php new file mode 100644 index 000000000..52389123c --- /dev/null +++ b/scripts/deleteuser.php @@ -0,0 +1,68 @@ +#!/usr/bin/env php +<?php +/* + * StatusNet - a distributed open-source microblogging tool + * Copyright (C) 2008, 2009, StatusNet, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'i::n::y'; +$longoptions = array('id::nickname::yes'); + +$helptext = <<<END_OF_DELETEUSER_HELP +deleteuser.php [options] +deletes a user from the database + + -i --id ID of the user + -n --nickname nickname of the user + -y --yes do not wait for confirmation + +END_OF_DELETEUSER_HELP; + +require_once INSTALLDIR.'/scripts/commandline.inc'; + +if (have_option('i', 'id')) { + $id = get_option_value('i', 'id'); + $user = User::staticGet('id', $id); + if (empty($user)) { + print "Can't find user with ID $id\n"; + exit(1); + } +} else if (have_option('n', 'nickname')) { + $nickname = get_option_value('n', 'nickname'); + $user = User::staticGet('nickname', $nickname); + if (empty($user)) { + print "Can't find user with nickname '$nickname'\n"; + exit(1); + } +} else { + print "You must provide either an ID or a nickname.\n"; + exit(1); +} + +if (!have_option('y', 'yes')) { + print "About to PERMANENTLY delete user '{$user->nickname}' ({$user->id}). Are you sure? [y/N] "; + $response = fgets(STDIN); + if (strtolower(trim($response)) != 'y') { + print "Aborting.\n"; + exit(0); + } +} + +print "Deleting..."; +$user->delete(); +print "DONE.\n"; diff --git a/scripts/getvaliddaemons.php b/scripts/getvaliddaemons.php index 8f48e8e6f..6dd019712 100755 --- a/scripts/getvaliddaemons.php +++ b/scripts/getvaliddaemons.php @@ -35,20 +35,36 @@ ENDOFHELP; require_once INSTALLDIR.'/scripts/commandline.inc'; +$daemons = array(); + +$daemons[] = INSTALLDIR.'/scripts/pluginqueuehandler.php'; +$daemons[] = INSTALLDIR.'/scripts/ombqueuehandler.php'; +$daemons[] = INSTALLDIR.'/scripts/facebookqueuehandler.php'; +$daemons[] = INSTALLDIR.'/scripts/pingqueuehandler.php'; + if(common_config('xmpp','enabled')) { - echo "xmppdaemon.php jabberqueuehandler.php publicqueuehandler.php "; - echo "xmppconfirmhandler.php "; + $daemons[] = INSTALLDIR.'/scripts/xmppdaemon.php'; + $daemons[] = INSTALLDIR.'/scripts/jabberqueuehandler.php'; + $daemons[] = INSTALLDIR.'/scripts/publicqueuehandler.php'; + $daemons[] = INSTALLDIR.'/scripts/xmppconfirmhandler.php'; } + if(common_config('twitterbridge','enabled')) { - echo "twitterstatusfetcher.php "; + $daemons[] = INSTALLDIR.'/scripts/twitterstatusfetcher.php'; } -echo "ombqueuehandler.php "; + if (common_config('twitter', 'enabled')) { - echo "twitterqueuehandler.php "; - echo "synctwitterfriends.php "; + $daemons[] = INSTALLDIR.'/scripts/twitterqueuehandler.php'; + $daemons[] = INSTALLDIR.'/scripts/synctwitterfriends.php'; } -echo "facebookqueuehandler.php "; -echo "pingqueuehandler.php "; + if (common_config('sms', 'enabled')) { - echo "smsqueuehandler.php "; + $daemons[] = INSTALLDIR.'/scripts/smsqueuehandler.php'; +} + +if (Event::handle('GetValidDaemons', array(&$daemons))) { + foreach ($daemons as $daemon) { + print $daemon . ' '; + } + print "\n"; } diff --git a/scripts/maildaemon.php b/scripts/maildaemon.php index 5705cfd50..586bef624 100755 --- a/scripts/maildaemon.php +++ b/scripts/maildaemon.php @@ -260,10 +260,11 @@ class MailerDaemon function add_notice($user, $msg, $fileRecords) { - $notice = Notice::saveNew($user->id, $msg, 'mail'); - if (is_string($notice)) { - $this->log(LOG_ERR, $notice); - return $notice; + try { + $notice = Notice::saveNew($user->id, $msg, 'mail'); + } catch (Exception $e) { + $this->log(LOG_ERR, $e->getMessage()); + return $e->getMessage(); } foreach($fileRecords as $fileRecord){ $this->attachFile($notice, $fileRecord); diff --git a/scripts/pluginqueuehandler.php b/scripts/pluginqueuehandler.php new file mode 100755 index 000000000..ae807db6a --- /dev/null +++ b/scripts/pluginqueuehandler.php @@ -0,0 +1,58 @@ +#!/usr/bin/env php +<?php +/* + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2008, 2009, StatusNet, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'i::'; +$longoptions = array('id::'); + +$helptext = <<<END_OF_OMB_HELP +Daemon script for letting plugins handle stuff at queue time + + -i --id Identity (default none) + +END_OF_OMB_HELP; + +require_once INSTALLDIR.'/scripts/commandline.inc'; +require_once INSTALLDIR . '/lib/queuehandler.php'; + +class PluginQueueHandler extends QueueHandler +{ + + function transport() + { + return 'plugin'; + } + + function handle_notice($notice) + { + Event::handle('HandleQueuedNotice', array(&$notice)); + return true; + } +} + +if (have_option('i', 'id')) { + $id = get_option_value('i', 'id'); +} else { + $id = null; +} + +$handler = new PluginQueueHandler($id); +$handler->runOnce(); diff --git a/actions/twitapinotifications.php b/scripts/showtable.php index 0653e69ab..eb18a98e2 100644 --- a/actions/twitapinotifications.php +++ b/scripts/showtable.php @@ -1,6 +1,7 @@ +#!/usr/bin/env php <?php /* - * StatusNet - the distributed open-source microblogging tool + * StatusNet - a distributed open-source microblogging tool * Copyright (C) 2008, 2009, StatusNet, Inc. * * This program is free software: you can redistribute it and/or modify @@ -17,24 +18,24 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); -require_once(INSTALLDIR.'/lib/twitterapi.php'); +$helptext = <<<END_OF_SHOWTABLE_HELP +showtable.php <tablename> +Shows the structure of a table -# This naming convention looks real sick -class TwitapinotificationsAction extends TwitterapiAction -{ +END_OF_SHOWTABLE_HELP; - function follow($args, $apidata) - { - parent::handle($args); - $this->serverError(_('API method under construction.'), $code=501); - } +require_once INSTALLDIR.'/scripts/commandline.inc'; - function leave($args, $apidata) - { - parent::handle($args); - $this->serverError(_('API method under construction.'), $code=501); - } +if (count($args) != 1) { + show_help(); +} -}
\ No newline at end of file +$name = $args[0]; + +$schema = Schema::get(); + +$td = $schema->getTableDef($name); + +print_r($td); diff --git a/scripts/startdaemons.sh b/scripts/startdaemons.sh index 298162673..5fb75414d 100755 --- a/scripts/startdaemons.sh +++ b/scripts/startdaemons.sh @@ -40,7 +40,7 @@ DAEMONS=`php $DIR/getvaliddaemons.php $ARGSG` for f in $DAEMONS; do printf "Starting $f..."; - php $DIR/$f $ARGSD + php $f $ARGSD printf "DONE.\n" done diff --git a/scripts/xmppdaemon.php b/scripts/xmppdaemon.php index 1b1aec3e6..b2efc07c3 100755 --- a/scripts/xmppdaemon.php +++ b/scripts/xmppdaemon.php @@ -323,12 +323,15 @@ class XMPPDaemon extends Daemon mb_strlen($content_shortened))); return; } - $notice = Notice::saveNew($user->id, $content_shortened, 'xmpp'); - if (is_string($notice)) { - $this->log(LOG_ERR, $notice); - $this->from_site($user->jabber, $notice); + + try { + $notice = Notice::saveNew($user->id, $content_shortened, 'xmpp'); + } catch (Exception $e) { + $this->log(LOG_ERR, $e->getMessage()); + $this->from_site($user->jabber, $e->getMessage()); return; } + common_broadcast_notice($notice); $this->log(LOG_INFO, 'Added notice ' . $notice->id . ' from user ' . $user->nickname); diff --git a/tests/HashTagDetectionTests.php b/tests/HashTagDetectionTests.php index aeac4a5e3..483d7135e 100644 --- a/tests/HashTagDetectionTests.php +++ b/tests/HashTagDetectionTests.php @@ -7,6 +7,7 @@ if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); define('STATUSNET', true); +define('LACONICA', true); require_once INSTALLDIR . '/lib/common.php'; diff --git a/tests/URLDetectionTest.php b/tests/URLDetectionTest.php index 87b537646..45203bf6e 100644 --- a/tests/URLDetectionTest.php +++ b/tests/URLDetectionTest.php @@ -7,6 +7,7 @@ if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); define('STATUSNET', true); +define('LACONICA', true); require_once INSTALLDIR . '/lib/common.php'; @@ -27,68 +28,72 @@ class URLDetectionTest extends PHPUnit_Framework_TestCase return array( array('not a link :: no way', 'not a link :: no way'), + array('link http://www.somesite.com/xyz/35637563@N00/52803365/ link', + 'link <a href="http://www.somesite.com/xyz/35637563@N00/52803365/" title="http://www.somesite.com/xyz/35637563@N00/52803365/" rel="external">http://www.somesite.com/xyz/35637563@N00/52803365/</a> link'), array('http://127.0.0.1', - '<a href="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'), + '<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'), array('127.0.0.1', - '<a href="http://127.0.0.1/" rel="external">127.0.0.1</a>'), + '<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="external">127.0.0.1</a>'), array('127.0.0.1:99', - '<a href="http://127.0.0.1:99/" rel="external">127.0.0.1:99</a>'), + '<a href="http://127.0.0.1:99/" title="http://127.0.0.1:99/" rel="external">127.0.0.1:99</a>'), array('127.0.0.1/Name:test.php', - '<a href="http://127.0.0.1/Name:test.php" rel="external">127.0.0.1/Name:test.php</a>'), + '<a href="http://127.0.0.1/Name:test.php" title="http://127.0.0.1/Name:test.php" rel="external">127.0.0.1/Name:test.php</a>'), array('127.0.0.1/~test', - '<a href="http://127.0.0.1/~test" rel="external">127.0.0.1/~test</a>'), + '<a href="http://127.0.0.1/~test" title="http://127.0.0.1/~test" rel="external">127.0.0.1/~test</a>'), array('127.0.0.1/+test', - '<a href="http://127.0.0.1/+test" rel="external">127.0.0.1/+test</a>'), + '<a href="http://127.0.0.1/+test" title="http://127.0.0.1/+test" rel="external">127.0.0.1/+test</a>'), array('127.0.0.1/$test', - '<a href="http://127.0.0.1/$test" rel="external">127.0.0.1/$test</a>'), + '<a href="http://127.0.0.1/$test" title="http://127.0.0.1/$test" rel="external">127.0.0.1/$test</a>'), array('127.0.0.1/\'test', - '<a href="http://127.0.0.1/\'test" rel="external">127.0.0.1/\'test</a>'), + '<a href="http://127.0.0.1/\'test" title="http://127.0.0.1/\'test" rel="external">127.0.0.1/\'test</a>'), array('127.0.0.1/"test', - '<a href="http://127.0.0.1/"test" rel="external">127.0.0.1/"test</a>'), + '<a href="http://127.0.0.1/"test" title="http://127.0.0.1/"test" rel="external">127.0.0.1/"test</a>'), array('127.0.0.1/-test', - '<a href="http://127.0.0.1/-test" rel="external">127.0.0.1/-test</a>'), + '<a href="http://127.0.0.1/-test" title="http://127.0.0.1/-test" rel="external">127.0.0.1/-test</a>'), array('127.0.0.1/_test', - '<a href="http://127.0.0.1/_test" rel="external">127.0.0.1/_test</a>'), + '<a href="http://127.0.0.1/_test" title="http://127.0.0.1/_test" rel="external">127.0.0.1/_test</a>'), array('127.0.0.1/!test', - '<a href="http://127.0.0.1/!test" rel="external">127.0.0.1/!test</a>'), + '<a href="http://127.0.0.1/!test" title="http://127.0.0.1/!test" rel="external">127.0.0.1/!test</a>'), array('127.0.0.1/*test', - '<a href="http://127.0.0.1/*test" rel="external">127.0.0.1/*test</a>'), + '<a href="http://127.0.0.1/*test" title="http://127.0.0.1/*test" rel="external">127.0.0.1/*test</a>'), array('127.0.0.1/test%20stuff', - '<a href="http://127.0.0.1/test%20stuff" rel="external">127.0.0.1/test%20stuff</a>'), + '<a href="http://127.0.0.1/test%20stuff" title="http://127.0.0.1/test%20stuff" rel="external">127.0.0.1/test%20stuff</a>'), array('http://[::1]:99/test.php', - '<a href="http://[::1]:99/test.php" rel="external">http://[::1]:99/test.php</a>'), + '<a href="http://[::1]:99/test.php" title="http://[::1]:99/test.php" rel="external">http://[::1]:99/test.php</a>'), array('http://::1/test.php', - '<a href="http://::1/test.php" rel="external">http://::1/test.php</a>'), + '<a href="http://::1/test.php" title="http://::1/test.php" rel="external">http://::1/test.php</a>'), array('http://::1', - '<a href="http://::1/" rel="external">http://::1</a>'), + '<a href="http://::1/" title="http://::1/" rel="external">http://::1</a>'), array('2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php', - '<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php" rel="external">2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php</a>'), + '<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php" title="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php" rel="external">2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php</a>'), array('[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php', - '<a href="http://[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php" rel="external">[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php</a>'), + '<a href="http://[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php" title="http://[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php" rel="external">[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php</a>'), array('2001:4978:1b5:0:21d:e0ff:fe66:59ab', - '<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/" rel="external">2001:4978:1b5:0:21d:e0ff:fe66:59ab</a>'), + '<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/" title="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/" rel="external">2001:4978:1b5:0:21d:e0ff:fe66:59ab</a>'), array('http://127.0.0.1', - '<a href="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'), + '<a href="http://127.0.0.1/" title="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'), array('example.com', - '<a href="http://example.com/" rel="external">example.com</a>'), + '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>'), array('example.com', - '<a href="http://example.com/" rel="external">example.com</a>'), + '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>'), array('http://example.com', - '<a href="http://example.com/" rel="external">http://example.com</a>'), + '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>'), array('http://example.com.', - '<a href="http://example.com/" rel="external">http://example.com</a>.'), + '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>.'), array('/var/lib/example.so', '/var/lib/example.so'), array('example', 'example'), array('user@example.com', - '<a href="mailto:user@example.com" rel="external">user@example.com</a>'), + '<a href="mailto:user@example.com" title="mailto:user@example.com" rel="external">user@example.com</a>'), array('user_name+other@example.com', - '<a href="mailto:user_name+other@example.com" rel="external">user_name+other@example.com</a>'), + '<a href="mailto:user_name+other@example.com" title="mailto:user_name+other@example.com" rel="external">user_name+other@example.com</a>'), array('mailto:user@example.com', - '<a href="mailto:user@example.com" rel="external">mailto:user@example.com</a>'), + '<a href="mailto:user@example.com" title="mailto:user@example.com" rel="external">mailto:user@example.com</a>'), array('mailto:user@example.com?subject=test', - '<a href="mailto:user@example.com?subject=test" rel="external">mailto:user@example.com?subject=test</a>'), + '<a href="mailto:user@example.com?subject=test" title="mailto:user@example.com?subject=test" rel="external">mailto:user@example.com?subject=test</a>'), + array('xmpp:user@example.com', + '<a href="xmpp:user@example.com" title="xmpp:user@example.com" rel="external">xmpp:user@example.com</a>'), array('#example', '#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('example'))) . '" rel="tag">example</a></span>'), array('#example.com', @@ -96,165 +101,165 @@ class URLDetectionTest extends PHPUnit_Framework_TestCase array('#.net', '#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('.net'))) . '" rel="tag">.net</a></span>'), array('http://example', - '<a href="http://example/" rel="external">http://example</a>'), + '<a href="http://example/" title="http://example/" rel="external">http://example</a>'), array('http://3xampl3', - '<a href="http://3xampl3/" rel="external">http://3xampl3</a>'), + '<a href="http://3xampl3/" title="http://3xampl3/" rel="external">http://3xampl3</a>'), array('http://example/', - '<a href="http://example/" rel="external">http://example/</a>'), + '<a href="http://example/" title="http://example/" rel="external">http://example/</a>'), array('http://example/path', - '<a href="http://example/path" rel="external">http://example/path</a>'), + '<a href="http://example/path" title="http://example/path" rel="external">http://example/path</a>'), array('http://example.com', - '<a href="http://example.com/" rel="external">http://example.com</a>'), + '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>'), array('https://example.com', - '<a href="https://example.com/" rel="external">https://example.com</a>'), + '<a href="https://example.com/" title="https://example.com/" rel="external">https://example.com</a>'), array('ftp://example.com', - '<a href="ftp://example.com/" rel="external">ftp://example.com</a>'), + '<a href="ftp://example.com/" title="ftp://example.com/" rel="external">ftp://example.com</a>'), array('ftps://example.com', - '<a href="ftps://example.com/" rel="external">ftps://example.com</a>'), + '<a href="ftps://example.com/" title="ftps://example.com/" rel="external">ftps://example.com</a>'), array('http://user@example.com', - '<a href="http://user@example.com/" rel="external">http://user@example.com</a>'), + '<a href="http://user@example.com/" title="http://user@example.com/" rel="external">http://user@example.com</a>'), array('http://user:pass@example.com', - '<a href="http://user:pass@example.com/" rel="external">http://user:pass@example.com</a>'), + '<a href="http://user:pass@example.com/" title="http://user:pass@example.com/" rel="external">http://user:pass@example.com</a>'), array('http://example.com:8080', - '<a href="http://example.com:8080/" rel="external">http://example.com:8080</a>'), + '<a href="http://example.com:8080/" title="http://example.com:8080/" rel="external">http://example.com:8080</a>'), array('http://example.com:8080/test.php', - '<a href="http://example.com:8080/test.php" rel="external">http://example.com:8080/test.php</a>'), + '<a href="http://example.com:8080/test.php" title="http://example.com:8080/test.php" rel="external">http://example.com:8080/test.php</a>'), array('example.com:8080/test.php', - '<a href="http://example.com:8080/test.php" rel="external">example.com:8080/test.php</a>'), + '<a href="http://example.com:8080/test.php" title="http://example.com:8080/test.php" rel="external">example.com:8080/test.php</a>'), array('http://www.example.com', - '<a href="http://www.example.com/" rel="external">http://www.example.com</a>'), + '<a href="http://www.example.com/" title="http://www.example.com/" rel="external">http://www.example.com</a>'), array('http://example.com/', - '<a href="http://example.com/" rel="external">http://example.com/</a>'), + '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com/</a>'), array('http://example.com/path', - '<a href="http://example.com/path" rel="external">http://example.com/path</a>'), + '<a href="http://example.com/path" title="http://example.com/path" rel="external">http://example.com/path</a>'), array('http://example.com/path.html', - '<a href="http://example.com/path.html" rel="external">http://example.com/path.html</a>'), + '<a href="http://example.com/path.html" title="http://example.com/path.html" rel="external">http://example.com/path.html</a>'), array('http://example.com/path.html#fragment', - '<a href="http://example.com/path.html#fragment" rel="external">http://example.com/path.html#fragment</a>'), + '<a href="http://example.com/path.html#fragment" title="http://example.com/path.html#fragment" rel="external">http://example.com/path.html#fragment</a>'), array('http://example.com/path.php?foo=bar&bar=foo', - '<a href="http://example.com/path.php?foo=bar&bar=foo" rel="external">http://example.com/path.php?foo=bar&bar=foo</a>'), + '<a href="http://example.com/path.php?foo=bar&bar=foo" title="http://example.com/path.php?foo=bar&bar=foo" rel="external">http://example.com/path.php?foo=bar&bar=foo</a>'), array('http://example.com.', - '<a href="http://example.com/" rel="external">http://example.com</a>.'), + '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>.'), array('http://müllärör.de', - '<a href="http://müllärör.de/" rel="external">http://müllärör.de</a>'), + '<a href="http://müllärör.de/" title="http://müllärör.de/" rel="external">http://müllärör.de</a>'), array('http://ﺱﺲﺷ.com', - '<a href="http://ﺱﺲﺷ.com/" rel="external">http://ﺱﺲﺷ.com</a>'), + '<a href="http://ﺱﺲﺷ.com/" title="http://ﺱﺲﺷ.com/" rel="external">http://ﺱﺲﺷ.com</a>'), array('http://сделаткартинки.com', - '<a href="http://сделаткартинки.com/" rel="external">http://сделаткартинки.com</a>'), + '<a href="http://сделаткартинки.com/" title="http://сделаткартинки.com/" rel="external">http://сделаткартинки.com</a>'), array('http://tūdaliņ.lv', - '<a href="http://tūdaliņ.lv/" rel="external">http://tūdaliņ.lv</a>'), + '<a href="http://tūdaliņ.lv/" title="http://tūdaliņ.lv/" rel="external">http://tūdaliņ.lv</a>'), array('http://brændendekærlighed.com', - '<a href="http://brændendekærlighed.com/" rel="external">http://brændendekærlighed.com</a>'), + '<a href="http://brændendekærlighed.com/" title="http://brændendekærlighed.com/" rel="external">http://brændendekærlighed.com</a>'), array('http://あーるいん.com', - '<a href="http://あーるいん.com/" rel="external">http://あーるいん.com</a>'), + '<a href="http://あーるいん.com/" title="http://あーるいん.com/" rel="external">http://あーるいん.com</a>'), array('http://예비교사.com', - '<a href="http://예비교사.com/" rel="external">http://예비교사.com</a>'), + '<a href="http://예비교사.com/" title="http://예비교사.com/" rel="external">http://예비교사.com</a>'), array('http://example.com.', - '<a href="http://example.com/" rel="external">http://example.com</a>.'), + '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>.'), array('http://example.com?', - '<a href="http://example.com/" rel="external">http://example.com</a>?'), + '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>?'), array('http://example.com!', - '<a href="http://example.com/" rel="external">http://example.com</a>!'), + '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>!'), array('http://example.com,', - '<a href="http://example.com/" rel="external">http://example.com</a>,'), + '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>,'), array('http://example.com;', - '<a href="http://example.com/" rel="external">http://example.com</a>;'), + '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>;'), array('http://example.com:', - '<a href="http://example.com/" rel="external">http://example.com</a>:'), + '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>:'), array('\'http://example.com\'', - '\'<a href="http://example.com/" rel="external">http://example.com</a>\''), + '\'<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>\''), array('"http://example.com"', - '"<a href="http://example.com/" rel="external">http://example.com</a>"'), + '"<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>"'), array('http://example.com', - '<a href="http://example.com/" rel="external">http://example.com</a>'), + '<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>'), array('(http://example.com)', - '(<a href="http://example.com/" rel="external">http://example.com</a>)'), + '(<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>)'), array('[http://example.com]', - '[<a href="http://example.com/" rel="external">http://example.com</a>]'), + '[<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>]'), array('<http://example.com>', - '<<a href="http://example.com/" rel="external">http://example.com</a>>'), + '<<a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a>>'), array('http://example.com/path/(foo)/bar', - '<a href="http://example.com/path/(foo)/bar" rel="external">http://example.com/path/(foo)/bar</a>'), + '<a href="http://example.com/path/(foo)/bar" title="http://example.com/path/(foo)/bar" rel="external">http://example.com/path/(foo)/bar</a>'), array('http://example.com/path/[foo]/bar', - '<a href="http://example.com/path/[foo]/bar" rel="external">http://example.com/path/[foo]/bar</a>'), + '<a href="http://example.com/path/[foo]/bar" title="http://example.com/path/[foo]/bar" rel="external">http://example.com/path/[foo]/bar</a>'), array('http://example.com/path/foo/(bar)', - '<a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>'), + '<a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>'), //Not a valid url - urls cannot contain unencoded square brackets array('http://example.com/path/foo/[bar]', - '<a href="http://example.com/path/foo/[bar]" rel="external">http://example.com/path/foo/[bar]</a>'), + '<a href="http://example.com/path/foo/[bar]" title="http://example.com/path/foo/[bar]" rel="external">http://example.com/path/foo/[bar]</a>'), array('Hey, check out my cool site http://example.com okay?', - 'Hey, check out my cool site <a href="http://example.com/" rel="external">http://example.com</a> okay?'), + 'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">http://example.com</a> okay?'), array('What about parens (e.g. http://example.com/path/foo/(bar))?', - 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>)?'), + 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>)?'), array('What about parens (e.g. http://example.com/path/foo/(bar)?', - 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>?'), + 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>?'), array('What about parens (e.g. http://example.com/path/foo/(bar).)?', - 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>.)?'), + 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>.)?'), //Not a valid url - urls cannot contain unencoded commas array('What about parens (e.g. http://example.com/path/(foo,bar)?', - 'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" rel="external">http://example.com/path/(foo,bar)</a>?'), + 'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" title="http://example.com/path/(foo,bar)" rel="external">http://example.com/path/(foo,bar)</a>?'), array('Unbalanced too (e.g. http://example.com/path/((((foo)/bar)?', - 'Unbalanced too (e.g. <a href="http://example.com/path/((((foo)/bar)" rel="external">http://example.com/path/((((foo)/bar)</a>?'), + 'Unbalanced too (e.g. <a href="http://example.com/path/((((foo)/bar)" title="http://example.com/path/((((foo)/bar)" rel="external">http://example.com/path/((((foo)/bar)</a>?'), array('Unbalanced too (e.g. http://example.com/path/(foo))))/bar)?', - 'Unbalanced too (e.g. <a href="http://example.com/path/(foo))))/bar" rel="external">http://example.com/path/(foo))))/bar</a>)?'), + 'Unbalanced too (e.g. <a href="http://example.com/path/(foo))))/bar" title="http://example.com/path/(foo))))/bar" rel="external">http://example.com/path/(foo))))/bar</a>)?'), array('Unbalanced too (e.g. http://example.com/path/foo/((((bar)?', - 'Unbalanced too (e.g. <a href="http://example.com/path/foo/((((bar)" rel="external">http://example.com/path/foo/((((bar)</a>?'), + 'Unbalanced too (e.g. <a href="http://example.com/path/foo/((((bar)" title="http://example.com/path/foo/((((bar)" rel="external">http://example.com/path/foo/((((bar)</a>?'), array('Unbalanced too (e.g. http://example.com/path/foo/(bar))))?', - 'Unbalanced too (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>)))?'), + 'Unbalanced too (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>)))?'), array('example.com', - '<a href="http://example.com/" rel="external">example.com</a>'), + '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>'), array('example.org', - '<a href="http://example.org/" rel="external">example.org</a>'), + '<a href="http://example.org/" title="http://example.org/" rel="external">example.org</a>'), array('example.co.uk', - '<a href="http://example.co.uk/" rel="external">example.co.uk</a>'), + '<a href="http://example.co.uk/" title="http://example.co.uk/" rel="external">example.co.uk</a>'), array('www.example.co.uk', - '<a href="http://www.example.co.uk/" rel="external">www.example.co.uk</a>'), + '<a href="http://www.example.co.uk/" title="http://www.example.co.uk/" rel="external">www.example.co.uk</a>'), array('farm1.images.example.co.uk', - '<a href="http://farm1.images.example.co.uk/" rel="external">farm1.images.example.co.uk</a>'), + '<a href="http://farm1.images.example.co.uk/" title="http://farm1.images.example.co.uk/" rel="external">farm1.images.example.co.uk</a>'), array('example.museum', - '<a href="http://example.museum/" rel="external">example.museum</a>'), + '<a href="http://example.museum/" title="http://example.museum/" rel="external">example.museum</a>'), array('example.travel', - '<a href="http://example.travel/" rel="external">example.travel</a>'), + '<a href="http://example.travel/" title="http://example.travel/" rel="external">example.travel</a>'), array('example.com.', - '<a href="http://example.com/" rel="external">example.com</a>.'), + '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>.'), array('example.com?', - '<a href="http://example.com/" rel="external">example.com</a>?'), + '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>?'), array('example.com!', - '<a href="http://example.com/" rel="external">example.com</a>!'), + '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>!'), array('example.com,', - '<a href="http://example.com/" rel="external">example.com</a>,'), + '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>,'), array('example.com;', - '<a href="http://example.com/" rel="external">example.com</a>;'), + '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>;'), array('example.com:', - '<a href="http://example.com/" rel="external">example.com</a>:'), + '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>:'), array('\'example.com\'', - '\'<a href="http://example.com/" rel="external">example.com</a>\''), + '\'<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>\''), array('"example.com"', - '"<a href="http://example.com/" rel="external">example.com</a>"'), + '"<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>"'), array('example.com', - '<a href="http://example.com/" rel="external">example.com</a>'), + '<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>'), array('(example.com)', - '(<a href="http://example.com/" rel="external">example.com</a>)'), + '(<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>)'), array('[example.com]', - '[<a href="http://example.com/" rel="external">example.com</a>]'), + '[<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>]'), array('<example.com>', - '<<a href="http://example.com/" rel="external">example.com</a>>'), + '<<a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>>'), array('Hey, check out my cool site example.com okay?', - 'Hey, check out my cool site <a href="http://example.com/" rel="external">example.com</a> okay?'), + 'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">example.com</a> okay?'), array('Hey, check out my cool site example.com.I made it.', - 'Hey, check out my cool site <a href="http://example.com/" rel="external">example.com</a>.I made it.'), + 'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>.I made it.'), array('Hey, check out my cool site example.com.Funny thing...', - 'Hey, check out my cool site <a href="http://example.com/" rel="external">example.com</a>.Funny thing...'), + 'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>.Funny thing...'), array('Hey, check out my cool site example.com.You will love it.', - 'Hey, check out my cool site <a href="http://example.com/" rel="external">example.com</a>.You will love it.'), + 'Hey, check out my cool site <a href="http://example.com/" title="http://example.com/" rel="external">example.com</a>.You will love it.'), array('What about parens (e.g. example.com/path/foo/(bar))?', - 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>)?'), + 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>)?'), array('What about parens (e.g. example.com/path/foo/(bar)?', - 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>?'), + 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>?'), array('What about parens (e.g. example.com/path/foo/(bar).)?', - 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>.)?'), + 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" title="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>.)?'), array('What about parens (e.g. example.com/path/(foo,bar)?', - 'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" rel="external">example.com/path/(foo,bar)</a>?'), + 'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" title="http://example.com/path/(foo,bar)" rel="external">example.com/path/(foo,bar)</a>?'), array('file.ext', 'file.ext'), array('file.html', diff --git a/tests/UserRightsTest.php b/tests/UserRightsTest.php new file mode 100644 index 000000000..6544ee53d --- /dev/null +++ b/tests/UserRightsTest.php @@ -0,0 +1,59 @@ +<?php + +if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { + print "This script must be run from the command line\n"; + exit(); +} + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); +define('STATUSNET', true); + +require_once INSTALLDIR . '/lib/common.php'; + +class UserRightsTest extends PHPUnit_Framework_TestCase +{ + protected $user = null; + + function setUp() + { + $this->user = User::register(array('nickname' => 'userrightstestuser')); + } + + function tearDown() + { + $profile = $this->user->getProfile(); + $this->user->delete(); + $profile->delete(); + } + + function testInvalidRole() + { + $this->assertFalse($this->user->hasRole('invalidrole')); + } + + function standardRoles() + { + return array('admin', 'moderator'); + } + + /** + * @dataProvider standardRoles + * + */ + + function testUngrantedRole($role) + { + $this->assertFalse($this->user->hasRole($role)); + } + + /** + * @dataProvider standardRoles + * + */ + + function testGrantedRole($role) + { + $this->user->grantRole($role); + $this->assertFalse($this->user->hasRole($role)); + } +}
\ No newline at end of file diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 1f37a7637..d9dca9815 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -251,7 +251,7 @@ margin-right:18px; margin-bottom:11px; margin-left:18px; } -#site_nav_global_primary ul li { +#site_nav_global_primary li { display:inline; margin-left:11px; } @@ -468,23 +468,22 @@ margin-bottom:7px; #form_notice #notice_data-attach { position:absolute; top:25px; +right:10.5%; cursor:pointer; } #form_notice label[for=notice_data-attach] { text-indent:-9999px; -left:86%; width:16px; height:16px; } #form_notice #notice_data-attach { -left:40.6%; padding:0; height:16px; } #form_notice .form_note { position:absolute; bottom:2px; -right:98px; +right:21.715%; z-index:9; } #form_notice .form_note dt { diff --git a/theme/base/images/icons/icons-01.png b/theme/base/images/icons/icons-01.png Binary files differnew file mode 100644 index 000000000..c4e371330 --- /dev/null +++ b/theme/base/images/icons/icons-01.png diff --git a/theme/cloudy/css/display.css b/theme/cloudy/css/display.css index 3851bc057..3fe05eb3d 100644 --- a/theme/cloudy/css/display.css +++ b/theme/cloudy/css/display.css @@ -120,6 +120,10 @@ float:left; margin-left:11px; float:left; } +.form_settings .form_data textarea { +width:325px; +} + .form_settings .form_data input.submit { margin-left:0; } @@ -968,9 +972,6 @@ right:7px; top:47px; right:7px; } -.notice-options .notice_reply dt { -display:none; -} .notice-options input, .notice-options a { @@ -978,13 +979,13 @@ text-indent:-9999px; outline:none; } -.notice-options .notice_reply a, +.notice-options .notice_reply, .notice-options input.submit { display:block; border:0; } -.notice-options .notice_reply a, -.notice-options .notice_delete a { +.notice-options .notice_reply, +.notice-options .notice_delete { text-decoration:none; padding-left:16px; } @@ -1375,6 +1376,12 @@ padding-top:160px; #smssettings #form_notice, #twittersettings #form_notice, #imsettings #form_notice, +#userdesignsettings #form_notice, +#groupdesignsettings #form_notice, +#grouplogo #form_notice, +#editgroup #form_notice, +#blockedfromgroup #form_notice, +#groupmembers #form_notice, #doc #form_notice, #usergroups #form_notice, #invite #form_notice, @@ -1584,11 +1591,11 @@ background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no background:none; } -.notice-options .notice_reply a, +.notice-options .notice_reply, .notice-options form input.submit { background-color:transparent; } -.notice-options .notice_reply a { +.notice-options .notice_reply { background:transparent url(../images/icons/icon_reply.gif) no-repeat 0 45%; } .notice-options form.form_favor input.submit { @@ -1597,7 +1604,7 @@ background:transparent url(../images/icons/icon_favourite.gif) no-repeat 0 45%; .notice-options form.form_disfavor input.submit { background:transparent url(../images/icons/icon_disfavourite.gif) no-repeat 0 45%; } -.notice-options .notice_delete a { +.notice-options .notice_delete { background:transparent url(../images/icons/icon_trash.gif) no-repeat 0 45%; } diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css index 9fc97180d..6339c9314 100644 --- a/theme/identica/css/display.css +++ b/theme/identica/css/display.css @@ -88,7 +88,7 @@ color:#333333; color:#000000; } #form_notice label[for=notice_data-attach] { -background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%; +background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -328px; } #form_notice #notice_data-attach { opacity:0; @@ -150,16 +150,18 @@ background-color:#9BB43E; #export_data li a { background-repeat:no-repeat; -background-position:0 45%; } #export_data li a.rss { -background-image:url(../../base/images/icons/icon_rss.png); +background-image:url(../../base/images/icons/icons-01.png); +background-position:0 -130px; } #export_data li a.atom { -background-image:url(../../base/images/icons/icon_atom.png); +background-image:url(../../base/images/icons/icons-01.png); +background-position:0 -64px; } #export_data li a.foaf { -background-image:url(../../base/images/icons/icon_foaf.gif); +background-image:url(../../base/images/icons/icons-01.png); +background-position:0 1px; } .entity_edit a, @@ -171,7 +173,6 @@ background-image:url(../../base/images/icons/icon_foaf.gif); .form_group_unblock input.submit, .entity_nudge p, .form_make_admin input.submit { -background-position: 0 40%; background-repeat: no-repeat; background-color:transparent; } @@ -189,43 +190,48 @@ background-color:#87B4C8; } .entity_edit a { -background-image:url(../../base/images/icons/twotone/green/edit.gif); +background-image:url(../../base/images/icons/icons-01.png); +background-position: 0 -718px; } .entity_send-a-message a { -background-image:url(../../base/images/icons/twotone/green/quote.gif); +background-image:url(../../base/images/icons/icons-01.png); +background-position: 0 -849px; } .entity_nudge p, .form_user_nudge input.submit { -background-image:url(../../base/images/icons/twotone/green/mail.gif); +background-image:url(../../base/images/icons/icons-01.png); +background-position: 0 -785px; } .form_user_block input.submit, .form_user_unblock input.submit, .form_group_block input.submit, .form_group_unblock input.submit { -background-image:url(../../base/images/icons/twotone/green/shield.gif); +background-image:url(../../base/images/icons/icons-01.png); +background-position: 0 -918px; } .form_make_admin input.submit { -background-image:url(../../base/images/icons/twotone/green/admin.gif); +background-image:url(../../base/images/icons/icons-01.png); +background-position: 0 -983px; } /* NOTICES */ .notice .attachment { -background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%; +background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -394px; } #attachments .attachment { background:none; } .notice-options .notice_reply { -background:transparent url(../../base/images/icons/twotone/green/reply.gif) no-repeat 0 45%; +background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -589px; } .notice-options form.form_favor input.submit { -background:transparent url(../../base/images/icons/twotone/green/favourite.gif) no-repeat 0 45%; +background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -457px; } .notice-options form.form_disfavor input.submit { -background:transparent url(../../base/images/icons/twotone/green/disfavourite.gif) no-repeat 0 45%; +background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -523px; } .notice-options .notice_delete { -background:transparent url(../../base/images/icons/twotone/green/trash.gif) no-repeat 0 45%; +background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -655px; } .notices div.entry-content, @@ -262,7 +268,7 @@ background-color:rgba(200, 200, 200, 0.300); /*END: NOTICES */ #new_group a { -background:transparent url(../../base/images/icons/twotone/green/news.gif) no-repeat 0 45%; +background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -1054px; } .pagination .nav_prev a, @@ -271,10 +277,10 @@ background-repeat:no-repeat; border-color:#CEE1E9; } .pagination .nav_prev a { -background-image:url(../../base/images/icons/twotone/green/arrow-left.gif); -background-position:10% 45%; +background-image:url(../../base/images/icons/icons-01.png); +background-position:10% -187px; } .pagination .nav_next a { -background-image:url(../../base/images/icons/twotone/green/arrow-right.gif); -background-position:90% 45%; +background-image:url(../../base/images/icons/icons-01.png); +background-position:105% -252px; } diff --git a/theme/identica/css/ie.css b/theme/identica/css/ie.css index 97cabc30a..044c32ff1 100644 --- a/theme/identica/css/ie.css +++ b/theme/identica/css/ie.css @@ -7,8 +7,14 @@ color:#FFFFFF; background-color:#D9DADB; } #form_notice .form_note + label { -background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%; +background:transparent url(../../base/images/icons/icons-01.png) no-repeat 0 -328px; } #form_notice #notice_data-attach { filter: alpha(opacity=0); } +.notice-options form.form_favor input.submit { +background-position:0 -460px; +} +.notice-options form.form_disfavor input.submit { +background-position:0 -526px; +} diff --git a/theme/iphone/bg-body.gif b/theme/iphone/bg-body.gif Binary files differdeleted file mode 100644 index d87e2e8d7..000000000 --- a/theme/iphone/bg-body.gif +++ /dev/null diff --git a/theme/iphone/bg-header.gif b/theme/iphone/bg-header.gif Binary files differdeleted file mode 100644 index 5154b2e5e..000000000 --- a/theme/iphone/bg-header.gif +++ /dev/null diff --git a/theme/iphone/default-avatar-mini.png b/theme/iphone/default-avatar-mini.png Binary files differdeleted file mode 100644 index 38b8692b4..000000000 --- a/theme/iphone/default-avatar-mini.png +++ /dev/null diff --git a/theme/iphone/default-avatar-profile.png b/theme/iphone/default-avatar-profile.png Binary files differdeleted file mode 100644 index f8357d4fc..000000000 --- a/theme/iphone/default-avatar-profile.png +++ /dev/null diff --git a/theme/iphone/default-avatar-stream.png b/theme/iphone/default-avatar-stream.png Binary files differdeleted file mode 100644 index 6b63baa70..000000000 --- a/theme/iphone/default-avatar-stream.png +++ /dev/null diff --git a/theme/iphone/display.css b/theme/iphone/display.css deleted file mode 100644 index 1838a8e86..000000000 --- a/theme/iphone/display.css +++ /dev/null @@ -1,698 +0,0 @@ -/* CSS Document */ -/* Design & CSS by Marie-Claude Doyon http://www.marieclaudedoyon.com */ -/* Simplified for mobile by Ken Sheppardson http://identi.ca/kshep */ - -@import url(../../base/css/display.css); - -html {} -body { - width: 100%; - padding: 0; - margin: 0; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 24px; - min-height: 100%; - height: 100%; - color: #193441; -} - -a { - color: #C15D42; - text-decoration: none; -} -a:hover { - text-decoration: underline; -} -img, img a { - border: 0; -} -h1 { - font-size: 1.2em; -} - -#wrap { - margin: 0; -} - -#header { - width: 100%; - float: left; - background-color: #193441; - margin: 0 0 20px 0; - padding: 0; -} -#logo { - float: left; - margin: 10px 0px 0px 10px; -} -p#branding { - margin: 0; - padding: 6px 0 3px 0; - color: #fbf2d7; - font-size: 2em; - font-weight: bold; - line-height: 2.5em; -} -p#branding a { - color: #dab134; -} - -#header h1.pagetitle { - display: none; - margin: 0; - padding: 0; - font-size: 1.2em; - line-height: 2em; - color: #d8e2d7; -} - -#header h2.sitename { - display: none; - margin: 0; - padding: 0; - color: #FCFFF5; -} - -/* ===== Begin Navigation Styling ===== */ - -/* ----- Navigation ------ */ -#nav { - float: right; - margin: 0; - padding: 0; - list-style-type: none; - font-size: 1.2em; -} -#nav li { - display: block; - float: left; -} -#nav li a { - display: block; - padding: 9px 15px 12px 0px; - color: #91AA9D; -} -#nav li a:hover { - text-decoration: underline; -} - -/* ----- Tabs ----- */ -#nav_views { - clear: both; - float: left; - margin: 10px 0px 0px 5px; - padding: 0; - bottom: 0; - list-style-type: none; - font-size: 1.1em; - font-weight: bold; -} -#nav_views li { - display: block; - float: left; - line-height: 1.3em; -} -#nav_views li a { - display: block; - margin: 0; - padding: 4px 12px 3px 12px; - color: #FCFFF5; - background-color: #91AA9D; - border-right: 1px solid #6A8787; -} -#nav_views li a:hover { - text-decoration: none; -} -#nav_views li.current a, #nav_views li.current a:hover { - color: #3F606F; - background-color: #FCFFF5; - border-right: 1px solid #6A8787; -} -#nav_views li.current a:hover { - color: #193441; -} -#nav_views li a:hover { - color: #FCFFF5; - background-color: #3F606F; - border-right: 1px solid #6A8787; -} - -/* ----- Nav Footer ----- */ -#nav_sub { - clear: both; - margin: 18px 10px 0 10px; - padding: 0; - list-style-type: none; - font-size: 1.1em; - font-weight: bold; - line-height: 2em; - border-top: 1px solid #D8E2D7; -} -#nav_sub li { - display: block; - float: left; -} -#nav_sub li a { - padding: 6px 24px 6px 0; -} -#nav_sub li a:hover { - text-decoration: underline; -} -/* ===== End Navigation Styling ===== */ - -#content { - clear: left; - margin: 10px; - font-family: Georgia, "Times New Roman", Times, serif; - font-size: 1em; - line-height: 1.1em; -} -#content h2 { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 1.1em; -} -#content label { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 1.1em; -} - -.instructions { - clear: both; - float: left; - margin: 5px 5px 10px 5px; -} -.instructions p, .success, .error { - font-weight: normal; - margin: 0; - padding: 10px; - font-family: Georgia, "Times New Roman", Times, serif; - font-size: 1.1em; - line-height: 1.2em; - border: 1px solid #91AA9D; - color: #FCFFF5; -} -.instructions a, .success a, .error a { - color: #d8e2d7; - text-decoration: underline; -} -.instructions a:hover, .success a:hover, .error a:hover { - color: #FCFFF5; -} -.success { - clear: both; - float: left; - margin: 5px 5px 10px 5px; - background-color: #48705b; -} -.error { - clear: both; - float: left; - margin: 5px 5px 10px 5px; - background-color: #ce3728; -} - -/* ----- Stream -----*/ - -#notices { - clear: both; - margin: 0 auto; - padding: 0; - list-style-type: none; - border-top: 1px solid #D8E2D7; -} -#notices a:hover { - text-decoration: underline; -} -.notice_single { - clear: both; - display: block; - margin: 0; - padding: 5px 5px 5px 0; - min-height: 48px; - font-family: Georgia, "Times New Roman", Times, serif; - font-size: 1em; - line-height: 1.4em; - border-bottom: 1px solid #D8E2D7; -} -.notice_single:hover { - background-color: #F3F8EA; -} -.notice_single p { - display: inline; - margin: 0; - padding: 0; -} -#notice_delete_form #confirmation_text { - display: block; - font-size: 1.1em; - font-weight: bold; -} -input#submit_yes, input#submit_no { - margin: 18px 10px 0px 0px; - padding: 4px; - font-weight: bold; - color: #FCFFF5; - background-color: #C15D42; - cursor: pointer; - border: 0; - width: 40px; -} -input#submit_yes:hover, input#submit_no:hover { - background-color: #904632; -} -.avatar.stream { - float: left; - margin: 0 10px 0 0; -} -p.time { - display: block; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 0.9em; - line-height: 2em; -} -p.time a { - color: #91AA9D; -} - -/* ----- Profile -----*/ -#profile { - clear: both; - float: left; - padding: 10px 0 0 0; - border-top: 1px solid #D8E2D7; - font-family: Georgia, "Times New Roman", Times, serif; -} -#profile h1 { - clear: both; - float: left; - margin: 0; - padding: 0; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 1.2em; -} -#profile h2 { - clear: both; - float: left; - margin: 0; - padding: 1em 0 0.2em 0; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 1.1em; - text-transform: uppercase; - color: #91AA9D; -} -#profile p { - clear: both; - float: left; - margin: 0 10px 0 0; - font-size: 1em; - line-height: 1.4em; -} -#profile p.location { - margin: 0 10px 12px 0; - font-style: italic; -} -#profile p.notice_current { - font-size: 1.2em; - line-height: 1.3em; -} -#profile_avatar { - float: left; - margin-right: 4px; -} -#profile_avatar img { - margin-bottom: 5px; -} -.avatar.profile { - clear: left; - margin: 0 10px 5px 0; -} -.avatar.original { - float: left; - margin: 0 10px 18px 0; -} -a.nickname { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-weight: bold; - font-size: 1.1em; - padding-right: 3px; -} -#profile_information { - float: left; -} - -.statistics { - clear: both; - float: left; -} -.statistics h2 { - clear: both; - float: left; - margin: 12px 0 3px 0; -} -dl.statistics { - margin: 0; -} -.statistics dt { - clear: left; - float: left; - width: 200px; -} -.statistics dd { - float: left; -} -.statistics dt:after { - content: ":"; -} -#subscriptions { - clear: both; - float: left; - margin: 18px 0 30px 0; -} -#subscriptions_avatars { - clear: both; - float: left; - margin: 6px 0 0 0; - padding: 0; - list-style-type: none; -} -#subscriptions_avatars li .avatar.mini { - float: left; - margin: 0 3px 3px 0; - padding: 0; - line-height: 0; -} -#subscriptions_viewall { - clear: left; -} -/* ----- End Profile -----*/ - -/* ----- Begin Subscriptions & Subscribers -----*/ - -ul.subscriptions, ul.subscribers { - float: none; - margin: 0; - padding: 0; - list-style-type: none; - overflow: auto; -} -ul.subscriptions li, ul.subscribers li { - display: block; - float: left; - padding: 0; -} -/* ----- End Subscriptions & Subscribers -----*/ - -#pagination { - margin: 18px auto; -} -#nav_pagination { - margin: 0 0 36px 0; - padding: 0; - float: right; - list-style-type: none; - font-size: 12px; - font-weight: bold; -} -#nav_pagination li { - display: block; - float: left; - background-color: #91AA9D; -} -#nav_pagination li.before { - margin-right: 1px; -} -#nav_pagination li a { - padding: 6px 15px; - line-height: 2em; - background-color: #91AA9D; - color: #FCFFF5; -} -#nav_pagination li a:hover { - background-color: #3F606F; - color: #FCFFF5; - text-decoration: none; -} - -#footer { - clear: both; - margin: 10px; - border-top: 1px solid #D8E2D7; -} -#footer p { - font-size: 0.8em; - margin-top: 1em; - line-height: 1.2em; -} -#cc { - float: left; - margin: 3px 10px 0 0; -} - -/* ===== Begin Forms Styling ===== */ - -/* ----- Forms General Style ----- */ -form { - margin: 0 auto; - padding: 0; -} -form { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 1em; -} -form label { - display: block; - font-size: 1em; - font-weight: bold; - line-height: 1.5em; -} -form input { - border: 1px solid #D8E2D7; - width: 264px; -} -input#submit, input.submit { - display: block; - margin: 18px 0; - padding: 4px; - font-weight: bold; - color: #FCFFF5; - background-color: #C15D42; - cursor: pointer; - border: 0; - width: auto; -} -input#submit:hover, input.submit:hover { - background-color: #904632; -} -input.checkbox { - width: auto; - border: 0; -} -textarea, input { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 1em; - color: #193441; - padding: 3px; -} -textarea:focus, input:focus { - background-color: #f0f6eb; -} -textarea { - width: 270px; - border: 1px solid #D8E2D7; -} -.input_instructions { - margin-top: 3px; - display: block; - font-size: 1em; - line-height: 1.2em; - color: #91aa9d; - font-family: Verdana, Arial, Helvetica, sans-serif; -} - -/* ----- Status Form ----- */ -#status_form { - width: 100%; - margin: 0px 0px 10px 5px; -} -#status_form p { - margin: 0; - padding: 0; -} -#status_label { - display: none; - clear: both; - margin: 0; - padding: 0 0 3px 0; - font-size: 1.5em; - font-weight: bold; - line-height: 2em; - color: #91AA9D; -} -#status_textarea { - display: block; - float: left; - width: 70%; - height: 3em; - margin: 0 0 10px 0; - padding: 0; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 1.1em; - color: #193441; - border: 0; -} -#status_submit { - display: block; - float: left; - margin: 0 0 0 4px; - padding: 1em 10px 1em 10px; - line-height: 1em; - width: 10%; - background-color: #C15D42; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-weight: bold; - font-size: 1em; - color: #FCFFF5; - cursor: pointer; - border: 0; -} -#status_submit:hover { - background-color: #904632; -} -#counter { - padding: 1em .5em 1em 5px; - color: #fff; - clear: both; - float: left; - font-weight: bold; - text-align: right; -} -/* ----- Subscribe Form ----- */ -#subscribe .submit, #unsubscribe .submit, #remotesubscribe .button, #remotesubscribe { - clear: left; - margin: 0; - width: 96px; - height: 27px; - font-family: verdana, arial, helvetica, sans-serif; - font-weight: bold; - font-size: 12px; - text-transform: uppercase; - background-color: #c15d42; - color: #fcfff5; - border: 0; -} -#remotesubscribe { - width: 96px; - height: 22px; - padding: 5px 0 0 0; - text-align: center; -} -#subscribe .button:hover, #unsubscribe .button:hover { - background-color: #904632; - cursor: pointer; -} - -a#remotesubscribe { - display: block; -} - -/* ----- Login Form -----*/ -input#license { - width: auto; - border: 0; -} -/* ----- Avatar Form -----*/ -form { - clear: left; -} - -/* ----- OpenID Form -----*/ - -input#openid_url { - background: url(login-bg.gif) no-repeat; - background-color: #fff; - background-position: 4px 50%; - color: #000; - padding-left: 24px; -} - -/* People lists (search results, maybe subscribers) */ - -#profiles { - clear: both; - margin: 0 auto; - padding: 0; - list-style-type: none; - border-top: 1px solid #D8E2D7; -} -#profiles a:hover { - text-decoration: underline; -} - -.profile_single { - clear: both; - display: block; - margin: 0; - padding: 5px 5px 5px 0; - min-height: 48px; - font-family: Georgia, "Times New Roman", Times, serif; - font-size: 1.2em; - line-height: 1.4em; - border-bottom: 1px solid #D8E2D7; -} -.profile_single:hover { - background-color: #F3F8EA; -} - -/* ----- IM Settings Form -----*/ - -#imsettings p { - margin: 0; - padding: 0; - line-height: 1.3em; -} - -/* ===== End Forms Styling ===== */ - -/* ===== Tag Cloud Styling ===== */ - -p.tagcloud { -text-align: center; -} - -p.tagcloud a { -line-height:1em; -vertical-align:middle; -} - -p.tagcloud a.largest { -font-size: 4em; -} -p.tagcloud a.verylarge { -font-size: 3em; -} - -p.tagcloud a.large { -font-size: 2em; -} - -p.tagcloud a.medium { -font-size: 1.5em; -} - -p.tagcloud a.small { -font-size: 1em; -} - -p.tagcloud a.verysmall { -font-size: 80%; -} - -p.tagcloud a.smallest { -font-size: 60%; -} - -a.replybutton { - border: 1px solid #D8E2D7; - padding: 0px 10px 0px 10px; - line-height: 0.8em; -} diff --git a/theme/iphone/display.css.dist b/theme/iphone/display.css.dist deleted file mode 100644 index 395da2e2f..000000000 --- a/theme/iphone/display.css.dist +++ /dev/null @@ -1,686 +0,0 @@ -/* CSS Document */ -/* Design & CSS by Marie-Claude Doyon http://www.marieclaudedoyon.com */ - -html { - background: url(bg-body.gif) repeat-y top center #d8e2d7; - } -body { - position: absolute; - width: 100%; - margin: 0; - padding: 0; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 10px; - line-height: 12px; - min-height: 100%; - height: 100%; - color: #193441; - } -a { - color: #C15D42; - text-decoration: none; - } -a:hover { - text-decoration: underline; - } -img, img a { - border: 0; - } -h1 { - font-size: 14px; - } - - -#wrap { - margin: 0 auto; - padding: 0 20px; - width: 760px; - background: url(bg-header.gif) repeat-x #FCFFF5; - } -#header { - position: relative; - margin: 0 auto; - width: 540px; - height: 216px; - } -#logo { - margin-top: 9px; - } -p#branding { - margin: 0; - padding: 6px 0 3px 0; - color: #fbf2d7; - font-size: 21px; - font-weight: bold; - line-height: 27px; - } -p#branding a { - color: #dab134; - } - -#header h1.pagetitle { - margin: 0; - padding: 0; - font-size: 15px; - line-height: 24px; - color: #d8e2d7; -} - -#header h2.sitename { - display: none; - margin: 0; - padding: 0; - color: #FCFFF5; -} - -/* ===== Begin Navigation Styling ===== */ - -/* ----- Navigation ------ */ -#nav { - float: right; - margin: 0; - padding: 0; - list-style-type: none; - font-size: 12px; - } -#nav li { - display: block; - float: left; - } -#nav li a { - display: block; - padding: 9px 9px 12px 9px; - color: #91AA9D; - } -#nav li a:hover { - text-decoration: underline; - } - -/* ----- Tabs ----- */ -#nav_views { - margin: 0 auto; - padding: 0; - position: absolute; - bottom: 0; - list-style-type: none; - font-size: 14px; - font-weight: bold; - width: 540px; - /*height: 30px;*/ - } -#nav_views li { - display: block; - float: left; - line-height: 21px; - } -#nav_views li a { - display: block; - margin: 0; - padding: 4px 12px 3px 12px; - color: #FCFFF5; - background-color: #91AA9D; - border-right: 1px solid #6A8787; - } -#nav_views li a:hover { - text-decoration: none; - } -#nav_views li.current a, #nav_views li.current a:hover { - color: #3F606F; - background-color: #FCFFF5; - border-right: 1px solid #6A8787; - } -#nav_views li.current a:hover { - color: #193441; - } -#nav_views li a:hover { - color: #FCFFF5; - background-color: #3F606F; - border-right: 1px solid #6A8787; - } - -/* ----- Nav Footer ----- */ -#nav_sub { - clear: both; - margin: 18px auto 0 auto; - padding: 0; - list-style-type: none; - font-size: 11px; - font-weight: bold; - line-height: 21px; - border-top: 1px solid #D8E2D7; - width: 540px; - } -#nav_sub li { - display: block; - float: left; - } -#nav_sub li a { - padding: 6px 24px 6px 0; - } -#nav_sub li a:hover { - text-decoration: underline; - } -/* ===== End Navigation Styling ===== */ - -#content { - clear: left; - margin: 40px 0 45px 0; - padding: 0 110px; - font-family: Georgia, "Times New Roman", Times, serif; - font-size: 14px; - line-height: 18px; - } -#content h2 { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 15px; - } -#content label { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 12px; - } -.instructions p, .success, .error { - font-weight: normal; - margin: 36px 0 0 0; - padding: 10px; - font-family: Georgia, "Times New Roman", Times, serif; - font-size: 13px; - line-height: 15px; - border: 1px solid #91AA9D; - color: #FCFFF5; - } -.instructions a, .success a, .error a { - color: #d8e2d7; - text-decoration: underline; - } -.instructions a:hover, .success a:hover, .error a:hover { - color: #FCFFF5; - } -.success { - background-color: #48705b; - } -.error { - background-color: #ce3728; - } - - -/* ----- Stream -----*/ - -#notices { - clear: both; - margin: 0 auto; - padding: 0; - list-style-type: none; - width: 540px; - border-top: 1px solid #D8E2D7; - } -#notices a:hover { - text-decoration: underline; - } -.notice_single { - clear: both; - display: block; - margin: 0; - padding: 5px 5px 5px 0; - min-height: 48px; - font-family: Georgia, "Times New Roman", Times, serif; - font-size: 13px; - line-height: 16px; - border-bottom: 1px solid #D8E2D7; - } -.notice_single:hover { - background-color: #F3F8EA; - } -.notice_single p { - display: inline; - margin: 0; - padding: 0; - } -#notice_delete_form #confirmation_text { - display: block; - font-size: 14px; - font-weight: bold; - } -input#submit_yes, input#submit_no { - margin: 18px 10px 0px 0px; - padding: 4px; - font-weight: bold; - color: #FCFFF5; - background-color: #C15D42; - cursor: pointer; - border: 0; - width: 40px; - } -input#submit_yes:hover, input#submit_no:hover { - background-color: #904632; - } -.avatar.stream { - float: left; - margin: 0 10px 0 0; - } -p.time { - display: block; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 10px; - line-height: 15px; - } -p.time a { - color: #91AA9D; - } - -/* ----- Profile -----*/ -#profile { - clear: left; - margin: 0 -110px; - padding: 10px 0 0 0; - min-height: 170px; - border-top: 1px solid #D8E2D7; - font-family: Georgia, "Times New Roman", Times, serif; - } -#profile h1 { - margin: 0; - padding: 0; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 14px; - } -#profile h2 { - margin: 0; - padding: 0; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 11px; - text-transform: uppercase; - color: #91AA9D; - } -#profile p { - margin: 0 10px 0 0; - font-size: 12px; - line-height: 14px; - } -#profile p.location { - margin: 0 10px 12px 0; - font-style: italic; - } -#profile p.notice_current { - font-size: 18px; - line-height: 21px; - } -#profile_avatar { - float: left; - margin-right: 4px; - } -#profile_avatar img { - margin-bottom: 5px; - } -.avatar.profile { - clear: left; - margin: 0 10px 5px 0; - } -.avatar.original { - float: left; - margin: 0 10px 18px 0; - } -a.nickname { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-weight: bold; - font-size: 12px; - padding-right: 3px; - } -#profile_information { - float: left; - position: relative; - width: 270px; - height: 200px; - } -.statistics { - margin-top: 18px; - } -.statistics h2 { - margin: 12px 0 3px 0; - } -dl.statistics { - margin: 0; - font-size: 12px; - line-height: 14px; - } -.statistics dt { - float: left; - width: 96px; -} -.statistics dd { - margin-left: 100px; -} -.statistics dt:after { - content: ":"; - } -#subscriptions { - float: left; - margin: 18px 0 30px 0; - } -#subscriptions_avatars { - float: left; - margin: 6px 0 0 0; - padding: 0; - list-style-type: none; - width: 270px; - } -#subscriptions_avatars li .avatar.mini { - float: left; - margin: 0 3px 3px 0; - padding: 0; - line-height: 0; - /* border: 1px solid #f00; */ - } -#subscriptions_viewall { - clear: left; - } -/* ----- End Profile -----*/ - -/* ----- Begin Subscriptions & Subscribers -----*/ - -ul.subscriptions, ul.subscribers { - float: none; - margin: 0; - padding: 0; - list-style-type: none; - overflow: auto; - } -ul.subscriptions li, ul.subscribers li { - display: block; - float: left; - padding: 0; - } -/* ----- End Subscriptions & Subscribers -----*/ - - - -#pagination { - margin: 18px auto; - width: 540px; - } -#nav_pagination { - margin: 0 0 36px 0; - padding: 0; - float: right; - list-style-type: none; - font-size: 12px; - font-weight: bold; - } -#nav_pagination li { - display: block; - float: left; - background-color: #91AA9D; - } -#nav_pagination li.before { - margin-right: 1px; - } -#nav_pagination li a { - padding: 6px 15px; - line-height: 21px; - background-color: #91AA9D; - color: #FCFFF5; - } -#nav_pagination li a:hover { - background-color: #3F606F; - color: #FCFFF5; - text-decoration: none; - } - -#footer { - clear: both; - margin: 0 auto; - padding: 0 0 36px 0; - width: 540px; - border-top: 1px solid #D8E2D7; - } -#footer p { - margin-top: 9px; - line-height: 12px; - } -#cc { - float: left; - margin: 3px 10px 0 0; - } - -/* ===== Begin Forms Styling ===== */ - -/* ----- Forms General Style ----- */ -form { - margin: 0 auto; - padding: 0; - } -form { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 12px; - } -form label { - display: block; - font-size: 12px; - font-weight: bold; - line-height: 18px; - } -form input { - border: 1px solid #D8E2D7; - width: 264px; - } -input#submit, input.submit { - display: block; - margin: 18px 0; - padding: 4px; - font-weight: bold; - color: #FCFFF5; - background-color: #C15D42; - cursor: pointer; - border: 0; - width: auto; - } -input#submit:hover, input.submit:hover { - background-color: #904632; - } -input.checkbox { - /*width: 14px; - height: 14px;*/ - width: auto; - border: 0; - } -textarea, input { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 12px; - color: #193441; - padding: 3px; - } -textarea:focus, input:focus { - background-color: #f0f6eb; - } -textarea { - width: 270px; - border: 1px solid #D8E2D7; - } -.input_instructions { - margin-top: 3px; - display: block; - font-size: 11px; - line-height: 15px; - color: #91aa9d; - font-family: Verdana, Arial, Helvetica, sans-serif; - } - -/* ----- Status Form ----- */ -#status_form { - height: 96px; - /*background-color: #F00;*/ - } -#status_form p { - margin: 36px 0 0 0; - padding: 0; - } -#status_label { - display: block; - clear: both; - margin: 0; - padding: 0 0 3px 0; - font-size: 18px; - font-weight: bold; - line-height: 24px; - color: #91AA9D; - } -#status_textarea { - display: block; - float: left; - width: 463px; - height: 35px; - padding: 5px; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 12px; - color: #193441; - border: 0; - } -#status_submit { - display: block; - float: left; - margin: 1px 0 0 4px; - width: 63px; - height: 45px; - background-color: #C15D42; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-weight: bold; - font-size: 14px; - color: #FCFFF5; - cursor: pointer; - border: 0; - } -#status_submit:hover { - background-color: #904632; - } -#counter { - position: absolute; - top: 140px; - left: -64px; - width: 50px; - font-weight: bold; - text-align: right; -} -/* ----- Subscribe Form ----- */ -#subscribe .submit, #unsubscribe .submit, #remotesubscribe .button, #remotesubscribe { - clear: left; - margin: 0; - width: 96px; - height: 27px; - font-family: verdana, arial, helvetica, sans-serif; - font-weight: bold; - font-size: 10px; - text-transform: uppercase; - background-color: #c15d42; - color: #fcfff5; - border: 0; - } -#remotesubscribe { - width: 96px; - height: 22px; - padding: 5px 0 0 0; - text-align: center; - } -#subscribe .button:hover, #unsubscribe .button:hover { - background-color: #904632; - cursor: pointer; - } - -a#remotesubscribe { - display: block; -} - -/* ----- Login Form -----*/ -input#license { - width: auto; - border: 0; - } -/* ----- Avatar Form -----*/ -form { - clear: left; -} - -/* ----- OpenID Form -----*/ - -input#openid_url { - background: url(login-bg.gif) no-repeat; - background-color: #fff; - background-position: 4px 50%; - color: #000; - padding-left: 24px; -} - -/* People lists (search results, maybe subscribers) */ - -#profiles { - clear: both; - margin: 0 auto; - padding: 0; - list-style-type: none; - width: 540px; - border-top: 1px solid #D8E2D7; - /*border: 1px solid #F00;*/ - } -#profiles a:hover { - text-decoration: underline; - } - -.profile_single { - clear: both; - display: block; - margin: 0; - padding: 5px 5px 5px 0; - min-height: 48px; - font-family: Georgia, "Times New Roman", Times, serif; - font-size: 13px; - line-height: 16px; - border-bottom: 1px solid #D8E2D7; - } -.profile_single:hover { - background-color: #F3F8EA; - } - -/* ----- IM Settings Form -----*/ - -#imsettings p { - margin: 0; - padding: 0; - line-height: 15px; -} - -/* ===== End Forms Styling ===== */ - -/* ===== Tag Cloud Styling ===== */ - -p.tagcloud { -text-align: center; -} - -p.tagcloud a { -line-height:100%; -vertical-align:middle; -} - -p.tagcloud a.largest { -font-size: 400%; -} -p.tagcloud a.verylarge { -font-size: 300%; -} - -p.tagcloud a.large { -font-size: 200%; -} - -p.tagcloud a.medium { -font-size: 150%; -} - -p.tagcloud a.small { -font-size: 100%; -} - -p.tagcloud a.verysmall { -font-size: 80%; -} - -p.tagcloud a.smallest { -font-size: 60%; -} diff --git a/theme/iphone/ie6.css.dist b/theme/iphone/ie6.css.dist deleted file mode 100644 index 97d9fee3f..000000000 --- a/theme/iphone/ie6.css.dist +++ /dev/null @@ -1,63 +0,0 @@ -@charset "UTF-8"; -/* CSS Document */ -body { - text-align: center; -} -input { - height: 24px; -} -#wrap { - margin: 0 auto; - padding: 0 20px; - width: 800px; - text-align: left; - background: url(bg-header.gif) repeat-x #FCFFF5; - } -#header { - position: relative; - margin-left: 108px; - } -#nav_views { - margin: 0; - } -#nav_views li { - line-height: 19px; - } -.statistics dd { - margin-top: -15px; - clear: both; - } -#notices { - margin: 0; - } -.notice_single { - height: 48px; - } -#profile p.notice_current { - height: 96px; - } - -#subscriptions_avatars li { - float: left; - margin: 0; - padding: 0; - } -img.avatar.original, img.avatar.profile { - clear: none; - float: left; -} -#status_textarea { - height: 46px; - } - -#nav_pagination li a { - padding: 6px 15px; - line-height: 27px; - } -#nav_sub { - position: relative; - margin-left: 108px; - } -#footer { - margin-left: 108px; -} diff --git a/theme/iphone/ie7.css.dist b/theme/iphone/ie7.css.dist deleted file mode 100644 index bbf52d5cf..000000000 --- a/theme/iphone/ie7.css.dist +++ /dev/null @@ -1,20 +0,0 @@ -@charset "UTF-8"; -/* CSS Document */ - -#statistics dd { - clear: both; - } - - -#subscriptions_avatars li { - float: left; - } -img.avatar.original, img.avatar.profile { - clear: none; - float: left; -} - -#nav_pagination li a { - padding: 6px 15px; - line-height: 27px; - }
\ No newline at end of file diff --git a/theme/iphone/login-bg.gif b/theme/iphone/login-bg.gif Binary files differdeleted file mode 100644 index e2d8377db..000000000 --- a/theme/iphone/login-bg.gif +++ /dev/null diff --git a/theme/iphone/logo.png b/theme/iphone/logo.png Binary files differdeleted file mode 100644 index 3b271814d..000000000 --- a/theme/iphone/logo.png +++ /dev/null |