code:
<?php
// @todo FIXME: standard file header missing.
/**
 * Form for blocking a user from a group
 *
 * @category Form
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @author   Sarven Capadisli <csarven@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      BlockForm
 */
class GroupBlockForm extends Form
{
    /**
     * Profile of user to block
     */

    var $profile = null;

    /**
     * Group to block the user from
     */

    var $group = null;

    /**
     * Return-to args
     */

    var $args = null;

    /**
     * Constructor
     *
     * @param HTMLOutputter $out     output channel
     * @param Profile       $profile profile of user to block
     * @param User_group    $group   group to block user from
     * @param array         $args    return-to args
     */
    function __construct($out=null, $profile=null, $group=null, $args=null)
    {
        parent::__construct($out);

        $this->profile = $profile;
        $this->group   = $group;
        $this->args    = $args;
    }

    /**
     * ID of the form
     *
     * @return int ID of the form
     */
    function id()
    {
        // This should be unique for the page.
        return 'block-' . $this->profile->id;
    }

    /**
     * class of the form
     *
     * @return string class of the form
     */
    function formClass()
    {
        return 'form_group_block';
    }

    /**
     * Action of the form
     *
     * @return string URL of the action
     */
    function action()
    {
        return common_local_url('groupblock');
    }

    /**
     * Legend of the Form
     *
     * @return void
     */
    function formLegend()
    {
        // TRANS: Form legend for form to block user from a group.
        $this->out->element('legend', null, _('Block user from group'));
    }

    /**
     * Data elements of the form
     *
     * @return void
     */
    function formData()
    {
        $this->out->hidden('blockto-' . $this->profile->id,
                           $this->profile->id,
                           'blockto');
        $this->out->hidden('blockgroup-' . $this->group->id,
                           $this->group->id,
                           'blockgroup');
        if ($this->args) {
            foreach ($this->args as $k => $v) {
                $this->out->hidden('returnto-' . $k, $v);
            }
        }
    }

    /**
     * Action elements
     *
     * @return void
     */
    function formActions()
    {
        $this->out->submit(
            'submit',
            // TRANS: Button text for the form that will block a user from a group.
            _m('BUTTON','Block'),
            'submit',
            null,
            // TRANS: Submit button title.
            _m('TOOLTIP', 'Block this user so that they can no longer post messages to it.'));
    }
}


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

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

require_once INSTALLDIR.'/lib/form.php';

/**
 * Form for editing a group
 *
 * @category Form
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @author   Sarven Capadisli <csarven@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      UnsubscribeForm
 */
class GroupEditForm extends Form
{
    /**
     * group for user to join
     */
    var $group = null;

    /**
     * Constructor
     *
     * @param Action     $out   output channel
     * @param User_group $group group to join
     */
    function __construct($out=null, $group=null)
    {
        parent::__construct($out);

        $this->group = $group;
    }

    /**
     * ID of the form
     *
     * @return string ID of the form
     */
    function id()
    {
        if ($this->group) {
            return 'form_group_edit-' . $this->group->id;
        } else {
            return 'form_group_add';
        }
    }

    /**
     * class of the form
     *
     * @return string of the form class
     */
    function formClass()
    {
        return 'form_settings';
    }

    /**
     * Action of the form
     *
     * @return string URL of the action
     */
    function action()
    {
        if ($this->group) {
            return common_local_url('editgroup',
                                    array('nickname' => $this->group->nickname));
        } else {
            return common_local_url('newgroup');
        }
    }

    /**
     * Name of the form
     *
     * @return void
     */
    function formLegend()
    {
        // TRANS: Form legend for group edit form.
        $this->out->element('legend', null, _('Create a new group'));
    }

    /**
     * Data elements of the form
     *
     * @return void
     */
    function formData()
    {
        if ($this->group) {
            $id = $this->group->id;
            $nickname = $this->group->nickname;
            $fullname = $this->group->fullname;
            $homepage = $this->group->homepage;
            $description = $this->group->description;
            $location = $this->group->location;
        } else {
            $id = '';
            $nickname = '';
            $fullname = '';
            $homepage = '';
            $description = '';
            $location = '';
        }

        $this->out->elementStart('ul', 'form_data');
        if (Event::handle('StartGroupEditFormData', array($this))) {
            $this->out->elementStart('li');
            $this->out->hidden('groupid', $id);
            // TRANS: Field label on group edit form.
            $this->out->input('newnickname', _('Nickname'),
                              ($this->out->arg('newnickname')) ? $this->out->arg('newnickname') : $nickname,
                              // TRANS: Field title on group edit form.
                              _('1-64 lowercase letters or numbers, no punctuation or spaces.'),
                              null, false,
                              $this->group instanceof User_group && !common_config('profile', 'changenick')
                                    ? array('disabled'=>'disabled') // can't change nickname
                                    : array()); // either we can change nickname, or we're creating a new group.
            $this->out->elementEnd('li');
            $this->out->elementStart('li');
            // TRANS: Field label on group edit form.
            $this->out->input('fullname', _('Full name'),
                              ($this->out->arg('fullname')) ? $this->out->arg('fullname') : $fullname);
            $this->out->elementEnd('li');
            $this->out->elementStart('li');
            // TRANS: Field label on group edit form; points to "more info" for a group.
            $this->out->input('homepage', _('Homepage'),
                              ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage,
                              // TRANS: Field title on group edit form.
                              _('URL of the homepage or blog of the group or topic.'));
            $this->out->elementEnd('li');
            $this->out->elementStart('li');
            $desclimit = User_group::maxDescription();
            if ($desclimit == 0) {
                // TRANS: Text area title for group description when there is no text limit.
                $descinstr = _('Describe the group or topic.');
            } else {
                // TRANS: Text area title for group description.
                // TRANS: %d is the number of characters available for the description.
                $descinstr = sprintf(_m('Describe the group or topic in %d character or less.',
                                        'Describe the group or topic in %d characters or less.',
                                        $desclimit),
                                     $desclimit);
            }
            // TRANS: Text area label on group edit form; contains description of group.
            $this->out->textarea('description', _('Description'),
                                 ($this->out->arg('description')) ? $this->out->arg('description') : $description,
                                 $descinstr);
            $this->out->elementEnd('li');
            $this->out->elementStart('li');
            // TRANS: Field label on group edit form.
            $this->out->input('location', _('Location'),
                              ($this->out->arg('location')) ? $this->out->arg('location') : $location,
                              // TRANS: Field title on group edit form.
                              _('Location for the group, if any, like "City, State (or Region), Country".'));
            $this->out->elementEnd('li');
            if (common_config('group', 'maxaliases') > 0) {
                $aliases = (empty($this->group)) ? array() : $this->group->getAliases();
                $this->out->elementStart('li');
                // TRANS: Field label on group edit form.
                $this->out->input('aliases', _('Aliases'),
                                  ($this->out->arg('aliases')) ? $this->out->arg('aliases') :
                                  (!empty($aliases)) ? implode(' ', $aliases) : '',
                                  // TRANS: Input field title for group aliases.
                                  // TRANS: %d is the maximum number of group aliases available.
                                  sprintf(_m('Extra nicknames for the group, separated with commas or spaces. Maximum %d alias allowed.',
                                             'Extra nicknames for the group, separated with commas or spaces. Maximum %d aliases allowed.',
                                             common_config('group', 'maxaliases')),
                                          common_config('group', 'maxaliases')));;
                $this->out->elementEnd('li');
            }
            $this->out->elementStart('li');
            // TRANS: Checkbox field label on group edit form to mark a group private.
            $this->out->checkbox('private', _m('LABEL','Private'),
                                  ($this->out->arg('private')) ? $this->out->arg('private') :
                                 ((!empty($this->group)) ? $this->group->isPrivate() : false),
                                 // TRANS: Checkbox field title on group edit form to mark a group private.
                                 _('New members must be approved by admin and all posts are forced to be private.'));
            $this->out->elementEnd('li');
            Event::handle('EndGroupEditFormData', array($this));
        }
        $this->out->elementEnd('ul');
    }

