diff --git a/actions/api.php b/actions/api.php deleted file mode 100644 index 1bc90de110..0000000000 --- a/actions/api.php +++ /dev/null @@ -1,306 +0,0 @@ -. - * - * @category Actions - * @package Actions - * @author Evan Prodromou - * @author Brenda Wallace - * @author Jeffery To - * @author Robin Millette - * @author Tom Adams - * @author Christopher Vollick - * @author CiaranG - * @author Craig Andrews - * @author Gina Haeussge - * @author Mike Cochrane - * @author Sarven Capadisli - * @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/home_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 0000000000..96179f175b --- /dev/null +++ b/actions/apiaccountratelimitstatus.php @@ -0,0 +1,112 @@ +. + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @author Robin Millette + * @author Zach Copley + * @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 + * @author Robin Millette + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..08b201dbff --- /dev/null +++ b/actions/apiaccountverifycredentials.php @@ -0,0 +1,85 @@ +. + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @author Robin Millette + * @author Zach Copley + * @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 + * @author Robin Millette + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..1cab2df5d0 --- /dev/null +++ b/actions/apiblockcreate.php @@ -0,0 +1,114 @@ +. + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @author Zach Copley + * @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 + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..16dbf94caf --- /dev/null +++ b/actions/apiblockdestroy.php @@ -0,0 +1,113 @@ +. + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @author Zach Copley + * @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 + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..a21fe86d20 --- /dev/null +++ b/actions/apidirectmessage.php @@ -0,0 +1,375 @@ +. + * + * @category API + * @package StatusNet + * @author Adrian Lang + * @author Evan Prodromou + * @author Robin Millette + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Robin Millette + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..fa6cafbe8d --- /dev/null +++ b/actions/apidirectmessagenew.php @@ -0,0 +1,188 @@ +. + * + * @category API + * @package StatusNet + * @author Adrian Lang + * @author Evan Prodromou + * @author Robin Millette + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Robin Millette + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..a80a6492e9 --- /dev/null +++ b/actions/apifavoritecreate.php @@ -0,0 +1,168 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiFavoriteCreateAction extends ApiAuthAction +{ + var $notice = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + $this->notice = Notice::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 0000000000..f131d1c7f2 --- /dev/null +++ b/actions/apifavoritedestroy.php @@ -0,0 +1,150 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiFavoriteDestroyAction extends ApiAuthAction +{ + + var $notice = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + $this->notice = Notice::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 0000000000..a824e734bf --- /dev/null +++ b/actions/apifriendshipscreate.php @@ -0,0 +1,137 @@ +. + * + * @category API + * @package StatusNet + * @author Dan Moore + * @author Evan Prodromou + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..3d9b7e001c --- /dev/null +++ b/actions/apifriendshipsdestroy.php @@ -0,0 +1,139 @@ +. + * + * @category API + * @package StatusNet + * @author Dan Moore + * @author Evan Prodromou + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..ae50c512c8 --- /dev/null +++ b/actions/apifriendshipsexists.php @@ -0,0 +1,128 @@ +. + * + * @category API + * @package StatusNet + * @author Dan Moore + * @author Evan Prodromou + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..8fc4367388 --- /dev/null +++ b/actions/apifriendshipsshow.php @@ -0,0 +1,168 @@ +. + * + * @category API + * @package StatusNet + * @author Dan Moore + * @author Evan Prodromou + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..cdb2afb5be --- /dev/null +++ b/actions/apigroupcreate.php @@ -0,0 +1,381 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..a8a40a6b3b --- /dev/null +++ b/actions/apigroupismember.php @@ -0,0 +1,122 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..071cd9290f --- /dev/null +++ b/actions/apigroupjoin.php @@ -0,0 +1,163 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..0d4bb9e4d3 --- /dev/null +++ b/actions/apigroupleave.php @@ -0,0 +1,149 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..c529c1e408 --- /dev/null +++ b/actions/apigrouplist.php @@ -0,0 +1,223 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..89469f36f4 --- /dev/null +++ b/actions/apigrouplistall.php @@ -0,0 +1,208 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..b31e47b398 --- /dev/null +++ b/actions/apigroupmembership.php @@ -0,0 +1,192 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..2bdb22bc41 --- /dev/null +++ b/actions/apigroupshow.php @@ -0,0 +1,152 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..e4ef55f2e3 --- /dev/null +++ b/actions/apihelptest.php @@ -0,0 +1,96 @@ +. + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @author Zach Copley + * @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 + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..8dc8793b53 --- /dev/null +++ b/actions/apistatusesdestroy.php @@ -0,0 +1,154 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author Tom Blankenship + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author Tom Blankenship + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..3be22ca597 --- /dev/null +++ b/actions/apistatusesshow.php @@ -0,0 +1,206 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author Tom Blankenship + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author Tom Blankenship + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..0d71e15128 --- /dev/null +++ b/actions/apistatusesupdate.php @@ -0,0 +1,241 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author Tom Blankenship + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author Tom Blankenship + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..ed1d151bfc --- /dev/null +++ b/actions/apistatusnetconfig.php @@ -0,0 +1,142 @@ +. + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @author Zach Copley + * @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 + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..e73ab983be --- /dev/null +++ b/actions/apistatusnetversion.php @@ -0,0 +1,102 @@ +. + * + * @category API + * @package StatusNet + * @author Evan Prodromou + * @author Zach Copley + * @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 + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..bc68dd192a --- /dev/null +++ b/actions/apisubscriptions.php @@ -0,0 +1,266 @@ +. + * + * @category API + * @package StatusNet + * @author Dan Moore + * @author Evan Prodromou + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..b8ae74f137 --- /dev/null +++ b/actions/apitimelinefavorites.php @@ -0,0 +1,237 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Zach Copley * @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 + * @author Evan Prodromou + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineFavoritesAction extends ApiBareAuthAction +{ + var $notices = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + 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 0000000000..1ea35866e2 --- /dev/null +++ b/actions/apitimelinefriends.php @@ -0,0 +1,247 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author mac65 + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author mac65 + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..5d05429185 --- /dev/null +++ b/actions/apitimelinegroup.php @@ -0,0 +1,231 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..fe5ff0f28f --- /dev/null +++ b/actions/apitimelinementions.php @@ -0,0 +1,233 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author mac65 + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author mac65 + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..58e2677348 --- /dev/null +++ b/actions/apitimelinepublic.php @@ -0,0 +1,213 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author mac65 + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author mac65 + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..a274daac00 --- /dev/null +++ b/actions/apitimelinetag.php @@ -0,0 +1,224 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..285735fd1a --- /dev/null +++ b/actions/apitimelineuser.php @@ -0,0 +1,248 @@ +. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Evan Prodromou + * @author Jeffery To + * @author mac65 + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Jeffery To + * @author mac65 + * @author Mike Cochrane + * @author Robin Millette + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..e8d92a773e --- /dev/null +++ b/actions/apiuserfollowers.php @@ -0,0 +1,89 @@ +. + * + * @category API + * @package StatusNet + * @author Dan Moore + * @author Evan Prodromou + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..741a26e588 --- /dev/null +++ b/actions/apiuserfriends.php @@ -0,0 +1,89 @@ +. + * + * @category API + * @package StatusNet + * @author Dan Moore + * @author Evan Prodromou + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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 0000000000..b3a939b431 --- /dev/null +++ b/actions/apiusershow.php @@ -0,0 +1,126 @@ +. + * + * @category API + * @package StatusNet + * @author Dan Moore + * @author Evan Prodromou + * @author mac65 + * @author Zach Copley + * @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 + * @author Evan Prodromou + * @author mac65 + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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/groupsearch.php b/actions/groupsearch.php index f0cca71561..55f4cee625 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 d5b0332f48..9ee031f936 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -240,6 +240,10 @@ class NewnoticeAction extends Action $this->maybeAddRedir($fileRecord->id, $fileurl); $short_fileurl = common_shorten_url($fileurl); + if (!$short_fileurl) { + // todo -- Consider forcing default shortener if none selected? + $short_fileurl = $fileurl; + } $content_shortened .= ' ' . $short_fileurl; if (Notice::contentTooLong($content_shortened)) { diff --git a/actions/twitapiaccount.php b/actions/twitapiaccount.php deleted file mode 100644 index 93c8443c9f..0000000000 --- a/actions/twitapiaccount.php +++ /dev/null @@ -1,127 +0,0 @@ -. - */ - -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 ed17946aef..0000000000 --- a/actions/twitapiblocks.php +++ /dev/null @@ -1,74 +0,0 @@ -. - */ - -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 08b8f4e9c1..0000000000 --- a/actions/twitapidirect_messages.php +++ /dev/null @@ -1,305 +0,0 @@ -. - */ - -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 f8943fe2dd..0000000000 --- a/actions/twitapifavorites.php +++ /dev/null @@ -1,216 +0,0 @@ -. - */ - -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 eea8945c39..0000000000 --- a/actions/twitapifriendships.php +++ /dev/null @@ -1,250 +0,0 @@ -. - */ - -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 493144e77e..0000000000 --- a/actions/twitapigroups.php +++ /dev/null @@ -1,453 +0,0 @@ -. - * - * @category Twitter - * @package StatusNet - * @author Craig Andrews - * @author Zach Copley - * @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 - * @author Zach Copley - * @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 join($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 false; - } - - if($this->auth_user->isMember($group)){ - $this->clientError(_('You are already a member of that group'), $code = 403); - return false; - } - - if (Group_block::isBlocked($group, $this->auth_user->getProfile())) { - $this->clientError(_('You have been blocked from that group by the admin.'), 403); - return false; - } - - $member = new Group_member(); - - $member->group_id = $group->id; - $member->profile_id = $this->auth_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->auth_user->nickname, $group->nickname)); - } - - 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 leave($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 false; - } - - if(! $this->auth_user->isMember($group)){ - $this->clientError(_('You are not a member of that group'), $code = 403); - return false; - } - - $member = new Group_member(); - - $member->group_id = $group->id; - $member->profile_id = $this->auth_user->id; - - if (!$member->find(true)) { - $this->serverError(_('Could not find membership record.')); - 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->auth_user->nickname, $group->nickname)); - } - - 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 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); - } - } - - function create($args, $apidata) - { - die("todo"); - } - - function update($args, $apidata) - { - die("todo"); - } - - function update_group_logo($args, $apidata) - { - die("todo"); - } - - function destroy($args, $apidata) - { - die("todo"); - } - - function tag($args, $apidata) - { - die("todo"); - } -} diff --git a/actions/twitapihelp.php b/actions/twitapihelp.php deleted file mode 100644 index 81381620e7..0000000000 --- a/actions/twitapihelp.php +++ /dev/null @@ -1,57 +0,0 @@ -. - */ - -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/twitapinotifications.php b/actions/twitapinotifications.php deleted file mode 100644 index 0653e69ab5..0000000000 --- a/actions/twitapinotifications.php +++ /dev/null @@ -1,40 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -require_once(INSTALLDIR.'/lib/twitterapi.php'); - -# This naming convention looks real sick -class TwitapinotificationsAction extends TwitterapiAction -{ - - function follow($args, $apidata) - { - parent::handle($args); - $this->serverError(_('API method under construction.'), $code=501); - } - - function leave($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 2f587d604f..7d618c471f 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 c628ee624a..c7fa741a06 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 * @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 87043b1821..0000000000 --- a/actions/twitapistatuses.php +++ /dev/null @@ -1,606 +0,0 @@ -. - */ - -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 home_timeline($args, $apidata) - { - call_user_func(array($this, 'friends_timeline'), $args, $apidata); - } - - 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); - - 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 490f11dce2..0000000000 --- a/actions/twitapistatusnet.php +++ /dev/null @@ -1,175 +0,0 @@ -. - * - * @category Twitter - * @package StatusNet - * @author Evan Prodromou - * @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 - * @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 0bcc55d378..0000000000 --- a/actions/twitapitags.php +++ /dev/null @@ -1,113 +0,0 @@ -. - * - * @category Twitter - * @package StatusNet - * @author Craig Andrews - * @author Zach Copley - * @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 - * @author Zach Copley - * @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 83ab28f35d..779405e6d6 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 703fa6754f..0000000000 --- a/actions/twitapiusers.php +++ /dev/null @@ -1,80 +0,0 @@ -. - */ - -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/lib/twitterapi.php b/lib/api.php similarity index 69% rename from lib/twitterapi.php rename to lib/api.php index 4a5de6ab3a..7a63a4a785 100644 --- a/lib/twitterapi.php +++ b/lib/api.php @@ -1,9 +1,12 @@ . + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Dan Moore + * @author Evan Prodromou + * @author Jeffery To + * @author Toby Inkster + * @author Zach Copley + * @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 +/** + * Contains most of the Twitter-compatible API output functions. + * + * @category API + * @package StatusNet + * @author Craig Andrews + * @author Dan Moore + * @author Evan Prodromou + * @author Jeffery To + * @author Toby Inkster + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiAction extends Action { - - var $auth_user; - + 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']); @@ -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'); + $this->initDocument('json'); + $status = $this->twitterStatusArray($notice); + $this->showJsonObjects($status); + $this->endDocument('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'); - } - 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'); + } + + 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->init_document('atom'); + $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,7 +979,7 @@ class TwitterapiAction extends Action return; } - function clientError($msg, $code = 400, $content_type = 'json') + function clientError($msg, $code = 400, $format = 'xml') { $action = $this->trimmed('action'); @@ -948,20 +993,23 @@ class TwitterapiAction extends Action header('HTTP/1.1 '.$code.' '.$status_string); - if ($content_type == 'xml') { - $this->init_document('xml'); + 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->end_document('xml'); - } else { - $this->init_document('json'); + $this->endDocument('xml'); + } elseif ($format == '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'); + } 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') @@ -979,21 +1027,21 @@ class TwitterapiAction extends Action 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')); @@ -1001,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! @@ -1018,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.')); @@ -1041,7 +1089,7 @@ class TwitterapiAction extends Action return; } - function get_user($id, $apidata=null) + function getTargetUser($id) { if (empty($id)) { @@ -1062,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)) { @@ -1073,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')) { @@ -1101,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 0000000000..2f2e44a264 --- /dev/null +++ b/lib/apiauth.php @@ -0,0 +1,203 @@ +. + * + * @category API + * @package StatusNet + * @author Adrian Lang + * @author Brenda Wallace + * @author Craig Andrews + * @author Dan Moore + * @author Evan Prodromou + * @author mEDI + * @author Sarven Capadisli + * @author Zach Copley + * @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 + * @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 0000000000..2d29c1ddd6 --- /dev/null +++ b/lib/apibareauth.php @@ -0,0 +1,109 @@ +. + * + * @category API + * @package StatusNet + * @author Adrian Lang + * @author Brenda Wallace + * @author Craig Andrews + * @author Dan Moore + * @author Evan Prodromou + * @author mEDI + * @author Sarven Capadisli + * @author Zach Copley + * @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 + * @author Brenda Wallace + * @author Craig Andrews + * @author Dan Moore + * @author Evan Prodromou + * @author mEDI + * @author Sarven Capadisli + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class 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/default.php b/lib/default.php index f9670cb7f9..329b041e9a 100644 --- a/lib/default.php +++ b/lib/default.php @@ -140,16 +140,15 @@ $default = 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), + array('enabled' => true, + 'consumer_key' => null, + 'consumer_secret' => null), 'memcached' => array('enabled' => false, 'server' => 'localhost', diff --git a/lib/router.php b/lib/router.php index 91f886bce1..b9a45d867f 100644 --- a/lib/router.php +++ b/lib/router.php @@ -270,22 +270,100 @@ class Router // statuses API - $m->connect('api/statuses/:method', - array('action' => 'api', - 'apiaction' => 'statuses'), - array('method' => '(public_timeline|home_timeline|friends_timeline|user_timeline|update|replies|mentions|show|friends|followers|featured)(\.(atom|rss|xml|json))?')); + $m->connect('api/statuses/public_timeline.:format', + array('action' => 'ApiTimelinePublic', + 'format' => '(xml|json|rss|atom)')); - $m->connect('api/statuses/:method/:argument', - array('action' => 'api', - 'apiaction' => 'statuses'), - array('method' => '(user_timeline|home_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)')); + $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', @@ -294,93 +372,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')); + $m->connect('api/friends/ids/:id.:format', + array('action' => 'apiFriends', + 'ids_only' => true)); - foreach (array('xml', 'json') as $e) { - $m->connect('api/friends/ids.'.$e, - array('action' => 'api', - 'apiaction' => 'statuses', - 'method' => 'friendsIDs.'.$e)); - } + $m->connect('api/followers/ids/:id.:format', + array('action' => 'apiFollowers', + 'ids_only' => true)); - $m->connect('api/followers/ids/:argument', - array('action' => 'api', - 'apiaction' => 'statuses', - 'method' => 'followersIDs')); + $m->connect('api/friends/ids.:format', + array('action' => 'apiFriends', + 'ids_only' => true)); - 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.: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')); + $m->connect('api/favorites/:id.:format', + array('action' => 'ApiTimelineFavorites', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xmljson|rss|atom)')); - 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/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 @@ -390,71 +474,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/timeline/:id.:format', + array('action' => 'ApiTimelineGroup', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xmljson|rss|atom)')); - $m->connect('api/statusnet/groups/:method', - array('action' => 'api', - 'apiaction' => 'statuses'), - array('method' => '(list_all|)(\.(atom|rss|xml|json))?')); + $m->connect('api/statusnet/groups/show.:format', + array('action' => 'ApiGroupShow', + 'format' => '(xml|json)')); - $m->connect('api/statuses/:method/:argument', - array('action' => 'api', - 'apiaction' => 'statuses'), - array('method' => '(user_timeline|home_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)')); + $m->connect('api/statusnet/groups/show/:id.:format', + array('action' => 'ApiGroupShow', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); - $m->connect('api/statusnet/groups/:method/:argument', - array('action' => 'api', - 'apiaction' => 'groups')); + $m->connect('api/statusnet/groups/join.:format', + array('action' => 'ApiGroupJoin', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); - $m->connect('api/statusnet/groups/:method', - array('action' => 'api', - 'apiaction' => 'groups')); + $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/searchaction.php b/lib/searchaction.php index 0d9f85a8f1..130b28ff52 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(_(<<elementStart('dl', array('id' => 'help_search', 'class' => 'help')); $this->element('dt', null, _('Search help')); $this->elementStart('dd', 'instructions'); diff --git a/lib/twitter.php b/lib/twitter.php index b49e2e1190..afc3f55bab 100644 --- a/lib/twitter.php +++ b/lib/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/lib/twitterbasicauthclient.php b/lib/twitterbasicauthclient.php index fd331fbdc9..1040d72fb6 100644 --- a/lib/twitterbasicauthclient.php +++ b/lib/twitterbasicauthclient.php @@ -36,8 +36,14 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { * * @category Integration * @package StatusNet - * @author Zach Copley - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @author Adrian Lang + * @author Brenda Wallace + * @author Craig Andrews + * @author Dan Moore + * @author Evan Prodromou + * @author mEDI + * @author Sarven Capadisli + * @author Zach Copley * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ * */ diff --git a/lib/util.php b/lib/util.php index d249b154fc..be10647fcb 100644 --- a/lib/util.php +++ b/lib/util.php @@ -998,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() diff --git a/plugins/FBConnect/FBCLoginGroupNav.php b/plugins/FBConnect/FBCLoginGroupNav.php index 6e12c20403..81b2520a4c 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','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')); + 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 29724d6bdf..ed02371e25 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 cb19b05461..ff74aade45 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,25 @@ 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. diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index 1819279686..0c7c1240c3 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -240,13 +240,13 @@ class RealtimePlugin extends Plugin // 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']); diff --git a/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php b/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php new file mode 100644 index 0000000000..4806538a04 --- /dev/null +++ b/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php @@ -0,0 +1,52 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @copyright 2009 Craig Andrews http://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/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +class RequireValidatedEmailPlugin extends Plugin +{ + function __construct() + { + parent::__construct(); + } + + function onStartNoticeSave($notice) + { + $user = User::staticGet('id', $notice->profile_id); + if (!empty($user)) { // it's a remote notice + if (empty($user->email)) { + throw new ClientException(_("You must validate your email address before posting.")); + } + } + return true; + } +} + diff --git a/theme/default/css/display.css b/theme/default/css/display.css index 86369cb993..3993da7177 100644 --- a/theme/default/css/display.css +++ b/theme/default/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:#C8D1D5; } .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; }