code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Authorize an OAuth request token
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  API
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @copyright 2010-2011 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);
}

/**
 * Authorize an OAuth request token
 *
 * @category API
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class ApiOAuthAuthorizeAction extends ApiOAuthAction
{
    var $oauthTokenParam;
    var $reqToken;
    var $callback;
    var $app;
    var $nickname;
    var $password;
    var $store;

    /**
     * Is this a read-only action?
     *
     * @return boolean false
     */
    function isReadOnly($args)
    {
        return false;
    }

    function prepare(array $args = array())
    {
        parent::prepare($args);

        $this->nickname        = $this->trimmed('nickname');
        $this->password        = $this->arg('password');
        $this->oauthTokenParam = $this->arg('oauth_token');
        $this->mode            = $this->arg('mode');
        $this->store           = new ApiGNUsocialOAuthDataStore();

        try {
            $this->app = $this->store->getAppByRequestToken($this->oauthTokenParam);
        } catch (Exception $e) {
            $this->clientError($e->getMessage());
        }

        return true;
    }

    /**
     * Handle input, produce output
     *
     * Switches on request method; either shows the form or handles its input.
     *
     * @param array $args $_REQUEST data
     *
     * @return void
     */
    function handle()
    {
        parent::handle();

        if ($_SERVER['REQUEST_METHOD'] == 'POST') {

            $this->handlePost();

        } else {

            // Make sure a oauth_token parameter was provided
            if (empty($this->oauthTokenParam)) {
                // TRANS: Client error given when no oauth_token was passed to the OAuth API.
                $this->clientError(_('No oauth_token parameter provided.'));
            } else {

                // Check to make sure the token exists
                $this->reqToken = $this->store->getTokenByKey($this->oauthTokenParam);

                if (empty($this->reqToken)) {
                    // TRANS: Client error given when an invalid request token was passed to the OAuth API.
                    $this->clientError(_('Invalid request token.'));
                } else {

                    // Check to make sure we haven't already authorized the token
                    if ($this->reqToken->state != 0) {
                        // TRANS: Client error given when an invalid request token was passed to the OAuth API.
                        $this->clientError(_('Request token already authorized.'));
                    }
                }
            }

            // make sure there's an app associated with this token
            if (empty($this->app)) {
                // TRANS: Client error given when an invalid request token was passed to the OAuth API.
                $this->clientError(_('Invalid request token.'));
            }

            $name = $this->app->name;

            $this->showForm();
        }
    }

    function handlePost()
    {
        // check session token for CSRF protection.

        $token = $this->trimmed('token');

        if (!$token || $token != common_session_token()) {
            $this->showForm(
                // TRANS: Form validation error in API OAuth authorisation because of an invalid session token.
                _('There was a problem with your session token. Try again, please.'));
            return;
        }

        // check creds

        $user = null;

        if (!common_logged_in()) {

            // XXX Force credentials check?

            // @fixme this should probably use a unified login form handler
            $user = null;
            if (Event::handle('StartOAuthLoginCheck', array($this, &$user))) {
                $user = common_check_user($this->nickname, $this->password);
            }
            Event::handle('EndOAuthLoginCheck', array($this, &$user));

            if (empty($user)) {
                // TRANS: Form validation error given when an invalid username and/or password was passed to the OAuth API.
                $this->showForm(_("Invalid nickname / password!"));
                return;
            }
        } else {
            $user = common_current_user();
        }

        // fetch the token
        $this->reqToken = $this->store->getTokenByKey($this->oauthTokenParam);
        assert(!empty($this->reqToken));

        if ($this->arg('allow')) {
            // mark the req token as authorized
            try {
                $this->store->authorize_token($this->oauthTokenParam);
            } catch (Exception $e) {
                $this->serverError($e->getMessage());
            }

            common_log(
                LOG_INFO,
                sprintf(
                    "API OAuth - User %d (%s) has authorized request token %s for OAuth application %d (%s).",
                    $user->id,
                    $user->nickname,
                    $this->reqToken->tok,
                    $this->app->id,
                    $this->app->name
                )
            );

            $tokenAssoc = new Oauth_token_association();

            $tokenAssoc->profile_id     = $user->id;
            $tokenAssoc->application_id = $this->app->id;
            $tokenAssoc->token          = $this->oauthTokenParam;
            $tokenAssoc->created        = common_sql_now();

            $result = $tokenAssoc->insert();

            if (!$result) {
                common_log_db_error($tokenAssoc, 'INSERT', __FILE__);
                // TRANS: Server error displayed when a database action fails.
                $this->serverError(_('Database error inserting oauth_token_association.'));
            }

            $callback = $this->getCallback();

            if (!empty($callback) && $this->reqToken->verified_callback != 'oob') {
                $targetUrl = $this->buildCallbackUrl(
                    $callback,
                    array(
                        'oauth_token'    => $this->oauthTokenParam,
                        'oauth_verifier' => $this->reqToken->verifier // 1.0a
                    )
                );

                common_log(LOG_INFO, "Redirecting to callback: $targetUrl");

                // Redirect the user to the provided OAuth callback
                common_redirect($targetUrl, 303);

            } elseif ($this->app->type == 2) {
                // Strangely, a web application seems to want to do the OOB
                // workflow. Because no callback was specified anywhere.
                common_log(
                    LOG_WARNING,
                    sprintf(
                        "API OAuth - No callback provided for OAuth web client ID %s (%s) "
                         . "during authorization step. Falling back to OOB workflow.",
                        $this->app->id,
                        $this->app->name
                    )
                );
            }

            // Otherwise, inform the user that the rt was authorized
            $this->showAuthorized();
        } else if ($this->arg('cancel')) {
            common_log(
                LOG_INFO,
                sprintf(
                    "API OAuth - User %d (%s) refused to authorize request token %s for OAuth application %d (%s).",
                    $user->id,
                    $user->nickname,
                    $this->reqToken->tok,
                    $this->app->id,
                    $this->app->name
                )
            );

            try {
                $this->store->revoke_token($this->oauthTokenParam, 0);
            } catch (Exception $e) {
                $this->ServerError($e->getMessage());
            }

            $callback = $this->getCallback();

            // If there's a callback available, inform the consumer the user
            // has refused authorization
            if (!empty($callback) && $this->reqToken->verified_callback != 'oob') {
                $targetUrl = $this->buildCallbackUrl(
                    $callback,
                    array(
                        'oauth_problem' => 'user_refused',
                    )
                );

                common_log(LOG_INFO, "Redirecting to callback: $targetUrl");

                // Redirect the user to the provided OAuth callback
                common_redirect($targetUrl, 303);
            }

            // otherwise inform the user that authorization for the rt was declined
            $this->showCanceled();

        } else {
            // TRANS: Client error given on when invalid data was passed through a form in the OAuth API.
            $this->clientError(_('Unexpected form submission.'));
        }
    }

    /**
     * Show body - override to add a special CSS class for the authorize
     * page's "desktop mode" (minimal display)
     *
     * Calls template methods
     *
     * @return nothing
     */
    function showBody()
    {
        $bodyClasses = array();

        if ($this->desktopMode()) {
            $bodyClasses[] = 'oauth-desktop-mode';
        }

        if (common_current_user()) {
            $bodyClasses[] = 'user_in';
        }

        $attrs = array('id' => strtolower($this->trimmed('action')));

        if (!empty($bodyClasses)) {
            $attrs['class'] = implode(' ', $bodyClasses);
        }

        $this->elementStart('body', $attrs);

        $this->elementStart('div', array('id' => 'wrap'));
        if (Event::handle('StartShowHeader', array($this))) {
            $this->showHeader();
            Event::handle('EndShowHeader', array($this));
        }
        $this->showCore();
        if (Event::handle('StartShowFooter', array($this))) {
            $this->showFooter();
            Event::handle('EndShowFooter', array($this));
        }
        $this->elementEnd('div');
        $this->showScripts();
        $this->elementEnd('body');
    }

    function showForm($error=null)
    {
        $this->error = $error;
        $this->showPage();
    }

    function showScripts()
    {
        parent::showScripts();
        if (!common_logged_in()) {
            $this->autofocus('nickname');
        }
    }

    /**
     * Title of the page
     *
     * @return string title of the page
     */
    function title()
    {
        // TRANS: Title for a page where a user can confirm/deny account access by an external application.
        return _('An application would like to connect to your account');
    }

    /**
     * Shows the authorization form.
     *
     * @return void
     */
    function showContent()
    {
        $this->elementStart('form', array('method' => 'post',
                                          'id' => 'form_apioauthauthorize',
                                          'class' => 'form_settings',
                                          'action' => common_local_url('ApiOAuthAuthorize')));
        $this->elementStart('fieldset');
        $this->element('legend', array('id' => 'apioauthauthorize_allowdeny'),
                                 // TRANS: Fieldset legend.
                                 _('Allow or deny access'));

        $this->hidden('token', common_session_token());
	$this->hidden('mode', $this->mode);
        $this->hidden('oauth_token', $this->oauthTokenParam);
        $this->hidden('oauth_callback', $this->callback);

        $this->elementStart('ul', 'form_data');
        $this->elementStart('li');
        $this->elementStart('p');
        if (!empty($this->app->icon) && $this->app->name != 'anonymous') {
            $this->element('img', array('src' => $this->app->icon));
        }

        $access = ($this->app->access_type & Oauth_application::$writeAccess) ?
          'access and update' : 'access';

        if ($this->app->name == 'anonymous') {
            // Special message for the anonymous app and consumer.
            // TRANS: User notification of external application requesting account access.
            // TRANS: %3$s is the access type requested (read-write or read-only), %4$s is the StatusNet sitename.
            $msg = _('An application would like the ability ' .
                 'to %3$s your %4$s account data. ' .
                 'You should only give access to your %4$s account ' .
                 'to third parties you trust.');
        } else {
            // TRANS: User notification of external application requesting account access.
            // TRANS: %1$s is the application name requesting access, %2$s is the organisation behind the application,
            // TRANS: %3$s is the access type requested, %4$s is the StatusNet sitename.
            $msg = _('The application %1$s by ' .
                     '%2$s would like the ability ' .
                     'to %3$s your %4$s account data. ' .
                     'You should only give access to your %4$s account ' .
                     'to third parties you trust.');
        }

        $this->raw(sprintf($msg,
                           $this->app->name,
                           $this->app->organization,
                           $access,
                           common_config('site', 'name')));
        $this->elementEnd('p');
        $this->elementEnd('li');
        $this->elementEnd('ul');

        // quickie hack
        $button = false;
        if (!common_logged_in()) {
            if (Event::handle('StartOAuthLoginForm', array($this, &$button))) {
                $this->elementStart('fieldset');
                // TRANS: Fieldset legend.
                $this->element('legend', null, _m('LEGEND','Account'));
                $this->elementStart('ul', 'form_data');
                $this->elementStart('li');
                // TRANS: Field label on OAuth API authorisation form.
                $this->input('nickname', _('Nickname'));
                $this->elementEnd('li');
                $this->elementStart('li');
                // TRANS: Field label on OAuth API authorisation form.
                $this->password('password', _('Password'));
                $this->elementEnd('li');
                $this->elementEnd('ul');

                $this->elementEnd('fieldset');
            }
            Event::handle('EndOAuthLoginForm', array($this, &$button));
        }

        $this->element('input', array('id' => 'cancel_submit',
                                      'class' => 'submit submit form_action-primary',
                                      'name' => 'cancel',
                                      'type' => 'submit',
                                      // TRANS: Button text that when clicked will cancel the process of allowing access to an account
                                      // TRANS: by an external application.
                                      'value' => _m('BUTTON','Cancel')));

        $this->element('input', array('id' => 'allow_submit',
                                      'class' => 'submit submit form_action-secondary',
                                      'name' => 'allow',
                                      'type' => 'submit',
                                      // TRANS: Button text that when clicked will allow access to an account by an external application.
                                      'value' => $button ? $button : _m('BUTTON','Allow')));

        $this->elementEnd('fieldset');
        $this->elementEnd('form');
    }

    /**
     * Instructions for using the form
     *
     * For "remembered" logins, we make the user re-login when they
     * try to change settings. Different instructions for this case.
     *
     * @return void
     */
    function getInstructions()
    {
        // TRANS: Form instructions.
        return _('Authorize access to your account information.');
    }

    /**
     * A local menu
     *
     * Shows different login/register actions.
     *
     * @return void
     */
    function showLocalNav()
    {
        // NOP
    }

    /*
     * Checks to see if a the "mode" parameter is present in the request
     * and set to "desktop". If it is, the page is meant to be displayed in
     * a small frame of another application, and we should  suppress the
     * header, aside, and footer.
     */
    function desktopMode()
    {
        if (isset($this->mode) && $this->mode == 'desktop') {
            return true;
        } else {
            return false;
        }
    }

    /*
     * Override - suppress output in "desktop" mode
     */
    function showHeader()
    {
        if ($this->desktopMode() == false) {
            parent::showHeader();
        }
    }

    /*
     * Override - suppress output in "desktop" mode
     */
    function showAside()
    {
        if ($this->desktopMode() == false) {
            parent::showAside();
        }
    }

    /*
     * Override - suppress output in "desktop" mode
     */
    function showFooter()
    {
        if ($this->desktopMode() == false) {
            parent::showFooter();
        }
    }

    /**
     * Show site notice.
     *
     * @return nothing
     */
    function showSiteNotice()
    {
        // NOP
    }

    /**
     * Show notice form.
     *
     * Show the form for posting a new notice
     *
     * @return nothing
     */
    function showNoticeForm()
    {
        // NOP
    }

    /*
     * Show a nice message confirming the authorization
     * operation was canceled.
     *
     * @return nothing
     */
    function showCanceled()
    {
        $info = new InfoAction(
            // TRANS: Header for user notification after revoking OAuth access to an application.
            _('Authorization canceled.'),
            sprintf(
                // TRANS: User notification after revoking OAuth access to an application.
                // TRANS: %s is an OAuth token.
                _('The request token %s has been revoked.'),
                $this->oauthTokenParam
            )
        );

        $info->showPage();
    }

    /*
     * Show a nice message that the authorization was successful.
     * If the operation is out-of-band, show a pin.
     *
     * @return nothing
     */
    function showAuthorized()
    {
        $title = null;
        $msg   = null;

        if ($this->app->name == 'anonymous') {

            $title =
                // TRANS: Title of the page notifying the user that an anonymous client application was successfully authorized to access the user's account with OAuth.
                _('You have successfully authorized the application');

            $msg =
                // TRANS: Message notifying the user that an anonymous client application was successfully authorized to access the user's account with OAuth.
                _('Please return to the application and enter the following security code to complete the process.');

        } else {

            $title = sprintf(
                // TRANS: Title of the page notifying the user that the client application was successfully authorized to access the user's account with OAuth.
                // TRANS: %s is the authorised application name.
                _('You have successfully authorized %s'),
                $this->app->name
            );

            $msg = sprintf(
                // TRANS: Message notifying the user that the client application was successfully authorized to access the user's account with OAuth.
                // TRANS: %s is the authorised application name.
                _('Please return to %s and enter the following security code to complete the process.'),
                $this->app->name
            );

        }

        if ($this->reqToken->verified_callback == 'oob') {
            $pin = new ApiOAuthPinAction(
                $title,
                $msg,
                $this->reqToken->verifier,
                $this->desktopMode()
            );
            $pin->showPage();
        } else {
            // NOTE: This would only happen if an application registered as
            // a web application but sent in 'oob' for the oauth_callback
            // parameter. Usually web apps will send in a callback and
            // not use the pin-based workflow.

            $info = new InfoAction(
                $title,
                $msg,
                $this->oauthTokenParam,
                $this->reqToken->verifier
            );

            $info->showPage();
        }
    }

    /*
     * Figure out what the callback should be
     */
    function getCallback()
    {
        $callback = null;

        // Return the verified callback if we have one
        if ($this->reqToken->verified_callback != 'oob') {

            $callback = $this->reqToken->verified_callback;

            // Otherwise return the callback that was provided when
            // registering the app
            if (empty($callback)) {

                common_debug(
                    "No verified callback found for request token, using application callback: "
                        . $this->app->callback_url,
                     __FILE__
                );

                $callback = $this->app->callback_url;
            }
        }

        return $callback;
    }

    /*
     * Properly format the callback URL and parameters so it's
     * suitable for a redirect in the OAuth dance
     *
     * @param string $url       the URL
     * @param array  $params    an array of parameters
     *
     * @return string $url  a URL to use for redirecting to
     */
    function buildCallbackUrl($url, $params)
    {
        foreach ($params as $k => $v) {
            $url = $this->appendQueryVar(
                $url,
                OAuthUtil::urlencode_rfc3986($k),
                OAuthUtil::urlencode_rfc3986($v)
            );
        }

        return $url;
    }

    /*
     * Append a new query parameter after any existing query
     * parameters.
     *
     * @param string $url   the URL
     * @prarm string $k     the parameter name
     * @param string $v     value of the paramter
     *
     * @return string $url  the new URL with added parameter
     */
    function appendQueryVar($url, $k, $v) {
        $url = preg_replace('/(.*)(\?|&)' . $k . '=[^&]+?(&)(.*)/i', '$1$2$4', $url . '&');
        $url = substr($url, 0, -1);
        if (strpos($url, '?') === false) {
            return ($url . '?' . $k . '=' . $v);
        } else {
            return ($url . '&' . $k . '=' . $v);
        }
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Action for displaying an OAuth verifier pin
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  Action
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @copyright 2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET') && !defined('LACONICA')) {
    exit(1);
}