    /**
     * Action elements
     *
     * @return void
     */
    function formActions()
    {
        // TRANS: Text for save button on group edit form.
        $this->out->submit('submit', _m('BUTTON','Save'));
    }
}

repo_name: dansup/gnu

license: mit


code:
<?php

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

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

require_once INSTALLDIR.'/lib/widget.php';

/**
 * Widget to show a list of groups
 *
 * @category Public
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */

class GroupList extends Widget
{
    /** Current group, group query. */
    var $group = null;
    /** Owner of this list */
    var $owner = null;
    /** Action object using us. */
    var $action = null;

    function __construct($group, $owner=null, $action=null)
    {
        parent::__construct($action);

        $this->group = $group;
        $this->owner = $owner;
        $this->action = $action;
    }

    function show()
    {
        $this->out->elementStart('ul', 'profiles groups xoxo');

        $cnt = 0;

        while ($this->group->fetch()) {
            $cnt++;
            if($cnt > GROUPS_PER_PAGE) {
                break;
            }
            $this->showgroup();
        }

        $this->out->elementEnd('ul');

        return $cnt;
    }

    function showGroup()
    {
        $this->out->elementStart('li', array('class' => 'profile h-card',
                                             'id' => 'group-' . $this->group->id));

        $user = common_current_user();

        $this->out->elementStart('div', 'entity_profile');

        $logo = $this->group->stream_logo ?: User_group::defaultLogo(AVATAR_STREAM_SIZE);

        $this->out->elementStart('a', array('href' => $this->group->homeUrl(),
                                            'class' => 'u-url p-nickname',
                                            'rel' => 'contact group'));
        $this->out->element('img', array('src' => $logo,
                                         'class' => 'avatar u-photo',
                                         'width' => AVATAR_STREAM_SIZE,
                                         'height' => AVATAR_STREAM_SIZE,
                                         'alt' => $this->group->getBestName()));
        $this->out->text($this->group->getNickname());
        $this->out->elementEnd('a');

        if ($this->group->fullname) {
            $this->out->text(' ');
            $this->out->elementStart('span', 'p-name');
            $this->out->raw($this->highlight($this->group->fullname));
            $this->out->elementEnd('span');
        }
        if ($this->group->location) {
            $this->out->text(' ');
            $this->out->elementStart('span', 'label');
            $this->out->raw($this->highlight($this->group->location));
            $this->out->elementEnd('span');
        }
        if ($this->group->homepage) {
            $this->out->text(' ');
            $this->out->elementStart('a', array('href' => $this->group->homepage,
                                                'class' => 'u-url'));
            $this->out->raw($this->highlight($this->group->homepage));
            $this->out->elementEnd('a');
        }
        if ($this->group->description) {
            $this->out->elementStart('p', 'note');
            $this->out->raw($this->highlight($this->group->description));
            $this->out->elementEnd('p');
        }

        // If we're on a list with an owner (subscriptions or subscribers)...

        if (!empty($user) && !empty($this->owner) && $user->id == $this->owner->id) {
            $this->showOwnerControls();
        }

        $this->out->elementEnd('div');

        if ($user) {
            $this->out->elementStart('div', 'entity_actions');
            $this->out->elementStart('ul');
            $this->out->elementStart('li', 'entity_subscribe');
            // XXX: special-case for user looking at own
            // subscriptions page
            if ($user->isMember($this->group)) {
                $lf = new LeaveForm($this->out, $this->group);
                $lf->show();
            } else if (!Group_block::isBlocked($this->group, $user->getProfile())) {
                $jf = new JoinForm($this->out, $this->group);
                $jf->show();
            }
            $this->out->elementEnd('li');
            $this->out->elementEnd('ul');
            $this->out->elementEnd('div');
        }

        $this->out->elementEnd('li');
    }

    /* Override this in subclasses. */

    function showOwnerControls()
    {
        return;
    }

    function highlight($text)
    {
        return htmlspecialchars($text);
    }
}


code:
<?php
// @todo FIXME: add documentation.

class GroupMemberList extends ProfileList
{
    var $group = null;

    function __construct($profile, $group, $action)
    {
        parent::__construct($profile, $action);

        $this->group = $group;
    }

    function newListItem($profile)
    {
        return new GroupMemberListItem($profile, $this->group, $this->action);
    }
}


code:
<?php
// @todo FIXME: add documentation.

class GroupMemberListItem extends ProfileListItem
{
    var $group = null;

    function __construct($profile, $group, $action)
    {
        parent::__construct($profile, $action);

        $this->group = $group;
    }

    function showFullName()
    {
        parent::showFullName();
        if ($this->profile->isAdmin($this->group)) {
            $this->out->text(' '); // for separating the classes.
            // TRANS: Indicator in group members list that this user is a group administrator.
            $this->out->element('span', 'role', _m('GROUPADMIN','Admin'));
        }
    }

    function showActions()
    {
        $this->startActions();
        if (Event::handle('StartProfileListItemActionElements', array($this))) {
            $this->showSubscribeButton();
            $this->showMakeAdminForm();
            $this->showGroupBlockForm();
            Event::handle('EndProfileListItemActionElements', array($this));
        }
        $this->endActions();
    }

    function showMakeAdminForm()
    {
        $user = common_current_user();

        if (!empty($user) &&
            $user->id != $this->profile->id &&
            ($user->isAdmin($this->group) || $user->hasRight(Right::MAKEGROUPADMIN)) &&
            !$this->profile->isAdmin($this->group)) {
            $this->out->elementStart('li', 'entity_make_admin');
            $maf = new MakeAdminForm($this->out, $this->profile, $this->group,
                                     $this->returnToArgs());
            $maf->show();
            $this->out->elementEnd('li');
        }

    }

    function showGroupBlockForm()
    {
        $user = common_current_user();

        if (!empty($user) && $user->id != $this->profile->id && $user->isAdmin($this->group)) {
            $this->out->elementStart('li', 'entity_block');
            $bf = new GroupBlockForm($this->out, $this->profile, $this->group,
                                     $this->returnToArgs());
            $bf->show();
            $this->out->elementEnd('li');
        }
    }

    function linkAttributes()
    {
        $aAttrs = parent::linkAttributes();

        if (common_config('nofollow', 'members')) {
            $aAttrs['rel'] .= ' nofollow';
        }

        return $aAttrs;
    }

    function homepageAttributes()
    {
        $aAttrs = parent::linkAttributes();

        if (common_config('nofollow', 'members')) {
            $aAttrs['rel'] = 'nofollow';
        }

        return $aAttrs;
    }

    /**
     * Fetch necessary return-to arguments for the profile forms
     * to return to this list when they're done.
     *
     * @return array
     */
    protected function returnToArgs()
    {
        $args = array('action' => 'groupmembers',
                      'nickname' => $this->group->nickname);
        $page = $this->out->arg('page');
        if ($page) {
            $args['param-page'] = $page;
        }
        return $args;
    }
}


code:
<?php

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

class GroupMembersMiniList extends ProfileMiniList
{
    function newListItem(Profile $profile)
    {
        return new GroupMembersMiniListItem($profile, $this->action);
    }
}


code:
<?php

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

class GroupMembersMiniListItem extends ProfileMiniListItem
{
    function linkAttributes()
    {
        $aAttrs = parent::linkAttributes();

        if (common_config('nofollow', 'members')) {
            $aAttrs['rel'] .= ' nofollow';
        }

        return $aAttrs;
    }
}


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

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

require_once INSTALLDIR.'/lib/grouplist.php';

/**
 * Widget to show a list of groups, good for sidebar
 *
 * @category Public
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class GroupMiniList extends GroupList
{
    function show()
    {
        $this->out->elementStart('ul', 'entities groups xoxo');

        $cnt = 0;

        while ($this->group->fetch()) {
            $cnt++;
            if($cnt > GROUPS_PER_MINILIST) {
                break;
            }
            $this->showGroup();
        }

        $this->out->elementEnd('ul');

        return $cnt;
    }

    function showGroup()
    {
        $this->out->elementStart('li', 'h-card');
        $this->out->elementStart('a', array('title' => $this->group->getBestName(),
                                       'href' => $this->group->homeUrl(),
                                       'rel' => 'contact group',
                                       'class' => 'p-name u-url org'));

        $logo = ($this->group->mini_logo) ?
          $this->group->mini_logo : User_group::defaultLogo(AVATAR_MINI_SIZE);

        $this->out->element('img', array('src' => $logo,
                                    'width' => AVATAR_MINI_SIZE,
                                    'height' => AVATAR_MINI_SIZE,
                                    'class' => 'avatar photo',
                                    'alt' => $this->group->getBestName()));
        $this->out->elementEnd('a');
        $this->out->elementEnd('li');
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Class for an exception when the group profile is missing
 *
 * 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  Exception
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
 * @link      http://status.net/
 */

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

/**
 * Class for an exception when the group profile is missing
 *
 * @category Exception
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
 * @link     http://status.net/
 */

class GroupNoProfileException extends NoProfileException
{
    protected $group = null;

    /**
     * constructor
     *
     * @param User_group $user User_group that's missing a profile
     */

    public function __construct(User_group $group)
    {
        $this->group = $group;

        // TRANS: Exception text shown when no profile can be found for a group.
        // TRANS: %1$s is a group nickname, $2$d is a group profile_id (number).
        $message = sprintf(_('Group "%1$s" (%2$d) has no profile record.'),
                           $group->nickname, $group->getID());

        parent::__construct($group->profile_id, $message);
    }

    /**
     * Accessor for user
     *
     * @return User_group the group that triggered this exception
     */

    public function getGroup()
    {
        return $this->group;
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Stream of notices for a group
 *
 * PHP version 5
 *
 * 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  Stream
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    // This check helps protect against security problems;
    // your code file can't be executed directly from the web.
    exit(1);
}

/**
 * Stream of notices for a group
 *
 * @category  Stream
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */
class GroupNoticeStream extends ScopingNoticeStream
{
    var $group;
    var $userProfile;

    function __construct($group, $profile = -1)
    {
        if (is_int($profile) && $profile == -1) {
            $profile = Profile::current();
        }
        $this->group = $group;
        $this->userProfile = $profile;

        parent::__construct(new CachingNoticeStream(new RawGroupNoticeStream($group),
                                                    'user_group:notice_ids:' . $group->id),
                            $profile);
    }

    function getNoticeIds($offset, $limit, $since_id, $max_id)
    {
        if ($this->impossibleStream()) {
            return array();
        } else {
            return parent::getNoticeIds($offset, $limit, $since_id, $max_id);
        }
    }

    function getNotices($offset, $limit, $sinceId = null, $maxId = null)
    {
        if ($this->impossibleStream()) {
            return new ArrayWrapper(array());
        } else {
            return parent::getNotices($offset, $limit, $sinceId, $maxId);
        }
    }

    function impossibleStream()
    {
        if ($this->group->force_scope &&
            (empty($this->userProfile) || !$this->userProfile->isMember($this->group))) {
            return true;
        }

        return false;
    }
}

/**
 * Stream of notices for a group
 *
 * @category  Stream
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */
class RawGroupNoticeStream extends NoticeStream
{
    protected $group;

    function __construct($group)
    {
        $this->group = $group;
    }

    function getNoticeIds($offset, $limit, $since_id, $max_id)
    {
        $inbox = new Group_inbox();

        $inbox->group_id = $this->group->id;

        $inbox->selectAdd();
        $inbox->selectAdd('notice_id');

        Notice::addWhereSinceId($inbox, $since_id, 'notice_id');
        Notice::addWhereMaxId($inbox, $max_id, 'notice_id');

        $inbox->orderBy('created DESC, notice_id DESC');

        if (!is_null($offset)) {
            $inbox->limit($offset, $limit);
        }

        $ids = array();

        if ($inbox->find()) {
            while ($inbox->fetch()) {
                $ids[] = $inbox->notice_id;
            }
        }

        return $ids;
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Profile block to show for a group
 *
 * PHP version 5
 *
 * 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  Widget
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    // This check helps protect against security problems;
    // your code file can't be executed directly from the web.
    exit(1);
}

/**
 * Profile block to show for a group
 *
 * @category  Widget
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */
class GroupProfileBlock extends ProfileBlock
{
    protected $group = null;

    function __construct($out, $group)
    {
        parent::__construct($out);
        $this->group = $group;
        $this->profile = $this->group->getProfile();
    }

    protected function showAvatar(Profile $profile, $size=null)
    {
        $avatar_url = $profile->getGroup()->homepage_logo ?: User_group::defaultLogo($size ?: $this->avatarSize());
        $this->out->element('img', array('src' => $avatar_url,
                                         'class' => 'avatar u-photo',
                                         'width' => $this->avatarSize(),
                                         'height' => $this->avatarSize(),
                                         'alt' => $profile->getBestName()));
    }

    function name()
    {
        return $this->group->getBestName();
    }

    function url()
    {
        return $this->group->homeUrl();
    }

    function location()
    {
        return $this->group->location;
    }

    function homepage()
    {
        return $this->group->homepage;
    }

    function description()
    {
        return $this->group->description;
    }

    function otherProfiles()
    {
        return array();
    }