/**
 * Class for displaying an OAuth verifier pin
 *
 * XXX: I'm pretty sure we don't need to check the logged in state here. -- Zach
 *
 * @category Action
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class ApiOAuthPinAction extends InfoAction
{
    function __construct($title, $message, $verifier, $desktopMode = false)
    {
        $this->verifier    = $verifier;
        $this->title       = $title;
        $this->desktopMode = $desktopMode;
        parent::__construct($title, $message);
    }

    /**
     * Show body - override to add a special CSS class for the pin pages's
     * "desktop mode" (minimal display)
     *
     * Calls template methods
     *
     * @return nothing
     */
    function showBody()
    {
        $bodyClasses = array();

        if ($this->desktopMode) {
            $bodyClasses[] = 'oauth-desktop-mode';
        }

        if (common_current_user()) {
            $bodyClasses[] = 'user_in';
        }

        $attrs = array('id' => strtolower($this->trimmed('action')));

        if (!empty($bodyClasses)) {
            $attrs['class'] = implode(' ', $bodyClasses);
        }

        $this->elementStart('body', $attrs);

        $this->elementStart('div', array('id' => 'wrap'));
        if (Event::handle('StartShowHeader', array($this))) {
            $this->showHeader();
            Event::handle('EndShowHeader', array($this));
        }
        $this->showCore();
        if (Event::handle('StartShowFooter', array($this))) {
            $this->showFooter();
            Event::handle('EndShowFooter', array($this));
        }
        $this->elementEnd('div');
        $this->showScripts();
        $this->elementEnd('body');
    }

    /**
     * A local menu
     *
     * Shows different login/register actions.
     *
     * @return void
     */
    function showLocalNav()
    {
        // NOP
    }

    /*
     * Override - suppress output in "desktop" mode
     */
    function showHeader()
    {
        if ($this->desktopMode == false) {
            parent::showHeader();
        }
    }

    /*
     * Override - suppress output in "desktop" mode
     */
    function showAside()
    {
        if ($this->desktopMode == false) {
            parent::showAside();
        }
    }

    /*
     * Override - suppress output in "desktop" mode
     */
    function showFooter()
    {
        if ($this->desktopMode == false) {
            parent::showFooter();
        }
    }

    /**
     * Show site notice.
     *
     * @return nothing
     */
    function showSiteNotice()
    {
        // NOP
    }

    /**
     * Show notice form.
     *
     * Show the form for posting a new notice
     *
     * @return nothing
     */
    function showNoticeForm()
    {
        // NOP
    }

    /**
     * Display content.
     *
     * @return nothing
     */
    function showContent()
    {
        $this->element('div', array('class' => 'info'), $this->message);
        $this->element('div', array('id' => 'oauth_pin'), $this->verifier);
    }
}

repo_name: andremichelle/gnu

license: mit


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Issue temporary OAuth credentials (a request token)
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  API
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @copyright 2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    exit(1);
}

/**
 * Issue temporary OAuth credentials (a request token)
 *
 * @category API
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class ApiOAuthRequestTokenAction extends ApiOAuthAction
{
    /**
     * Take arguments for running
     *
     * @param array $args $_REQUEST args
     *
     * @return boolean success flag
     */
    function prepare(array $args = array())
    {
        parent::prepare($args);

        // XXX: support "force_login" parameter like Twitter? (Forces the user to enter
        // their credentials to ensure the correct users account is authorized.)

        return true;
    }

    /**
     * Handle a request for temporary OAuth credentials
     *
     * Make sure the request is kosher, then emit a set of temporary
     * credentials -- AKA an unauthorized request token.
     *
     * @param array $args array of arguments
     *
     * @return void
     */
    function handle()
    {
        parent::handle();

        $datastore   = new ApiGNUsocialOAuthDataStore();
        $server      = new OAuthServer($datastore);
        $hmac_method = new OAuthSignatureMethod_HMAC_SHA1();

        $server->add_signature_method($hmac_method);

        try {

            $req = OAuthRequest::from_request();

            // verify callback
            if (!$this->verifyCallback($req->get_parameter('oauth_callback'))) {
                throw new OAuthException(
                    "You must provide a valid URL or 'oob' in oauth_callback.",
                    400
                );
            }

            // check signature and issue a new request token
            $token = $server->fetch_request_token($req);

            common_log(
                LOG_INFO,
                sprintf(
                    "API OAuth - Issued request token %s for consumer %s with oauth_callback %s",
                    $token->key,
                    $req->get_parameter('oauth_consumer_key'),
                    "'" . $req->get_parameter('oauth_callback') ."'"
                )
            );

            // return token to the client
            $this->showRequestToken($token);

        } catch (OAuthException $e) {
            common_log(LOG_WARNING, 'API OAuthException - ' . $e->getMessage());

            // Return 401 for for bad credentials or signature problems,
            // and 400 for missing or unsupported parameters

            $code = $e->getCode();
            $this->clientError($e->getMessage(), empty($code) ? 401 : $code, 'text');
        }
    }

    /*
     * Display temporary OAuth credentials
     */
    function showRequestToken($token)
    {
        header('Content-Type: application/x-www-form-urlencoded');
        print $token;
        print '&oauth_callback_confirmed=true';
    }

    /* Make sure the callback parameter contains either a real URL
     * or the string 'oob'.
     *
     * @todo Check for evil/banned URLs here
     *
     * @return boolean true or false
     */
    function verifyCallback($callback)
    {
        if ($callback == "oob") {
            common_debug("OAuth request token requested for out of band client.");

            // XXX: Should we throw an error if a client is registered as a
            // web application but requests the pin based workflow? For now I'm
            // allowing the workflow to proceed and issuing a pin. --Zach

            return true;
        } else {
            return filter_var($callback, FILTER_VALIDATE_URL);
        }
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Action for showing Twitter-like Atom search results
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  Search
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @copyright 2008-2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET') && !defined('LACONICA')) {
    exit(1);
}

/**
 * Action for outputting search results in Twitter compatible Atom
 * format.
 *
 * TODO: abstract Atom stuff into a ruseable base class like
 * RSS10Action.
 *
 * @category Search
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 *
 * @see      ApiPrivateAuthAction
 */
class ApiSearchAtomAction extends ApiPrivateAuthAction
{
    var $cnt;
    var $query;
    var $lang;
    var $rpp;
    var $page;
    var $since_id;
    var $geocode;

    /**
     * Constructor
     *
     * Just wraps the Action constructor.
     *
     * @param string  $output URI to output to, default = stdout
     * @param boolean $indent Whether to indent output, default true
     *
     * @see Action::__construct
     */
    function __construct($output='php://output', $indent=null)
    {
        parent::__construct($output, $indent);
    }