    function showActions()
    {
        $cur = common_current_user();
        $this->out->elementStart('div', 'entity_actions');
        // TRANS: Group actions header (h2). Text hidden by default.
        $this->out->element('h2', null, _('Group actions'));
        $this->out->elementStart('ul');
        if (Event::handle('StartGroupActionsList', array($this, $this->group))) {
            $this->out->elementStart('li', 'entity_subscribe');
            if (Event::handle('StartGroupSubscribe', array($this, $this->group))) {
                if ($cur) {
                    $profile = $cur->getProfile();
                    if ($profile->isMember($this->group)) {
                        $lf = new LeaveForm($this->out, $this->group);
                        $lf->show();
                    } else if ($profile->isPendingMember($this->group)) {
                        $cf = new CancelGroupForm($this->out, $this->group);
                        $cf->show();
                    } else if (!Group_block::isBlocked($this->group, $profile)) {
                        $jf = new JoinForm($this->out, $this->group);
                        $jf->show();
                    }
                }
                Event::handle('EndGroupSubscribe', array($this, $this->group));
            }
            $this->out->elementEnd('li');
            if ($cur && $cur->isAdmin($this->group)) {
                $this->out->elementStart('li', 'entity_edit');
                $this->out->element('a', array('href' => common_local_url('editgroup',
                                                                          array('nickname' => $this->group->nickname)),
                                               // TRANS: Tooltip for menu item in the group navigation page. Only shown for group administrators.
                                               // TRANS: %s is the nickname of the group.
                                               'title' => sprintf(_m('TOOLTIP','Edit %s group properties'), $this->group->nickname)),
                                    // TRANS: Link text for link on user profile.
                                    _m('BUTTON','Edit'));
                $this->out->elementEnd('li');
                $this->out->elementStart('li', 'entity_edit');
                $this->out->element('a', array('href' => common_local_url('grouplogo',
                                                                          array('nickname' => $this->group->nickname)),
                                               // TRANS: Tooltip for menu item in the group navigation page. Only shown for group administrators.
                                               // TRANS: %s is the nickname of the group.
                                               'title' => sprintf(_m('TOOLTIP','Add or edit %s logo'), $this->group->nickname)),
                                    // TRANS: Link text for link on user profile.
                                    _m('MENU','Logo'));
                $this->out->elementEnd('li');
            }
            if ($cur && $cur->hasRight(Right::DELETEGROUP)) {
                $this->out->elementStart('li', 'entity_delete');
                $df = new DeleteGroupForm($this->out, $this->group);
                $df->show();
                $this->out->elementEnd('li');
            }

            Event::handle('EndGroupActionsList', array($this, $this->group));
        }
        $this->out->elementEnd('ul');
        $this->out->elementEnd('div');
    }

    function show()
    {
        $this->out->elementStart('div', 'profile_block group_profile_block section');
        if (Event::handle('StartShowGroupProfileBlock', array($this->out, $this->group))) {
            parent::show();
            Event::handle('EndShowGroupProfileBlock', array($this->out, $this->group));
        }
        $this->out->elementEnd('div');
    }

    function showName()
    {
        parent::showName();
        $this->showAliases();
    }