    /**
     * Do we need to write to the database?
     *
     * @return boolean true
     */
    function isReadonly()
    {
        return true;
    }

    /**
     * Read arguments and initialize members
     *
     * @param array $args Arguments from $_REQUEST
     *
     * @return boolean success
     */
    function prepare(array $args = array())
    {
        parent::prepare($args);

        $this->query = $this->trimmed('q');
        $this->lang  = $this->trimmed('lang');
        $this->rpp   = $this->trimmed('rpp');

        if (!$this->rpp) {
            $this->rpp = 15;
        }

        if ($this->rpp > 100) {
            $this->rpp = 100;
        }

        $this->page = $this->trimmed('page');

        if (!$this->page) {
            $this->page = 1;
        }

        // TODO: Suppport max_id -- we need to tweak the backend
        // Search classes to support it.

        $this->since_id = $this->trimmed('since_id');
        $this->geocode  = $this->trimmed('geocode');

        // TODO: Also, language and geocode

        return true;
    }

    /**
     * Handle a request
     *
     * @param array $args Arguments from $_REQUEST
     *
     * @return void
     */
    function handle()
    {
        parent::handle();
        common_debug("In apisearchatom handle()");
        $this->showAtom();
    }

    /**
     * Get the notices to output as results. This also sets some class
     * attrs so we can use them to calculate pagination, and output
     * since_id and max_id.
     *
     * @return array an array of Notice objects sorted in reverse chron
     */
    function getNotices()
    {
        // TODO: Support search operators like from: and to:, boolean, etc.

        $notices = array();
        $notice = new Notice();

        // lcase it for comparison
        $q = strtolower($this->query);

        $search_engine = $notice->getSearchEngine('notice');
        $search_engine->set_sort_mode('chron');
        $search_engine->limit(($this->page - 1) * $this->rpp,
            $this->rpp + 1, true);
        if (false === $search_engine->query($q)) {
            $this->cnt = 0;
        } else {
            $this->cnt = $notice->find();
        }

        $cnt = 0;
        $this->max_id = 0;

        if ($this->cnt > 0) {
            while ($notice->fetch()) {
                ++$cnt;

                if (!$this->max_id) {
                    $this->max_id = $notice->id;
                }

                if ($this->since_id && $notice->id <= $this->since_id) {
                    break;
                }

                if ($cnt > $this->rpp) {
                    break;
                }

                $notices[] = clone($notice);
            }
        }

        return $notices;
    }

    /**
     * Output search results as an Atom feed
     *
     * @return void
     */
    function showAtom()
    {
        $notices = $this->getNotices();

        $this->initAtom();
        $this->showFeed();

        foreach ($notices as $n) {
            $profile = $n->getProfile();

            // Don't show notices from deleted users

            if (!empty($profile)) {
                $this->showEntry($n);
            }
        }

        $this->endAtom();
    }

    /**
     * Show feed specific Atom elements
     *
     * @return void
     */
    function showFeed()
    {
        // TODO: A9 OpenSearch stuff like search.twitter.com?

        $server   = common_config('site', 'server');
        $sitename = common_config('site', 'name');

        // XXX: Use xmlns:statusnet instead?

        $this->elementStart('feed',
            array('xmlns' => 'http://www.w3.org/2005/Atom',

                             // XXX: xmlns:twitter causes Atom validation to fail
                             // It's used for the source attr on notices

                             'xmlns:twitter' => 'http://api.twitter.com/',
                             'xml:lang' => 'en-US')); // XXX Other locales ?

        $taguribase = TagURI::base();
        $this->element('id', null, "tag:$taguribase:search/$server");

        $site_uri = common_path(false);

        $search_uri = $site_uri . 'api/search.atom?q=' . urlencode($this->query);

        if ($this->rpp != 15) {
            $search_uri .= '&rpp=' . $this->rpp;
        }

        // FIXME: this alternate link is not quite right because our
        // web-based notice search doesn't support a rpp (responses per
        // page) param yet

        $this->element('link', array('type' => 'text/html',
                                     'rel'  => 'alternate',
                                     'href' => $site_uri . 'search/notice?q=' .
                                        urlencode($this->query)));

        // self link

        $self_uri = $search_uri;
        $self_uri .= ($this->page > 1) ? '&page=' . $this->page : '';

        $this->element('link', array('type' => 'application/atom+xml',
                                     'rel'  => 'self',
                                     'href' => $self_uri));

        // @todo Needs i18n?
        $this->element('title', null, "$this->query - $sitename Search");
        $this->element('updated', null, common_date_iso8601('now'));

        // XXX: The below "rel" links are not valid Atom, but it's what
        // Twitter does...

        // refresh link

        $refresh_uri = $search_uri . "&since_id=" . $this->max_id;

        $this->element('link', array('type' => 'application/atom+xml',
                                     'rel'  => 'refresh',
                                     'href' => $refresh_uri));

        // pagination links

        if ($this->cnt > $this->rpp) {

            $next_uri = $search_uri . "&max_id=" . $this->max_id .
                '&page=' . ($this->page + 1);

            $this->element('link', array('type' => 'application/atom+xml',
                                         'rel'  => 'next',
                                         'href' => $next_uri));
        }

        if ($this->page > 1) {

            $previous_uri = $search_uri . "&max_id=" . $this->max_id .
                '&page=' . ($this->page - 1);

            $this->element('link', array('type' => 'application/atom+xml',
                                         'rel'  => 'previous',
                                         'href' => $previous_uri));
        }
    }

    /**
     * Build an Atom entry similar to search.twitter.com's based on
     * a given notice
     *
     * @param Notice $notice the notice to use
     *
     * @return void
     */
    function showEntry($notice)
    {
        $server  = common_config('site', 'server');
        $profile = $notice->getProfile();
        $nurl    = common_local_url('shownotice', array('notice' => $notice->id));

        $this->elementStart('entry');

        $taguribase = TagURI::base();

        $this->element('id', null, "tag:$taguribase:$notice->id");
        $this->element('published', null, common_date_w3dtf($notice->created));
        $this->element('link', array('type' => 'text/html',
                                     'rel'  => 'alternate',
                                     'href' => $nurl));
        $this->element('title', null, common_xml_safe_str(trim($notice->content)));
        $this->element('content', array('type' => 'html'), $notice->getRendered());
        $this->element('updated', null, common_date_w3dtf($notice->created));
        $this->element('link', array('type' => 'image/png',
                                     // XXX: Twitter uses rel="image" (not valid)
                                     'rel' => 'related',
                                     'href' => $profile->avatarUrl()));

        // @todo: Here is where we'd put in a link to an atom feed for threads

        $source = null;
        $source_link = null;

        $ns = $notice->getSource();
        if ($ns instanceof Notice_source) {
            $source = $ns->code;
            if (!empty($ns->url)) {
                $source_link = $ns->url;
                if (!empty($ns->name)) {
                    $source = $ns->name;
                }
            }
        }

        $this->element("twitter:source", null, $source);
        $this->element("twitter:source_link", null, $source_link);

        $this->elementStart('author');

        $name = $profile->nickname;

        if ($profile->fullname) {
            // @todo Needs proper i18n?
            $name .= ' (' . $profile->fullname . ')';
        }

        $this->element('name', null, $name);
        $this->element('uri', null, common_profile_uri($profile));
        $this->elementEnd('author');

        $this->elementEnd('entry');
    }

    /**
     * Initialize the Atom output, send headers
     *
     * @return void
     */
    function initAtom()
    {
        header('Content-Type: application/atom+xml; charset=utf-8');
        $this->startXml();
    }