    function showAliases()
    {
        $aliases = $this->group->getAliases();

        if (!empty($aliases)) {
            $this->out->elementStart('ul', 'group_aliases');
            foreach ($aliases as $alias) {
                $this->out->element('li', 'group_alias', $alias);
            }
            $this->out->elementEnd('ul');
        }
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Groups with the most members section
 *
 * 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  Widget
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

/**
 * Groups with the most members section
 *
 * @category Widget
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class GroupsByMembersSection extends GroupSection
{
    function getGroups()
    {
        $qry = 'SELECT user_group.*, count(*) as value ' .
          'FROM user_group JOIN group_member '.
          'ON user_group.id = group_member.group_id ' .
          'GROUP BY user_group.id,user_group.nickname,user_group.fullname,user_group.homepage,user_group.description,user_group.location,user_group.original_logo,user_group.homepage_logo,user_group.stream_logo,user_group.mini_logo,user_group.created,user_group.modified ' .
          'ORDER BY value DESC ';

        $limit = GROUPS_PER_SECTION;
        $offset = 0;

        if (common_config('db','type') == 'pgsql') {
            $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
        } else {
            $qry .= ' LIMIT ' . $offset . ', ' . $limit;
        }

        $group = Memcached_DataObject::cachedQuery('User_group',
                                                   $qry,
                                                   3600);
        return $group;
    }

    function title()
    {
        // TRANS: Title for groups with the most members section.
        return _('Popular groups');
    }

    function divId()
    {
        return 'top_groups_by_member';
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Groups with the most posts section
 *
 * 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  Widget
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

/**
 * Groups with the most posts section
 *
 * @category Widget
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class GroupsByPostsSection extends GroupSection
{
    function getGroups()
    {
        $qry = 'SELECT user_group.*, count(*) as value ' .
          'FROM user_group JOIN group_inbox '.
          'ON user_group.id = group_inbox.group_id ' .
          'GROUP BY user_group.id,user_group.nickname,user_group.fullname,user_group.homepage,user_group.description,user_group.location,user_group.original_logo,user_group.homepage_logo,user_group.stream_logo,user_group.mini_logo,user_group.created,user_group.modified ' .
          'ORDER BY value DESC ';

        $limit = GROUPS_PER_SECTION;
        $offset = 0;

        if (common_config('db','type') == 'pgsql') {
            $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
        } else {
            $qry .= ' LIMIT ' . $offset . ', ' . $limit;
        }

        $group = Memcached_DataObject::cachedQuery('User_group',
                                                   $qry,
                                                   3600);
        return $group;
    }

    function title()
    {
        // TRANS: Title for groups with the most posts section.
        return _('Active groups');
    }

    function divId()
    {
        return 'top_groups_by_post';
    }
}


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

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

define('GROUPS_PER_SECTION', 6);

/**
 * Base class for sections
 *
 * These are the widgets that show interesting data about a person
 * group, or site.
 *
 * @category Widget
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
abstract class GroupSection extends Section
{
    function showContent()
    {
        $profiles = $this->getGroups();

        if (!$profiles) {
            return false;
        }

        $cnt = 0;

        $this->out->elementStart('table');

        while ($profiles->fetch() && ++$cnt <= GROUPS_PER_SECTION) {
            $this->showGroup($profiles);
        }

        $this->out->elementEnd('table');

        return ($cnt > GROUPS_PER_SECTION);
    }

    function getGroups()
    {
        return null;
    }

    function showGroup($group)
    {
        $this->out->elementStart('tr');
        $this->out->elementStart('td');
        $this->out->elementStart('a', array('title' => $group->getBestName(),
                                            'href' => $group->homeUrl(),
                                            'rel' => 'contact group',
                                            'class' => 'h-card org nickname'));
        $logo = $group->stream_logo ?: User_group::defaultLogo(AVATAR_STREAM_SIZE);
        $this->out->element('img', array('src' => $logo,
                                         'width' => AVATAR_MINI_SIZE,
                                         'height' => AVATAR_MINI_SIZE,
                                         'class' => 'avatar u-photo',
                                         'alt' =>  $group->getBestName()));
        $this->out->text($group->nickname);
        $this->out->elementEnd('a');
        $this->out->elementEnd('td');
        if ($group->value) {
            $this->out->element('td', 'value', $group->value);
        }
        $this->out->elementEnd('tr');
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Menu for streams
 *
 * PHP version 5
 *
 * 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  Cache
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    // This check helps protect against security problems;
    // your code file can't be executed directly from the web.
    exit(1);
}

/**
 * Menu for streams you follow
 *
 * @category  General
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */
class GroupsNav extends MoreMenu
{
    protected $user;
    protected $groups;

    function __construct($action, $user)
    {
        parent::__construct($action);
        $this->user = $user;
        $this->groups = $user->getGroups();
    }

    function haveGroups()
    {
        return ($this->groups instanceof User_group && $this->groups->N > 0);
    }

    function tag()
    {
        return 'groups';
    }

    function getItems()
    {
        $items = array();

        while ($this->groups instanceof User_group && $this->groups->fetch()) {
            $items[] = array('placeholder',
                             array('nickname' => $this->groups->getNickname(),
                                   'mainpage' => $this->groups->homeUrl()),
                             $this->groups->getNickname(),
                             $this->groups->getBestName()
                            );
        }

        return $items;
    }

    function seeAllItem() {
        return array('usergroups',
                     array('nickname' => $this->user->nickname),
                     // TRANS: Link description for seeing all groups.
                     _('See all'),
                     // TRANS: Link title for seeing all groups.
                     _('See all groups you belong to.'));
    }

    function item($actionName, array $args, $label, $description, $id=null, $cls=null)
    {
        if ($actionName != 'placeholder') {
            return parent::item($actionName, $args, $label, $description, $id, $cls);
        }

        if (empty($id)) {
            $id = $this->menuItemID('showgroup', array('nickname' => $args['nickname']));
        }

        $url = $args['mainpage'];

        $this->out->menuItem($url,
                             $label,
                             $description,
                             $this->isCurrent($actionName, $args),
                             $id,
                             $cls);
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Personal tag cloud section
 *
 * 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  Widget
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

/**
 * Group tag cloud section
 *
 * @category Widget
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class GroupTagCloudSection extends TagCloudSection
{
    var $group = null;

    function __construct($out=null, $group=null)
    {
        parent::__construct($out);
        $this->group = $group;
    }

    function title()
    {
        // TRANS: Title for group tag cloud section.
        // TRANS: %s is a group name.
        return _('Tags');
    }

    function getTags()
    {
        $weightexpr = common_sql_weight('notice_tag.created', common_config('tag', 'dropoff'));
        // @fixme should we use the cutoff too? Doesn't help with indexing per-group.

        $names = $this->group->getAliases();

        $names = array_merge(array($this->group->nickname), $names);

        // XXX This is dumb.

        $quoted = array();

        foreach ($names as $name) {
            $quoted[] = "'$name'";
        }

        $namestring = implode(',', $quoted);

        $qry = 'SELECT notice_tag.tag, '.
          $weightexpr . ' as weight ' .
          'FROM notice_tag JOIN notice ' .
          'ON notice_tag.notice_id = notice.id ' .
          'JOIN group_inbox on group_inbox.notice_id = notice.id ' .
          'WHERE group_inbox.group_id = %d ' .
          'AND notice_tag.tag not in (%s) '.
          'GROUP BY notice_tag.tag ' .
          'ORDER BY weight DESC ';

        $limit = TAGS_PER_SECTION;
        $offset = 0;

        if (common_config('db','type') == 'pgsql') {
            $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
        } else {
            $qry .= ' LIMIT ' . $offset . ', ' . $limit;
        }

        $tag = Memcached_DataObject::cachedQuery('Notice_tag',
                                                 sprintf($qry,
                                                         $this->group->id,
                                                         $namestring),
                                                 3600);
        return $tag;
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Stub sub-menu for sections that need to go home
 *
 * PHP version 5
 *
 * 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  Menu
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    // This check helps protect against security problems;
    // your code file can't be executed directly from the web.
    exit(1);
}

/**
 * stub sub-menu for settings and stuff
 *
 * @category  General
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

class HomeStubNav extends Menu
{
    function getItems()
    {
        $items = array();
        if (Event::handle('StartHomeStubNavItems', array($this->action, &$items))) {
            $items[] = array('top',
                            array(),
                            // TRANS: Menu item in personal group navigation menu.
                            _m('MENU','Home'),
                            // TRANS: Menu item title in personal group navigation menu.
                            // TRANS: %s is a username.
                            _('Back to top'),
                            'nav_return_top');
            Event::handle('EndHomeStubNavItems', array($this->action, &$items));
        }
        return $items;
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Low-level generator for HTML
 *
 * 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  Output
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @author    Sarven Capadisli <csarven@status.net>
 * @copyright 2008 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

// Can include XHTML options but these are too fragile in practice.
define('PAGE_TYPE_PREFS', 'text/html');

/**
 * Low-level generator for HTML
 *
 * Abstracts some of the code necessary for HTML generation. Especially
 * has methods for generating HTML form elements. Note that these have
 * been created kind of haphazardly, not with an eye to making a general
 * HTML-creation class.
 *
 * @category Output
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @author   Sarven Capadisli <csarven@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      Action
 * @see      XMLOutputter
 */

class HTMLOutputter extends XMLOutputter
{
    protected $DTD = array('doctype' => 'html',
                           'spec'    => '-//W3C//DTD XHTML 1.0 Strict//EN',
                           'uri'     => 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
    /**
     * Constructor
     *
     * Just wraps the XMLOutputter constructor.
     *
     * @param string  $output URI to output to, default = stdout
     * @param boolean $indent Whether to indent output, default true
     */

    function __construct($output='php://output', $indent=null)
    {
        parent::__construct($output, $indent);
    }

    /**
     * Start an HTML document
     *
     * If $type isn't specified, will attempt to do content negotiation.
     *
     * Attempts to do content negotiation for language, also.
     *
     * @param string $type MIME type to use; default is to do negotation.
     *
     * @todo extract content negotiation code to an HTTP module or class.
     *
     * @return void
     */

    function startHTML($type=null)
    {
        if (!$type) {
            $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ?
              $_SERVER['HTTP_ACCEPT'] : null;

            // XXX: allow content negotiation for RDF, RSS, or XRDS

            $cp = common_accept_to_prefs($httpaccept);
            $sp = common_accept_to_prefs(PAGE_TYPE_PREFS);

            $type = common_negotiate_type($cp, $sp);

            if (!$type) {
                // TRANS: Client exception 406
                throw new ClientException(_('This page is not available in a '.
                                            'media type you accept'), 406);
            }
        }

        header('Content-Type: '.$type);

	// Output anti-framing headers to prevent clickjacking (respected by newer
        // browsers).
	if (common_config('javascript', 'bustframes')) {
            header('X-XSS-Protection: 1; mode=block'); // detect XSS Reflection attacks
            header('X-Frame-Options: SAMEORIGIN'); // no rendering if origin mismatch
        }

        $this->extraHeaders();
        if (preg_match("/.*\/.*xml/", $type)) {
            // Required for XML documents
            $this->startXML();
        }

        $this->writeDTD();

        $language = $this->getLanguage();

        $attrs = array(
            'xmlns' => 'http://www.w3.org/1999/xhtml',
            'xml:lang' => $language,
            'lang' => $language
        );

        if (Event::handle('StartHtmlElement', array($this, &$attrs))) {
            $this->elementStart('html', $attrs);
            Event::handle('EndHtmlElement', array($this, &$attrs));
        }
    }

    public function setDTD($doctype, $spec, $uri)
    {
        $this->DTD = array('doctype' => $doctype, 'spec' => $spec, 'uri' => $uri);
    }

    protected function writeDTD()
    {
        $this->xw->writeDTD($this->DTD['doctype'],
                            $this->DTD['spec'],
                            $this->DTD['uri']);
    }

    function getLanguage()
    {
        // FIXME: correct language for interface
        return common_language();
    }

    /**
    *  Ends an HTML document
    *
    *  @return void
    */
    function endHTML()
    {
        $this->elementEnd('html');
        $this->endXML();
    }

    /**
    *  To specify additional HTTP headers for the action
    *
    *  @return void
    */
    function extraHeaders()
    {
        // Needs to be overloaded
    }

    /**
     * Output an HTML text input element
     *
     * Despite the name, it is specifically for outputting a
     * text input element, not other <input> elements. It outputs
     * a cluster of elements, including a <label> and an associated
     * instructions span.
     *
     * If $attrs['type'] does not exist it will be set to 'text'.
     *
     * @param string $id           element ID, must be unique on page
     * @param string $label        text of label for the element
     * @param string $value        value of the element, default null
     * @param string $instructions instructions for valid input
     * @param string $name         name of the element; if null, the id will
     *                             be used
     * @param bool   $required     HTML5 required attribute (exclude when false)
     * @param array  $attrs        Initial attributes manually set in an array (overwritten by previous options)
     *
     * @todo add a $maxLength parameter
     * @todo add a $size parameter
     *
     * @return void
     */

    function input($id, $label, $value=null, $instructions=null, $name=null, $required=false, array $attrs=array())
    {
        $this->element('label', array('for' => $id), $label);
        if (!array_key_exists('type', $attrs)) {
            $attrs['type'] = 'text';
        }
        $attrs['id'] = $id;
        $attrs['name'] = is_null($name) ? $id : $name;
        if (array_key_exists('placeholder', $attrs) && (is_null($attrs['placeholder']) || $attrs['placeholder'] === '')) {
            // If placeholder is type-aware equal to '' or null, unset it as we apparently don't want a placeholder value
            unset($attrs['placeholder']);
        } else {
            // If the placeholder is set use it, or use the label as fallback.
            $attrs['placeholder'] = isset($attrs['placeholder']) ? $attrs['placeholder'] : $label;
        }

        if (!is_null($value)) { // value can be 0 or ''
            $attrs['value'] = $value;
        }
        if (!empty($required)) {
            $attrs['required'] = 'required';
        }
        $this->element('input', $attrs);
        if ($instructions) {
            $this->element('p', 'form_guide', $instructions);
        }
    }

    /**
     * output an HTML checkbox and associated elements
     *
     * Note that the value is default 'true' (the string), which can
     * be used by Action::boolean()
     *
     * @param string $id           element ID, must be unique on page
     * @param string $label        text of label for the element
     * @param string $checked      if the box is checked, default false
     * @param string $instructions instructions for valid input
     * @param string $value        value of the checkbox, default 'true'
     * @param string $disabled     show the checkbox disabled, default false
     *
     * @return void
     *
     * @todo add a $name parameter
     */

    function checkbox($id, $label, $checked=false, $instructions=null,
                      $value='true', $disabled=false)
    {
        $attrs = array('name' => $id,
                       'type' => 'checkbox',
                       'class' => 'checkbox',
                       'id' => $id);
        if ($value) {
            $attrs['value'] = $value;
        }
        if ($checked) {
            $attrs['checked'] = 'checked';
        }
        if ($disabled) {
            $attrs['disabled'] = 'true';
        }
        $this->element('input', $attrs);
        $this->text(' ');
        $this->element('label', array('class' => 'checkbox',
                                      'for' => $id),
                       $label);
        $this->text(' ');
        if ($instructions) {
            $this->element('p', 'form_guide', $instructions);
        }
    }

    /**
     * output an HTML combobox/select and associated elements
     *
     * $content is an array of key-value pairs for the dropdown, where
     * the key is the option value attribute and the value is the option
     * text. (Careful on the overuse of 'value' here.)
     *
     * @param string $id           element ID, must be unique on page
     * @param string $label        text of label for the element
     * @param array  $content      options array, value => text
     * @param string $instructions instructions for valid input
     * @param string $blank_select whether to have a blank entry, default false
     * @param string $selected     selected value, default null
     *
     * @return void
     *
     * @todo add a $name parameter
     */

    function dropdown($id, $label, $content, $instructions=null,
                      $blank_select=false, $selected=null)
    {
        $this->element('label', array('for' => $id), $label);
        $this->elementStart('select', array('id' => $id, 'name' => $id));
        if ($blank_select) {
            $this->element('option', array('value' => ''));
        }
        foreach ($content as $value => $option) {
            if ($value == $selected) {
                $this->element('option', array('value' => $value,
                                               'selected' => 'selected'),
                               $option);
            } else {
                $this->element('option', array('value' => $value), $option);
            }
        }
        $this->elementEnd('select');
        if ($instructions) {
            $this->element('p', 'form_guide', $instructions);
        }
    }

    /**
     * output an HTML hidden element
     *
     * $id is re-used as name
     *
     * @param string $id    element ID, must be unique on page
     * @param string $value hidden element value, default null
     * @param string $name  name, if different than ID
     *
     * @return void
     */

    function hidden($id, $value, $name=null)
    {
        $this->element('input', array('name' => $name ?: $id,
                                      'type' => 'hidden',
                                      'id' => $id,
                                      'value' => $value));
    }

    /**
     * output an HTML password input and associated elements
     *
     * @param string $id           element ID, must be unique on page
     * @param string $label        text of label for the element
     * @param string $instructions instructions for valid input
     *
     * @return void
     *
     * @todo add a $name parameter
     */

    function password($id, $label, $instructions=null)
    {
        $this->element('label', array('for' => $id), $label);
        $attrs = array('name' => $id,
                       'type' => 'password',
                       'class' => 'password',
                       'id' => $id);
        $this->element('input', $attrs);
        if ($instructions) {
            $this->element('p', 'form_guide', $instructions);
        }
    }

    /**
     * output an HTML submit input and associated elements
     *
     * @param string $id    element ID, must be unique on page
     * @param string $label text of the button
     * @param string $cls   class of the button, default 'submit'
     * @param string $name  name, if different than ID
     * @param string $title  title text for the submit button
     *
     * @return void
     *
     * @todo add a $name parameter
     */

    function submit($id, $label, $cls='submit', $name=null, $title=null)
    {
        $this->element('input', array('type' => 'submit',
                                      'id' => $id,
                                      'name'  => $name ?: $id,
                                      'class' => $cls,
                                      'value' => $label,
                                      'title' => $title));
    }

    /**
     * output a script (almost always javascript) tag
     *
     * @param string $src          relative or absolute script path
     * @param string $type         'type' attribute value of the tag
     *
     * @return void
     */
    function script($src, $type='text/javascript')
    {
        if (Event::handle('StartScriptElement', array($this,&$src,&$type))) {

            $url = parse_url($src);

            if (empty($url['scheme']) && empty($url['host']) && empty($url['query']) && empty($url['fragment'])) {

                // XXX: this seems like a big assumption

                if (strpos($src, 'plugins/') === 0 || strpos($src, 'local/') === 0) {

                    $src = common_path($src, GNUsocial::isHTTPS()) . '?version=' . GNUSOCIAL_VERSION;

                } else {

                    if (GNUsocial::isHTTPS()) {

                        $sslserver = common_config('javascript', 'sslserver');

                        if (empty($sslserver)) {
                            if (is_string(common_config('site', 'sslserver')) &&
                                mb_strlen(common_config('site', 'sslserver')) > 0) {
                                $server = common_config('site', 'sslserver');
                            } else if (common_config('site', 'server')) {
                                $server = common_config('site', 'server');
                            }
                            $path   = common_config('site', 'path') . '/js/';
                        } else {
                            $server = $sslserver;
                            $path   = common_config('javascript', 'sslpath');
                            if (empty($path)) {
                                $path = common_config('javascript', 'path');
                            }
                        }

                        $protocol = 'https';

                    } else {

                        $path = common_config('javascript', 'path');

                        if (empty($path)) {
                            $path = common_config('site', 'path') . '/js/';
                        }

                        $server = common_config('javascript', 'server');

                        if (empty($server)) {
                            $server = common_config('site', 'server');
                        }

                        $protocol = 'http';
                    }

                    if ($path[strlen($path)-1] != '/') {
                        $path .= '/';
                    }

                    if ($path[0] != '/') {
                        $path = '/'.$path;
                    }

                    $src = $protocol.'://'.$server.$path.$src . '?version=' . GNUSOCIAL_VERSION;
                }
            }

            $this->element('script', array('type' => $type,
                                           'src' => $src),
                           ' ');

            Event::handle('EndScriptElement', array($this,$src,$type));
        }
    }

    /**
     * output a script (almost always javascript) tag with inline
     * code.
     *
     * @param string $code         code to put in the script tag
     * @param string $type         'type' attribute value of the tag
     *
     * @return void
     */

    function inlineScript($code, $type='text/javascript')
    {
        if(Event::handle('StartInlineScriptElement', array($this,&$code,&$type))) {
            $this->elementStart('script', array('type' => $type));
            if($type == 'text/javascript') {
                $this->raw('/*<![CDATA[*/ '); // XHTML compat
            }
            $this->raw($code);
            if($type == 'text/javascript') {
                $this->raw(' /*]]>*/'); // XHTML compat
            }
            $this->elementEnd('script');
            Event::handle('EndInlineScriptElement', array($this,$code,$type));
        }
    }

    /**
     * output a css link
     *
     * @param string $src     relative path within the theme directory, or an absolute path
     * @param string $theme        'theme' that contains the stylesheet
     * @param string media         'media' attribute of the tag
     *
     * @return void
     */
    function cssLink($src,$theme=null,$media=null)
    {
        if(Event::handle('StartCssLinkElement', array($this,&$src,&$theme,&$media))) {
            $url = parse_url($src);
            if( empty($url['scheme']) && empty($url['host']) && empty($url['query']) && empty($url['fragment']))
            {
                if(file_exists(Theme::file($src,$theme))){
                   $src = Theme::path($src, $theme);
                }else{
                    $src = common_path($src, GNUsocial::isHTTPS());
                }
                $src.= '?version=' . GNUSOCIAL_VERSION;
            }
            $this->element('link', array('rel' => 'stylesheet',
                                    'type' => 'text/css',
                                    'href' => $src,
                                    'media' => $media));
            Event::handle('EndCssLinkElement', array($this,$src,$theme,$media));
        }
    }

    /**
     * output a style (almost always css) tag with inline
     * code.
     *
     * @param string $code         code to put in the style tag
     * @param string $type         'type' attribute value of the tag
     * @param string $media        'media' attribute value of the tag
     *
     * @return void
     */

    function style($code, $type = 'text/css', $media = null)
    {
        if(Event::handle('StartStyleElement', array($this,&$code,&$type,&$media))) {
            $this->elementStart('style', array('type' => $type, 'media' => $media));
            $this->raw($code);
            $this->elementEnd('style');
            Event::handle('EndStyleElement', array($this,$code,$type,$media));
        }
    }

    /**
     * output an HTML textarea and associated elements
     *
     * @param string $id           element ID, must be unique on page
     * @param string $label        text of label for the element
     * @param string $content      content of the textarea, default none
     * @param string $instructions instructions for valid input
     * @param string $name         name of textarea; if null, $id will be used
     * @param int    $cols         number of columns
     * @param int    $rows         number of rows
     * @param bool   $required     HTML5 required attribute (exclude when false)
     *
     * @return void
     */

    function textarea(
        $id,
        $label,
        $content      = null,
        $instructions = null,
        $name         = null,
        $cols         = null,
        $rows         = null,
        $required     = false
    ) {
        $this->element('label', array('for' => $id), $label);
        $attrs = array(
            'rows' => 3,
            'cols' => 40,
            'id' => $id
        );
        $attrs['name'] = is_null($name) ? $id : $name;

        if ($cols != null) {
            $attrs['cols'] = $cols;

        }
        if ($rows != null) {
            $attrs['rows'] = $rows;
        }
        $this->element(
            'textarea',
            $attrs,
            is_null($content) ? '' : $content
        );
        if ($instructions) {
            $this->element('p', 'form_guide', $instructions);
        }
    }

   /**
    * Internal script to autofocus the given element on page onload.
    *
    * @param string $id element ID, must refer to an existing element
    *
    * @return void
    *
    */
    function autofocus($id)
    {
        $this->inlineScript(
                   ' $(document).ready(function() {'.
                   ' var el = $("#' . $id . '");'.
                   ' if (el.length) { el.focus(); }'.
                   ' });');
    }
}


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

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

/**
 * Useful structure for HTTP responses
 *
 * We make HTTP calls in several places, and we have several different
 * ways of doing them. This class hides the specifics of what underlying
 * library (curl or PHP-HTTP or whatever) that's used.
 *
 * This extends the HTTP_Request2_Response class with methods to get info
 * about any followed redirects.
 *
 * Originally used the name 'HTTPResponse' to match earlier code, but
 * this conflicts with a class in in the PECL HTTP extension.
 *
 * @category HTTP
 * @package StatusNet
 * @author Evan Prodromou <evan@status.net>
 * @author Brion Vibber <brion@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 GNUsocial_HTTPResponse extends HTTP_Request2_Response
{
    function __construct(HTTP_Request2_Response $response, $url, $redirects=0)
    {
        foreach (get_object_vars($response) as $key => $val) {
            $this->$key = $val;
        }
        $this->url = strval($url);
        $this->redirectCount = intval($redirects);
    }

    /**
     * Get the count of redirects that have been followed, if any.
     * @return int
     */
    function getRedirectCount()
    {
        return $this->redirectCount;
    }

    /**
     * Gets the target URL, before any redirects. Use getEffectiveUrl() for final target.
     * @return string URL
     */
    function getUrl()
    {
        return $this->url;
    }

    /**
     * Check if the response is OK, generally a 200 or other 2xx status code.
     * @return bool
     */
    function isOk()
    {
        $status = $this->getStatus();
        return ($status >= 200 && $status < 300);
    }
}

/**
 * Utility class for doing HTTP client stuff
 *
 * We make HTTP calls in several places, and we have several different
 * ways of doing them. This class hides the specifics of what underlying
 * library (curl or PHP-HTTP or whatever) that's used.
 *
 * This extends the PEAR HTTP_Request2 package:
 * - sends StatusNet-specific User-Agent header
 * - 'follow_redirects' config option, defaulting on
 * - 'max_redirs' config option, defaulting to 10
 * - extended response class adds getRedirectCount() and getUrl() methods
 * - get() and post() convenience functions return body content directly
 *
 * @category HTTP
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @author   Brion Vibber <brion@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 HTTPClient extends HTTP_Request2
{

    function __construct($url=null, $method=self::METHOD_GET, $config=array())
    {
        $this->config['connect_timeout'] = common_config('http', 'connect_timeout') ?: $this->config['connect_timeout'];
        $this->config['max_redirs'] = 10;
        $this->config['follow_redirects'] = true;

        // We've had some issues with keepalive breaking with
        // HEAD requests, such as to youtube which seems to be
        // emitting chunked encoding info for an empty body
        // instead of not emitting anything. This may be a
        // bug on YouTube's end, but the upstream libray
        // ought to be investigated to see if we can handle
        // it gracefully in that case as well.
        $this->config['protocol_version'] = '1.0';

        // Default state of OpenSSL seems to have no trusted
        // SSL certificate authorities, which breaks hostname
        // verification and means we have a hard time communicating
        // with other sites' HTTPS interfaces.
        //
        // Turn off verification unless we've configured a CA bundle.
        if (common_config('http', 'ssl_cafile')) {
            $this->config['ssl_cafile'] = common_config('http', 'ssl_cafile');
        } else {
            $this->config['ssl_verify_peer'] = false;
        }

        // This means "verify the cert hostname against what we connect to", it does not
        // imply CA trust or anything like that. Just the hostname.
        $this->config['ssl_verify_host'] = common_config('http', 'ssl_verify_host');

        if (common_config('http', 'curl') && extension_loaded('curl')) {
            $this->config['adapter'] = 'HTTP_Request2_Adapter_Curl';
        }

        foreach (array('host', 'port', 'user', 'password', 'auth_scheme') as $cf) {
            $k = 'proxy_'.$cf;
            $v = common_config('http', $k);
            if (!empty($v)) {
                $this->config[$k] = $v;
            }
        }

        parent::__construct($url, $method, $config);
        $this->setHeader('User-Agent', self::userAgent());
    }

    /**
     * Convenience/back-compat instantiator
     * @return HTTPClient
     */
    public static function start()
    {
        return new HTTPClient();
    }

    /**
     * Quick static function to GET a URL
     */
    public static function quickGet($url, $accept=null, $params=array())
    {
        if (!empty($params)) {
            $params = http_build_query($params, null, '&');
            if (strpos($url, '?') === false) {
                $url .= '?' . $params;
            } else {
                $url .= '&' . $params;
            }
        }

        $client = new HTTPClient();
        if (!is_null($accept)) {
            $client->setHeader('Accept', $accept);
        }
        $response = $client->get($url);
        if (!$response->isOk()) {
            // TRANS: Exception. %s is the URL we tried to GET.
            throw new Exception(sprintf(_m('Could not GET URL %s.'), $url), $response->getStatus());
        }
        return $response->getBody();
    }

    public static function quickGetJson($url, $params=array())
    {
        $data = json_decode(self::quickGet($url, null, $params));
        if (is_null($data)) {
            common_debug('Could not decode JSON data from URL: '.$url);
            throw new ServerException('Could not decode JSON data from URL');
        }
        return $data;
    }

    /**
     * Convenience function to run a GET request.
     *
     * @return GNUsocial_HTTPResponse
     * @throws HTTP_Request2_Exception
     */
    public function get($url, $headers=array())
    {
        return $this->doRequest($url, self::METHOD_GET, $headers);
    }

    /**
     * Convenience function to run a HEAD request.
     *
     * NOTE: Will probably turn into a GET request if you let it follow redirects!
     *       That option is only there to be flexible and may be removed in the future!
     *
     * @return GNUsocial_HTTPResponse
     * @throws HTTP_Request2_Exception
     */
    public function head($url, $headers=array(), $follow_redirects=false)
    {
        // Save the configured value for follow_redirects
        $old_follow = $this->config['follow_redirects'];
        try {
            // Temporarily (possibly) override the follow_redirects setting
            $this->config['follow_redirects'] = $follow_redirects;
            return $this->doRequest($url, self::METHOD_HEAD, $headers);
        } catch (Exception $e) {
            // Let the exception go on its merry way.
            throw $e;
        } finally {
            // reset to the old value
            $this->config['follow_redirects'] = $old_follow;
        }
        //we've either returned or thrown exception here
    }

    /**
     * Convenience function to POST form data.
     *
     * @param string $url
     * @param array $headers optional associative array of HTTP headers
     * @param array $data optional associative array or blob of form data to submit
     * @return GNUsocial_HTTPResponse
     * @throws HTTP_Request2_Exception
     */
    public function post($url, $headers=array(), $data=array())
    {
        if ($data) {
            $this->addPostParameter($data);
        }
        return $this->doRequest($url, self::METHOD_POST, $headers);
    }

    /**
     * @return GNUsocial_HTTPResponse
     * @throws HTTP_Request2_Exception
     */
    protected function doRequest($url, $method, $headers)
    {
        $this->setUrl($url);

        // Workaround for HTTP_Request2 not setting up SNI in socket contexts;
        // This fixes cert validation for SSL virtual hosts using SNI.
        // Requires PHP 5.3.2 or later and OpenSSL with SNI support.
        if ($this->url->getScheme() == 'https' && defined('OPENSSL_TLSEXT_SERVER_NAME')) {
            $this->config['ssl_SNI_enabled'] = true;
            $this->config['ssl_SNI_server_name'] = $this->url->getHost();
        }

        $this->setMethod($method);
        if ($headers) {
            foreach ($headers as $header) {
                $this->setHeader($header);
            }
        }
        $response = $this->send();
        if (is_null($response)) {
            // TRANS: Failed to retrieve a remote web resource, %s is the target URL.
            throw new NoHttpResponseException($url);
        }
        return $response;
    }

    protected function log($level, $detail) {
        $method = $this->getMethod();
        $url = $this->getUrl();
        common_log($level, __CLASS__ . ": HTTP $method $url - $detail");
    }

    /**
     * Pulls up GNU Social's customized user-agent string, so services
     * we hit can track down the responsible software.
     *
     * @return string
     */
    static public function userAgent()
    {
        return GNUSOCIAL_ENGINE . '/' . GNUSOCIAL_VERSION
                . ' (' . GNUSOCIAL_CODENAME . ')';
    }

    /**
     * Actually performs the HTTP request and returns a
     * GNUsocial_HTTPResponse object with response body and header info.
     *
     * Wraps around parent send() to add logging and redirection processing.
     *
     * @return GNUsocial_HTTPResponse
     * @throw HTTP_Request2_Exception
     */
    public function send()
    {
        $maxRedirs = intval($this->config['max_redirs']);
        if (empty($this->config['max_redirs'])) {
            $maxRedirs = 0;
        }
        $redirs = 0;
        $redirUrls = array();
        do {
            try {
                $response = parent::send();
            } catch (Exception $e) {
                $this->log(LOG_ERR, $e->getMessage());
                throw $e;
            }
            $code = $response->getStatus();
            $effectiveUrl = $response->getEffectiveUrl();
            $redirUrls[] = $effectiveUrl;
            $response->redirUrls = $redirUrls;
            if ($code >= 200 && $code < 300) {
                $reason = $response->getReasonPhrase();
                $this->log(LOG_INFO, "$code $reason");
            } elseif ($code >= 300 && $code < 400) {
                $url = $this->getUrl();
                $target = $response->getHeader('Location');

                if (++$redirs >= $maxRedirs) {
                    common_log(LOG_ERR, __CLASS__ . ": Too many redirects: skipping $code redirect from $url to $target");
                    break;
                }
                try {
                    $this->setUrl($target);
                    $this->setHeader('Referer', $url);
                    common_log(LOG_INFO, __CLASS__ . ": Following $code redirect from $url to $target");
                    continue;
                } catch (HTTP_Request2_Exception $e) {
                    common_log(LOG_ERR, __CLASS__ . ": Invalid $code redirect from $url to $target");
                }
            } else {
                $reason = $response->getReasonPhrase();
                $this->log(LOG_ERR, "$code $reason");
            }
            break;
        } while ($maxRedirs);
        return new GNUsocial_HTTPResponse($response, $this->getUrl(), $redirs);
    }
}