    /**
     * End the Atom feed
     *
     * @return void
     */
    function endAtom()
    {
        $this->elementEnd('feed');
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Action for showing Twitter-like JSON search results
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  Search
 * @package   GNUsocial
 * @author    Zach Copley <zach@status.net>
 * @copyright 2008-2010 StatusNet, Inc.
 * @copyright 2013 Free Software Foundation, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://www.gnu.org/software/social/
 */

if (!defined('GNUSOCIAL')) { exit(1); }

/**
 * Action handler for Twitter-compatible API search
 *
 * @category Search
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 * @see      ApiAction
 */
class ApiSearchJSONAction extends ApiPrivateAuthAction
{
    var $query;
    var $lang;
    var $rpp;
    var $page;
    var $since_id;
    var $limit;
    var $geocode;

    /**
     * Initialization.
     *
     * @param array $args Web and URL arguments
     *
     * @return boolean true if nothing goes wrong
     */
    function prepare(array $args = array())
    {
        parent::prepare($args);

        $this->query = $this->trimmed('q');
        $this->lang  = $this->trimmed('lang');
        $this->rpp   = $this->trimmed('rpp');

        if (!$this->rpp) {
            $this->rpp = 15;
        }

        if ($this->rpp > 100) {
            $this->rpp = 100;
        }

        $this->page = $this->trimmed('page');

        if (!$this->page) {
            $this->page = 1;
        }

        // TODO: Suppport max_id -- we need to tweak the backend
        // Search classes to support it.

        $this->since_id = $this->trimmed('since_id');
        $this->geocode  = $this->trimmed('geocode');

        return true;
    }

    /**
     * Handle a request
     *
     * @param array $args Arguments from $_REQUEST
     *
     * @return void
     */
    function handle()
    {
        parent::handle();
        $this->showResults();
    }

    /**
     * Show search results
     *
     * @return void
     */
    function showResults()
    {
        // TODO: Support search operators like from: and to:, boolean, etc.

        $notice = new Notice();

        $this->notices = array();
        $search_engine = $notice->getSearchEngine('notice');
        $search_engine->set_sort_mode('chron');
        $search_engine->limit(($this->page - 1) * $this->rpp, $this->rpp + 1);
        if ($search_engine->query($this->query)) {
            $cnt = $notice->find();
            $this->notices = $notice->fetchAll();
        }

       $this->showJsonTimeline($this->notices);
    }

    /**
     * Do we need to write to the database?
     *
     * @return boolean true
     */
    function isReadOnly($args)
    {
        return true;
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Destroy a notice through the API
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  API
 * @package   StatusNet
 * @author    Craig Andrews <candrews@integralblue.com>
 * @author    Evan Prodromou <evan@status.net>
 * @author    Jeffery To <jeffery.to@gmail.com>
 * @author    Tom Blankenship <mac65@mac65.com>
 * @author    Mike Cochrane <mikec@mikenz.geek.nz>
 * @author    Robin Millette <robin@millette.info>
 * @author    Zach Copley <zach@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
 * @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('GNUSOCIAL')) { exit(1); }

/**
 * Deletes one of the authenticating user's statuses (notices).
 *
 * @category API
 * @package  StatusNet
 * @author   Craig Andrews <candrews@integralblue.com>
 * @author   Evan Prodromou <evan@status.net>
 * @author   Jeffery To <jeffery.to@gmail.com>
 * @author   Tom Blankenship <mac65@mac65.com>
 * @author   Mike Cochrane <mikec@mikenz.geek.nz>
 * @author   Robin Millette <robin@millette.info>
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class ApiStatusesDestroyAction extends ApiAuthAction
{
    protected function prepare(array $args=array())
    {
        parent::prepare($args);

        if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
            // TRANS: Client error displayed trying to delete a status not using POST or DELETE.
            // TRANS: POST and DELETE should not be translated.
            throw new ClientException(_('This method requires a POST or DELETE.'));
        }

        // FIXME: Return with a Not Acceptable status code?
        if (!in_array($this->format, array('xml', 'json'))) {
            // TRANS: Client error displayed when coming across a non-supported API method.
            throw new ClientException(_('API method not found.'), 404);
        }

        try {
            $this->notice = Notice::getByID($this->trimmed('id'));
        } catch (NoResultException $e) {
            // TRANS: Client error displayed trying to delete a status with an invalid ID.
            throw new ClientException(_('No status found with that ID.'), 404);
        }

        return true;
     }

    protected function handle()
    {
        parent::handle();

        if (!$this->scoped->sameAs($this->notice->getProfile()) && !$this->scoped->hasRight(Right::DELETEOTHERSNOTICE)) {
            // TRANS: Client error displayed trying to delete a status of another user.
            throw new AuthorizationException(_('You may not delete another user\'s status.'));
        }

        if (Event::handle('StartDeleteOwnNotice', array($this->scoped->getUser(), $this->notice))) {
            $this->notice->deleteAs($this->scoped);
            Event::handle('EndDeleteOwnNotice', array($this->scoped->getUser(), $this->notice));
        }
        $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);
            }
        }
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Show a notice (as a Twitter-style status)
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  API
 * @package   StatusNet
 * @author    Craig Andrews <candrews@integralblue.com>
 * @author    Evan Prodromou <evan@status.net>
 * @author    Jeffery To <jeffery.to@gmail.com>
 * @author    Tom Blankenship <mac65@mac65.com>
 * @author    Mike Cochrane <mikec@mikenz.geek.nz>
 * @author    Robin Millette <robin@millette.info>
 * @author    Zach Copley <zach@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
 * @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('GNUSOCIAL')) { exit(1); }

/**
 * Returns the notice specified by id as a Twitter-style status and inline user
 *
 * @category API
 * @package  StatusNet
 * @author   Craig Andrews <candrews@integralblue.com>
 * @author   Evan Prodromou <evan@status.net>
 * @author   Jeffery To <jeffery.to@gmail.com>
 * @author   Tom Blankenship <mac65@mac65.com>
 * @author   Mike Cochrane <mikec@mikenz.geek.nz>
 * @author   Robin Millette <robin@millette.info>
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class ApiStatusesShowAction extends ApiPrivateAuthAction
{
    var $notice_id = null;
    var $notice    = null;

    /**
     * Take arguments for running
     *
     * @param array $args $_REQUEST args
     *
     * @return boolean success flag
     */
    protected function prepare(array $args=array())
    {
        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');

        $this->notice = null;
        try {
            $this->notice = Notice::getByID($this->notice_id);
        } catch (NoResultException $e) {
            // No such notice was found, maybe it was deleted?
            $deleted = null;
            Event::handle('IsNoticeDeleted', array($this->notice_id, &$deleted));
            if ($deleted === true) {
                // TRANS: Client error displayed trying to show a deleted notice.
                throw new ClientException(_('Notice deleted.'), 410);
            }
            // TRANS: Client error displayed trying to show a non-existing notice.
            throw new ClientException(_('No such notice.'), 404);
        }

        if (!$this->notice->inScope($this->scoped)) {
            // TRANS: Client exception thrown when trying a view a notice the user has no access to.
            throw new ClientException(_('Access restricted.'), 403);
        }

        return true;
    }

    /**
     * Handle the request
     *
     * Check the format and show the notice
     *
     * @return void
     */
    protected function handle()
    {
        parent::handle();

        if (!in_array($this->format, array('xml', 'json', 'atom'))) {
            // TRANS: Client error displayed when coming across a non-supported API method.
            $this->clientError(_('API method not found.'), 404);
        }

        switch ($_SERVER['REQUEST_METHOD']) {
        case 'GET':
            $this->showNotice();
            break;
        case 'DELETE':
            $this->deleteNotice();
            break;
        default:
            // TRANS: Client error displayed calling an unsupported HTTP error in API status show.
            $this->clientError(_('HTTP method not supported.'), 405);
        }
    }

    /**
     * Show the notice
     *
     * @return void
     */
    function showNotice()
    {
        switch ($this->format) {
        case 'xml':
            $this->showSingleXmlStatus($this->notice);
            break;
        case 'json':
            $this->show_single_json_status($this->notice);
            break;
        case 'atom':
            $this->showSingleAtomStatus($this->notice);
            break;
        default:
            // TRANS: Exception thrown requesting an unsupported notice output format.
            // TRANS: %s is the requested output format.
            throw new Exception(sprintf(_("Unsupported format: %s."), $this->format));
        }
    }

    /**
     * We expose AtomPub here, so non-GET/HEAD reqs must be read/write.
     *
     * @param array $args other arguments
     *
     * @return boolean true
     */

    function isReadOnly($args)
    {
        return ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD');
    }

    /**
     * When was this notice last modified?
     *
     * @return string datestamp of the latest notice in the stream
     */
    function lastModified()
    {
        return strtotime($this->notice->created);
    }

    /**
     * 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()
    {
        return '"' . implode(
            ':',
            array($this->arg('action'),
                  common_user_cache_hash($this->auth_user),
                  common_language(),
                  $this->notice->id,
                  strtotime($this->notice->created))
        )
        . '"';
    }

    function deleteNotice()
    {
        if ($this->format != 'atom') {
            // TRANS: Client error displayed when trying to delete a notice not using the Atom format.
            $this->clientError(_('Can only delete using the Atom format.'));
        }

        if (empty($this->auth_user) ||
            ($this->notice->profile_id != $this->auth_user->id &&
             !$this->auth_user->hasRight(Right::DELETEOTHERSNOTICE))) {
            // TRANS: Client error displayed when a user has no rights to delete notices of other users.
            $this->clientError(_('Cannot delete this notice.'), 403);
        }

        if (Event::handle('StartDeleteOwnNotice', array($this->auth_user, $this->notice))) {
            $this->notice->deleteAs($this->scoped);
            Event::handle('EndDeleteOwnNotice', array($this->auth_user, $this->notice));
        }

        // @fixme is there better output we could do here?

        header('HTTP/1.1 200 OK');
        header('Content-Type: text/plain');
        // TRANS: Confirmation of notice deletion in API. %d is the ID (number) of the deleted notice.
        print(sprintf(_('Deleted notice %d'), $this->notice->id));
        print("\n");
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Post a notice (update your status) through the API
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  API
 * @package   StatusNet
 * @author    Craig Andrews <candrews@integralblue.com>
 * @author    Evan Prodromou <evan@status.net>
 * @author    Jeffery To <jeffery.to@gmail.com>
 * @author    Tom Blankenship <mac65@mac65.com>
 * @author    Mike Cochrane <mikec@mikenz.geek.nz>
 * @author    Robin Millette <robin@millette.info>
 * @author    Zach Copley <zach@status.net>
 * @copyright 2009-2010 StatusNet, Inc.
 * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

/* External API usage documentation. Please update when you change how this method works. */

/*! @page statusesupdate statuses/update

    @section Description
    Updates the authenticating user's status. Requires the status parameter specified below.
    Request must be a POST.

    @par URL pattern
    /api/statuses/update.:format

    @par Formats (:format)
    xml, json, atom

    @par HTTP Method(s)
    POST

    @par Requires Authentication
    Yes

    @param status (Required) The URL-encoded text of the status update.
    @param source (Optional) The source application name, if using HTTP authentication or an anonymous OAuth consumer.
    @param in_reply_to_status_id (Optional) The ID of an existing status that the update is in reply to.
    @param lat (Optional) The latitude the status refers to.
    @param long (Optional) The longitude the status refers to.
    @param media (Optional) a media upload, such as an image or movie file.

    @sa @ref authentication
    @sa @ref apiroot

    @subsection usagenotes Usage notes

    @li The URL pattern is relative to the @ref apiroot.
    @li If the @e source parameter is not supplied the source of the status will default to 'api'. When authenticated via a registered OAuth application, the application's registered name and URL will always override the source parameter.
    @li The XML response uses GeoRSS
    to encode the latitude and longitude (see example response below <georss:point>).
    @li Data uploaded via the @e media parameter should be multipart/form-data encoded.

    @subsection exampleusage Example usage

    @verbatim
    curl -u username:password http://example.com/api/statuses/update.xml -d status='Howdy!' -d lat='30.468' -d long='-94.743'
    @endverbatim

    @subsection exampleresponse Example response

    @verbatim
    <?xml version="1.0" encoding="UTF-8"?>
    <status>
      <text>Howdy!</text>
      <truncated>false</truncated>
      <created_at>Tue Mar 30 23:28:05 +0000 2010</created_at>
      <in_reply_to_status_id/>
      <source>api</source>
      <id>26668724</id>
      <in_reply_to_user_id/>
      <in_reply_to_screen_name/>
      <geo xmlns:georss="http://www.georss.org/georss">
        <georss:point>30.468 -94.743</georss:point>
      </geo>
      <favorited>false</favorited>
      <user>
        <id>25803</id>
        <name>Jed Sanders</name>
        <screen_name>jedsanders</screen_name>
        <location>Hoop and Holler, Texas</location>
        <description>I like to think of myself as America's Favorite.</description>
        <profile_image_url>http://avatar.example.com/25803-48-20080924200604.png</profile_image_url>
        <url>http://jedsanders.net</url>
        <protected>false</protected>
        <followers_count>5</followers_count>
        <profile_background_color/>
        <profile_text_color/>
        <profile_link_color/>
        <profile_sidebar_fill_color/>
        <profile_sidebar_border_color/>
        <friends_count>2</friends_count>
        <created_at>Wed Sep 24 20:04:00 +0000 2008</created_at>
        <favourites_count>0</favourites_count>
        <utc_offset>0</utc_offset>
        <time_zone>UTC</time_zone>
        <profile_background_image_url/>
        <profile_background_tile>false</profile_background_tile>
        <statuses_count>70</statuses_count>
        <following>true</following>
        <notifications>true</notifications>
      </user>
    </status>
    @endverbatim
*/

if (!defined('STATUSNET')) {
    exit(1);
}

/**
 * Updates the authenticating user's status (posts a notice).
 *
 * @category API
 * @package  StatusNet
 * @author   Craig Andrews <candrews@integralblue.com>
 * @author   Evan Prodromou <evan@status.net>
 * @author   Jeffery To <jeffery.to@gmail.com>
 * @author   Tom Blankenship <mac65@mac65.com>
 * @author   Mike Cochrane <mikec@mikenz.geek.nz>
 * @author   Robin Millette <robin@millette.info>
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class ApiStatusesUpdateAction extends ApiAuthAction
{
    protected $needPost = true;

    var $status                = null;
    var $in_reply_to_status_id = null;
    var $lat                   = null;
    var $lon                   = null;
    var $media_ids             = array();   // file_id in the keys

    /**
     * Take arguments for running
     *
     * @param array $args $_REQUEST args
     *
     * @return boolean success flag
     */
    protected function prepare(array $args=array())
    {
        parent::prepare($args);

        $this->status = $this->trimmed('status');
        $this->lat    = $this->trimmed('lat');
        $this->lon    = $this->trimmed('long');
        $matches = array();
        common_debug(get_called_class().': media_ids=='._ve($this->trimmed('media_ids')));
        if (preg_match_all('/\d+/', $this->trimmed('media_ids'), $matches) !== false) {
            foreach (array_unique($matches[0]) as $match) {
                try {
                    $this->media_ids[$match] = File::getByID($match);
                } catch (EmptyPkeyValueException $e) {
                    // got a zero from the client, at least Twidere does this on occasion
                } catch (NoResultException $e) {
                    // File ID was not found. Do we abort and report to the client?
                }
            }
        }

        $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
     *
     * @return void
     */
    protected function handle()
    {
        parent::handle();

        // Workaround for PHP returning empty $_POST and $_FILES when POST
        // length > post_max_size in php.ini

        if (empty($_FILES)
            && empty($_POST)
            && ($_SERVER['CONTENT_LENGTH'] > 0)
        ) {
             // TRANS: Client error displayed when the number of bytes in a POST request exceeds a limit.
             // TRANS: %s is the number of bytes of the CONTENT_LENGTH.
             $msg = _m('The server was unable to handle that much POST data (%s byte) due to its current configuration.',
                      'The server was unable to handle that much POST data (%s bytes) due to its current configuration.',
                      intval($_SERVER['CONTENT_LENGTH']));

            $this->clientError(sprintf($msg, $_SERVER['CONTENT_LENGTH']));
        }

        if (empty($this->status)) {
            // TRANS: Client error displayed when the parameter "status" is missing.
            $this->clientError(_('Client must provide a \'status\' parameter with a value.'));
        }

        if (is_null($this->scoped)) {
            // TRANS: Client error displayed when updating a status for a non-existing user.
            $this->clientError(_('No such user.'), 404);
        }

        /* Do not call shortenLinks until the whole notice has been build */

        // Check for commands

        $inter = new CommandInterpreter();
        $cmd = $inter->handle_command($this->auth_user, $this->status);

        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->auth_user->getCurrentNotice();
        } else {
            $reply_to = null;

            if (!empty($this->in_reply_to_status_id)) {
                // Check whether notice actually exists

                $reply = Notice::getKV($this->in_reply_to_status_id);

                if ($reply) {
                    $reply_to = $this->in_reply_to_status_id;
                } else {
                    // TRANS: Client error displayed when replying to a non-existing notice.
                    $this->clientError(_('Parent notice not found.'), 404);
                }
            }

            foreach(array_keys($this->media_ids) as $media_id) {
                // FIXME: Validation on this... Worst case is that if someone sends bad media_ids then
                // we'll fill the notice with non-working links, so no real harm, done, but let's fix.
                // The File objects are in the array, so we could get URLs from them directly.
                $this->status .= ' ' . common_local_url('attachment', array('attachment' => $media_id));
            }

            $upload = null;
            try {
                $upload = MediaFile::fromUpload('media', $this->scoped);
                $this->status .= ' ' . $upload->shortUrl();
                /* Do not call shortenLinks until the whole notice has been build */
            } catch (NoUploadedMediaException $e) {
                // There was no uploaded media for us today.
            }

            /* Do call shortenlinks here & check notice length since notice is about to be saved & sent */
            $status_shortened = $this->auth_user->shortenLinks($this->status);

            if (Notice::contentTooLong($status_shortened)) {
                if ($upload instanceof MediaFile) {
                    $upload->delete();
                }
                // TRANS: Client error displayed exceeding the maximum notice length.
                // TRANS: %d is the maximum lenth for a notice.
                $msg = _m('Maximum notice size is %d character, including attachment URL.',
                          'Maximum notice size is %d characters, including attachment URL.',
                          Notice::maxContent());
                /* Use HTTP 413 error code (Request Entity Too Large)
                 * instead of basic 400 for better understanding
                 */
                $this->clientError(sprintf($msg, Notice::maxContent()), 413);
            }

            $content = html_entity_decode($status_shortened, ENT_NOQUOTES, 'UTF-8');

            $options = array('reply_to' => $reply_to);

            if ($this->scoped->shareLocation()) {

                $locOptions = Notice::locationOptions($this->lat,
                                                      $this->lon,
                                                      null,
                                                      null,
                                                      $this->scoped);

                $options = array_merge($options, $locOptions);
            }

            try {
                $this->notice = Notice::saveNew(
                    $this->scoped->id,
                    $content,
                    $this->source,
                    $options
                );
            } catch (Exception $e) {
                $this->clientError($e->getMessage(), $e->getCode());
            }

            if (isset($upload)) {
                $upload->attachToNotice($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);
            } elseif ($this->format == 'atom') {
                $this->showSingleAtomStatus($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('SubCommand', 'UnsubCommand',
            'OnCommand', 'OffCommand', 'JoinCommand', 'LeaveCommand');

        $supported = null;

        if (Event::handle('CommandSupportedAPI', array($cmd, &$supported))) {
            $supported = $supported || in_array(get_class($cmd), $cmdlist);
        }

        return $supported;
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Base class for showing subscription information in the API
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  API
 * @package   StatusNet
 * @author    Dan Moore <dan@moore.cx>
 * @author    Evan Prodromou <evan@status.net>
 * @author    Zach Copley <zach@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    exit(1);
}

/**
 * This class outputs a list of profiles as Twitter-style user and status objects.
 * It is used by the API methods /api/statuses/(friends|followers). To support the
 * social graph methods it also can output a simple list of IDs.
 *
 * @category API
 * @package  StatusNet
 * @author   Dan Moore <dan@moore.cx>
 * @author   Evan Prodromou <evan@status.net>
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
abstract 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
     */
    protected function prepare(array $args=array())
    {
        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->target = $this->getTargetProfile($this->arg('id'));

        if (!($this->target instanceof Profile)) {
            // TRANS: Client error displayed when requesting a list of followers for a non-existing user.
            $this->clientError(_('No such user.'), 404);
        }

        $this->profiles = $this->getProfiles();

        return true;
    }

    /**
     * Handle the request
     *
     * Show the profiles
     *
     * @return void
     */
    protected function handle()
    {
        parent::handle();

        if (!in_array($this->format, array('xml', 'json'))) {
            // TRANS: Client error displayed when coming across a non-supported API method.
            $this->clientError(_('API method not found.'), 404);
        }

        $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 related to the type of subscriber/subscription action
     *
     * @return array Profiles
     */
    abstract protected 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_user_cache_hash($this->auth_user),
                      common_language(),
                      $this->target->id,
                      // Caching tags.
                      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',
                                               'xmlns:statusnet' => 'http://status.net/schema/api/1/'));
            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:
            // TRANS: Client error displayed when requesting profiles of followers in an unsupported format.
            $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:
            // TRANS: Client error displayed when requesting IDs of followers in an unsupported format.
            $this->clientError(_('Unsupported format.'));
            break;
        }
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Show the friends timeline
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  API
 * @package   StatusNet
 * @author    Craig Andrews <candrews@integralblue.com>
 * @author    Evan Prodromou <evan@status.net>
 * @author    Jeffery To <jeffery.to@gmail.com>
 * @author    mac65 <mac65@mac65.com>
 * @author    Mike Cochrane <mikec@mikenz.geek.nz>
 * @author    Robin Millette <robin@millette.info>
 * @author    Zach Copley <zach@status.net>
 * @copyright 2009-2010 StatusNet, Inc.
 * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

/* External API usage documentation. Please update when you change how this method works. */

/*! @page friendstimeline statuses/friends_timeline

    @section Description
    Returns the 20 most recent statuses posted by the authenticating
    user and that user's friends. This is the equivalent of "You and
    friends" page in the web interface.

    @par URL patterns
    @li /api/statuses/friends_timeline.:format
    @li /api/statuses/friends_timeline/:id.:format

    @par Formats (:format)
    xml, json, rss, atom

    @par ID (:id)
    username, user id

    @par HTTP Method(s)
    GET

    @par Requires Authentication
    Sometimes (see: @ref authentication)

    @param user_id (Optional) Specifies a user by ID
    @param screen_name (Optional) Specifies a user by screename (nickname)
    @param since_id (Optional) Returns only statuses with an ID greater
    than (that is, more recent than) the specified ID.
    @param max_id (Optional) Returns only statuses with an ID less than
    (that is, older than) or equal to the specified ID.
    @param count (Optional) Specifies the number of statuses to retrieve.
    @param page (Optional) Specifies the page of results to retrieve.

    @sa @ref authentication
    @sa @ref apiroot

    @subsection usagenotes Usage notes
    @li The URL pattern is relative to the @ref apiroot.
    @li The XML response uses GeoRSS
    to encode the latitude and longitude (see example response below <georss:point>).

    @subsection exampleusage Example usage

    @verbatim
    curl http://identi.ca/api/statuses/friends_timeline/evan.xml?count=1&page=2
    @endverbatim

    @subsection exampleresponse Example response

    @verbatim
    <?xml version="1.0"?>
    <statuses type="array">
      <status>
        <text>back from the !yul !drupal meet with Evolving Web folk, @anarcat, @webchick and others, and an interesting refresher on SQL indexing</text>
        <truncated>false</truncated>
        <created_at>Wed Mar 31 01:33:02 +0000 2010</created_at>
        <in_reply_to_status_id/>
        <source><a href="http://somesourcecode.net/microblog/">mbpidgin</a></source>
        <id>26674201</id>
        <in_reply_to_user_id/>
        <in_reply_to_screen_name/>
        <geo/>
        <favorited>false</favorited>
        <user>
          <id>246</id>
          <name>Mark</name>
          <screen_name>lambic</screen_name>
          <location>Montreal, Canada</location>
          <description>Geek</description>
          <profile_image_url>http://avatar.identi.ca/246-48-20080702141545.png</profile_image_url>
          <url>http://lambic.co.uk</url>
          <protected>false</protected>
          <followers_count>73</followers_count>
          <profile_background_color>#F0F2F5</profile_background_color>
          <profile_text_color/>
          <profile_link_color>#002E6E</profile_link_color>
          <profile_sidebar_fill_color>#CEE1E9</profile_sidebar_fill_color>
          <profile_sidebar_border_color/>
          <friends_count>58</friends_count>
          <created_at>Wed Jul 02 14:12:15 +0000 2008</created_at>
          <favourites_count>2</favourites_count>
          <utc_offset>-14400</utc_offset>
          <time_zone>US/Eastern</time_zone>
          <profile_background_image_url/>
          <profile_background_tile>false</profile_background_tile>
          <statuses_count>933</statuses_count>
          <following>false</following>
          <notifications>false</notifications>
        </user>
      </status>
    </statuses>
    @endverbatim
*/

if (!defined('STATUSNET')) {
    exit(1);
}

/**
 * Returns the most recent notices (default 20) posted by the target user.
 * This is the equivalent of 'You and friends' page accessed via Web.
 *
 * @category API
 * @package  StatusNet
 * @author   Craig Andrews <candrews@integralblue.com>
 * @author   Evan Prodromou <evan@status.net>
 * @author   Jeffery To <jeffery.to@gmail.com>
 * @author   mac65 <mac65@mac65.com>
 * @author   Mike Cochrane <mikec@mikenz.geek.nz>
 * @author   Robin Millette <robin@millette.info>
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class ApiTimelineFriendsAction extends ApiBareAuthAction
{
    var $notices  = null;

    /**
     * Take arguments for running
     *
     * @param array $args $_REQUEST args
     *
     * @return boolean success flag
     *
     */
    protected function prepare(array $args=array())
    {
        parent::prepare($args);
        $this->target = $this->getTargetProfile($this->arg('id'));

        if (!($this->target instanceof Profile)) {
            // TRANS: Client error displayed when requesting dents of a user and friends for a user that does not exist.
            $this->clientError(_('No such user.'), 404);
        }

        $this->notices = $this->getNotices();

        return true;
    }

    /**
     * Handle the request
     *
     * Just show the notices
     *
     * @return void
     */
    protected function handle()
    {
        parent::handle();
        $this->showTimeline();
    }

    /**
     * Show the timeline of notices
     *
     * @return void
     */
    function showTimeline()
    {
        $sitename   = common_config('site', 'name');
        // TRANS: Title of API timeline for a user and friends.
        // TRANS: %s is a username.
        $title      = sprintf(_("%s and friends"), $this->target->nickname);
        $taguribase = TagURI::base();
        $id         = "tag:$taguribase:FriendsTimeline:" . $this->target->id;

        $subtitle = sprintf(
            // TRANS: Message is used as a subtitle. %1$s is a user nickname, %2$s is a site name.
            _('Updates from %1$s and friends on %2$s!'),
            $this->target->nickname,
            $sitename
        );

        $logo = $this->target->avatarUrl(AVATAR_PROFILE_SIZE);
        $link = common_local_url('all',
                    array('nickname' => $this->target->nickname));
        $self = $this->getSelfUri();

        switch($this->format) {
        case 'xml':
            $this->showXmlTimeline($this->notices);
            break;
        case 'rss':

            $this->showRssTimeline(
                $this->notices,
                $title,
                $link,
                $subtitle,
                null,
                $logo,
                $self
            );
            break;
        case 'atom':
            header('Content-Type: application/atom+xml; charset=utf-8');

            $atom = new AtomNoticeFeed($this->auth_user);

            $atom->setId($id);
            $atom->setTitle($title);
            $atom->setSubtitle($subtitle);
            $atom->setLogo($logo);
            $atom->setUpdated('now');
            $atom->addLink($link);
            $atom->setSelfLink($self);

            $atom->addEntryFromNotices($this->notices);

            $this->raw($atom->getString());

            break;
        case 'json':
            $this->showJsonTimeline($this->notices);
            break;
        case 'as':
            header('Content-Type: ' . ActivityStreamJSONDocument::CONTENT_TYPE);
            $doc = new ActivityStreamJSONDocument($this->auth_user, $title);
            $doc->addLink($link, 'alternate', 'text/html');
            $doc->addItemsFromNotices($this->notices);
            $this->raw($doc->asString());
            break;
        default:
            // TRANS: Client error displayed when coming across a non-supported API method.
            $this->clientError(_('API method not found.'), 404);
        }
    }

    /**
     * Get notices
     *
     * @return array notices
     */
    function getNotices()
    {
        $notices = array();

        $stream = new InboxNoticeStream($this->target, $this->scoped);

        $notice = $stream->getNotices(($this->page-1) * $this->count,
                                      $this->count,
                                      $this->since_id,
                                      $this->max_id);

        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_user_cache_hash($this->auth_user),
                                       common_language(),
                                       $this->target->id,
                                       strtotime($this->notices[0]->created),
                                       strtotime($this->notices[$last]->created))
                                 )
              . '"';
        }

        return null;
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Show a group's notices
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  API
 * @package   StatusNet
 * @author    Craig Andrews <candrews@integralblue.com>
 * @author    Evan Prodromou <evan@status.net>
 * @author    Jeffery To <jeffery.to@gmail.com>
 * @author    Zach Copley <zach@status.net>
 * @copyright 2009-2010 StatusNet, Inc.
 * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
 * @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);
}

/**
 * Returns the most recent notices (default 20) posted to the group specified by ID
 *
 * @category API
 * @package  StatusNet
 * @author   Craig Andrews <candrews@integralblue.com>
 * @author   Evan Prodromou <evan@status.net>
 * @author   Jeffery To <jeffery.to@gmail.com>
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class ApiTimelineGroupAction extends ApiPrivateAuthAction
{
    var $group   = null;
    var $notices = null;

    /**
     * Take arguments for running
     *
     * @param array $args $_REQUEST args
     *
     * @return boolean success flag
     *
     */
    protected function prepare(array $args=array())
    {
        parent::prepare($args);

        $this->group   = $this->getTargetGroup($this->arg('id'));

        return true;
    }

    /**
     * Handle the request
     *
     * Just show the notices
     *
     * @return void
     */
    protected function handle()
    {
        parent::handle();

        if (empty($this->group)) {
            // TRANS: Client error displayed requesting most recent notices to a group for a non-existing group.
            $this->clientError(_('Group not found.'), 404);
        }

        $this->notices = $this->getNotices();
        $this->showTimeline();
    }

    /**
     * Show the timeline of notices
     *
     * @return void
     */
    function showTimeline()
    {
        // We'll pull common formatting out of this for other formats
        $atom = new AtomGroupNoticeFeed($this->group, $this->auth_user);

        $self = $this->getSelfUri();

        $link = common_local_url('showgroup',
                                 array('nickname' => $this->group->nickname));

        switch($this->format) {
        case 'xml':
            $this->showXmlTimeline($this->notices);
            break;
        case 'rss':
            $this->showRssTimeline(
                $this->notices,
                $atom->title,
                $this->group->homeUrl(),
                $atom->subtitle,
                null,
                $atom->logo,
                $self
            );
            break;
        case 'atom':
            header('Content-Type: application/atom+xml; charset=utf-8');
                $atom->addEntryFromNotices($this->notices);
                $this->raw($atom->getString());
            break;
        case 'json':
            $this->showJsonTimeline($this->notices);
            break;
        case 'as':
            header('Content-Type: ' . ActivityStreamJSONDocument::CONTENT_TYPE);
            $doc = new ActivityStreamJSONDocument($this->auth_user);
            $doc->setTitle($atom->title);
            $doc->addLink($link, 'alternate', 'text/html');
            $doc->addItemsFromNotices($this->notices);
            $this->raw($doc->asString());
            break;
        default:
            // TRANS: Client error displayed when trying to handle an unknown API method.
            $this->clientError(_('API method not found.'), 404);
        }
    }

    /**
     * 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
        );

        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_user_cache_hash($this->auth_user),
                      common_language(),
                      $this->group->id,
                      strtotime($this->notices[0]->created),
                      strtotime($this->notices[$last]->created))
            )
            . '"';
        }

        return null;
    }
}