code:
<?php
/*
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2008, 2009, 2010 StatusNet, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

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

require_once(INSTALLDIR . '/lib/ui/channel.php');

class Command
{
    protected $scoped = null;   // The Profile of the user performing the command
    var $user = null;

    function __construct($user=null)
    {
        $this->scoped = empty($user)?null:$user->getProfile();
        $this->user = $user;
    }

    /**
     * Execute the command and send success or error results
     * back via the given communications channel.
     *
     * @param Channel
     */
    public function execute($channel)
    {
        try {
            $this->handle($channel);
        } catch (CommandException $e) {
            $channel->error($this->user, $e->getMessage());
        } catch (Exception $e) {
            common_log(LOG_ERR, "Error handling " . get_class($this) . ": " . $e->getMessage());
            $channel->error($this->user, $e->getMessage());
        }
    }

    /**
     * Override this with the meat!
     *
     * An error to send back to the user may be sent by throwing
     * a CommandException with a formatted message.
     *
     * @param Channel
     * @throws CommandException
     */
    function handle($channel)
    {
        return false;
    }

    /**
     * Look up a notice from an argument, by poster's name to get last post
     * or notice_id prefixed with #.
     *
     * @return Notice
     * @throws CommandException
     */
    function getNotice($arg)
    {
        $notice = null;
        if (Event::handle('StartCommandGetNotice', array($this, $arg, &$notice))) {
            if(substr($this->other,0,1)=='#'){
                // A specific notice_id #123

                $notice = Notice::getKV(substr($arg,1));
                if (!$notice) {
                    // TRANS: Command exception text shown when a notice ID is requested that does not exist.
                    throw new CommandException(_('Notice with that id does not exist.'));
                }
            }

            if (Validate::uri($this->other)) {
                // A specific notice by URI lookup
                $notice = Notice::getKV('uri', $arg);
            }

            if (!$notice) {
                // Local or remote profile name to get their last notice.
                // May throw an exception and report 'no such user'
                $recipient = $this->getProfile($arg);

                $notice = $recipient->getCurrentNotice();
                if (!$notice) {
                    // TRANS: Command exception text shown when a last user notice is requested and it does not exist.
                    throw new CommandException(_('User has no last notice.'));
                }
            }
        }
        Event::handle('EndCommandGetNotice', array($this, $arg, &$notice));
        if (!$notice) {
            // TRANS: Command exception text shown when a notice ID is requested that does not exist.
            throw new CommandException(_('Notice with that id does not exist.'));
        }
        return $notice;
    }

    /**
     * Look up a local or remote profile by nickname.
     *
     * @return Profile
     * @throws CommandException
     */
    function getProfile($arg)
    {
        $profile = null;
        if (Event::handle('StartCommandGetProfile', array($this, $arg, &$profile))) {
            $profile =
              common_relative_profile($this->user, common_canonical_nickname($arg));
        }
        Event::handle('EndCommandGetProfile', array($this, $arg, &$profile));
        if (!$profile) {
            // TRANS: Message given requesting a profile for a non-existing user.
            // TRANS: %s is the nickname of the user for which the profile could not be found.
            throw new CommandException(sprintf(_('Could not find a user with nickname %s.'), $arg));
        }
        return $profile;
    }

    /**
     * Get a local user by name
     * @return User
     * @throws CommandException
     */
    function getUser($arg)
    {
        $user = null;
        if (Event::handle('StartCommandGetUser', array($this, $arg, &$user))) {
            $user = User::getKV('nickname', Nickname::normalize($arg));
        }
        Event::handle('EndCommandGetUser', array($this, $arg, &$user));
        if (!$user){
            // TRANS: Message given getting a non-existing user.
            // TRANS: %s is the nickname of the user that could not be found.
            throw new CommandException(sprintf(_('Could not find a local user with nickname %s.'),
                               $arg));
        }
        return $user;
    }

    /**
     * Get a local or remote group by name.
     * @return User_group
     * @throws CommandException
     */
    function getGroup($arg)
    {
        $group = null;
        if (Event::handle('StartCommandGetGroup', array($this, $arg, &$group))) {
            $group = User_group::getForNickname($arg, $this->user->getProfile());
        }
        Event::handle('EndCommandGetGroup', array($this, $arg, &$group));
        if (!$group) {
            // TRANS: Command exception text shown when a group is requested that does not exist.
            throw new CommandException(_('No such group.'));
        }
        return $group;
    }
}

class CommandException extends Exception
{
}

class UnimplementedCommand extends Command
{
    function handle($channel)
    {
        // TRANS: Error text shown when an unimplemented command is given.
        $channel->error($this->user, _('Sorry, this command is not yet implemented.'));
    }
}

class TrackingCommand extends UnimplementedCommand
{
}

class TrackOffCommand extends UnimplementedCommand
{
}

class TrackCommand extends UnimplementedCommand
{
    var $word = null;
    function __construct($user, $word)
    {
        parent::__construct($user);
        $this->word = $word;
    }
}

class UntrackCommand extends UnimplementedCommand
{
    var $word = null;
    function __construct($user, $word)
    {
        parent::__construct($user);
        $this->word = $word;
    }
}

class NudgeCommand extends Command
{
    var $other = null;
    function __construct($user, $other)
    {
        parent::__construct($user);
        $this->other = $other;
    }

    function handle($channel)
    {
        $recipient = $this->getUser($this->other);
        if ($recipient->id == $this->user->id) {
            // TRANS: Command exception text shown when a user tries to nudge themselves.
            throw new CommandException(_('It does not make a lot of sense to nudge yourself!'));
        } else {
            if ($recipient->email && $recipient->emailnotifynudge) {
                mail_notify_nudge($this->user, $recipient);
            }
            // XXX: notify by IM
            // XXX: notify by SMS
            // TRANS: Message given having nudged another user.
            // TRANS: %s is the nickname of the user that was nudged.
            $channel->output($this->user, sprintf(_('Nudge sent to %s.'),
                           $recipient->nickname));
        }
    }
}

class InviteCommand extends UnimplementedCommand
{
    var $other = null;
    function __construct($user, $other)
    {
        parent::__construct($user);
        $this->other = $other;
    }
}

class StatsCommand extends Command
{
    function handle($channel)
    {
        $profile = $this->user->getProfile();

        $subs_count   = $profile->subscriptionCount();
        $subbed_count = $profile->subscriberCount();
        $notice_count = $profile->noticeCount();

        // TRANS: User statistics text.
        // TRANS: %1$s is the number of other user the user is subscribed to.
        // TRANS: %2$s is the number of users that are subscribed to the user.
        // TRANS: %3$s is the number of notices the user has sent.
        $channel->output($this->user, sprintf(_("Subscriptions: %1\$s\n".
                                   "Subscribers: %2\$s\n".
                                   "Notices: %3\$s"),
                                 $subs_count,
                                 $subbed_count,
                                 $notice_count));
    }
}

class JoinCommand extends Command
{
    var $other = null;

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

    function handle($channel)
    {
        $group = $this->getGroup($this->other);
        $cur   = $this->user;

        if ($cur->isMember($group)) {
            // TRANS: Error text shown a user tries to join a group they already are a member of.
            $channel->error($cur, _('You are already a member of that group.'));
            return;
        }
        if (Group_block::isBlocked($group, $cur->getProfile())) {
            // TRANS: Error text shown when a user tries to join a group they are blocked from joining.
          $channel->error($cur, _('You have been blocked from that group by the admin.'));
            return;
        }

        try {
            $cur->joinGroup($group);
        } catch (Exception $e) {
            // TRANS: Message given having failed to add a user to a group.
            // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group.
            $channel->error($cur, sprintf(_('Could not join user %1$s to group %2$s.'),
                                          $cur->nickname, $group->nickname));
            return;
        }

        // TRANS: Message given having added a user to a group.
        // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group.
        $channel->output($cur, sprintf(_('%1$s joined group %2$s.'),
                                              $cur->nickname,
                                              $group->nickname));
    }
}

class DropCommand extends Command
{
    var $other = null;

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

    function handle($channel)
    {
        $group = $this->getGroup($this->other);
        $cur   = $this->user;

        if (!$group) {
            // TRANS: Error text shown when trying to leave a group that does not exist.
            $channel->error($cur, _('No such group.'));
            return;
        }

        if (!$cur->isMember($group)) {
            // TRANS: Error text shown when trying to leave an existing group the user is not a member of.
            $channel->error($cur, _('You are not a member of that group.'));
            return;
        }

        try {
            $cur->leaveGroup($group);
        } catch (Exception $e) {
            // TRANS: Message given having failed to remove a user from a group.
            // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group.
            $channel->error($cur, sprintf(_('Could not remove user %1$s from group %2$s.'),
                                          $cur->nickname, $group->nickname));
            return;
        }

        // TRANS: Message given having removed a user from a group.
        // TRANS: %1$s is the nickname of the user, %2$s is the nickname of the group.
        $channel->output($cur, sprintf(_('%1$s left group %2$s.'),
                                              $cur->nickname,
                                              $group->nickname));
    }
}

class TagCommand extends Command
{
    var $other = null;
    var $tags = null;
    function __construct($user, $other, $tags)
    {
        parent::__construct($user);
        $this->other = $other;
        $this->tags = $tags;
    }

    function handle($channel)
    {
        $profile = $this->getProfile($this->other);
        $cur     = $this->user->getProfile();

        if (!$profile) {
            // TRANS: Client error displayed trying to perform an action related to a non-existing profile.
            $channel->error($cur, _('No such profile.'));
            return;
        }
        if (!$cur->canTag($profile)) {
            // TRANS: Error displayed when trying to tag a user that cannot be tagged.
            $channel->error($cur, _('You cannot tag this user.'));
            return;
        }

        $privs = array();
        $tags = preg_split('/[\s,]+/', $this->tags);
        $clean_tags = array();

        foreach ($tags as $tag) {
            $private = @$tag[0] === '.';
            $tag = $clean_tags[] = common_canonical_tag($tag);

            if (!common_valid_profile_tag($tag)) {
                // TRANS: Error displayed if a given tag is invalid.
                // TRANS: %s is the invalid tag.
                $channel->error($cur, sprintf(_('Invalid tag: "%s".'), $tag));
                return;
            }
            $privs[$tag] = $private;
        }

        try {
            foreach ($clean_tags as $tag) {
                Profile_tag::setTag($cur->id, $profile->id, $tag, null, $privs[$tag]);
            }
        } catch (Exception $e) {
            // TRANS: Error displayed if tagging a user fails.
            // TRANS: %1$s is the tagged user, %2$s is the error message (no punctuation).
            $channel->error($cur, sprintf(_('Error tagging %1$s: %2$s'),
                                          $profile->nickname, $e->getMessage()));
            return;
        }

        // TRANS: Succes message displayed if tagging a user succeeds.
        // TRANS: %1$s is the tagged user's nickname, %2$s is a list of tags.
        // TRANS: Plural is decided based on the number of tags added (not part of message).
        $channel->output($cur, sprintf(_m('%1$s was tagged %2$s',
                                          '%1$s was tagged %2$s',
                                          count($clean_tags)),
                                       $profile->nickname,
                                       // TRANS: Separator for list of tags.
                                       implode(_(', '), $clean_tags)));
    }
}

class UntagCommand extends TagCommand
{
    function handle($channel)
    {
        $profile = $this->getProfile($this->other);
        $cur     = $this->user->getProfile();

        if (!$profile) {
            // TRANS: Client error displayed trying to perform an action related to a non-existing profile.
            $channel->error($cur, _('No such profile.'));
            return;
        }
        if (!$cur->canTag($profile)) {
            // TRANS: Error displayed when trying to tag a user that cannot be tagged.
            $channel->error($cur, _('You cannot tag this user.'));
            return;
        }

        $tags = array_map('common_canonical_tag', preg_split('/[\s,]+/', $this->tags));

        foreach ($tags as $tag) {
            if (!common_valid_profile_tag($tag)) {
                // TRANS: Error displayed if a given tag is invalid.
                // TRANS: %s is the invalid tag.
                $channel->error($cur, sprintf(_('Invalid tag: "%s"'), $tag));
                return;
            }
        }

        try {
            foreach ($tags as $tag) {
                Profile_tag::unTag($cur->id, $profile->id, $tag);
            }
        } catch (Exception $e) {
            // TRANS: Error displayed if untagging a user fails.
            // TRANS: %1$s is the untagged user, %2$s is the error message (no punctuation).
            $channel->error($cur, sprintf(_('Error untagging %1$s: %2$s'),
                                          $profile->nickname, $e->getMessage()));
            return;
        }

        // TRANS: Succes message displayed if untagging a user succeeds.
        // TRANS: %1$s is the untagged user's nickname, %2$s is a list of tags.
        // TRANS: Plural is decided based on the number of tags removed (not part of message).
        $channel->output($cur, sprintf(_m('The following tag was removed from user %1$s: %2$s.',
                                         'The following tags were removed from user %1$s: %2$s.',
                                         count($tags)),
                                       $profile->nickname,
                                       // TRANS: Separator for list of tags.
                                       implode(_(', '), $tags)));
    }
}

class WhoisCommand extends Command
{
    var $other = null;
    function __construct($user, $other)
    {
        parent::__construct($user);
        $this->other = $other;
    }

    function handle($channel)
    {
        $recipient = $this->getProfile($this->other);

        // TRANS: Whois output.
        // TRANS: %1$s nickname of the queried user, %2$s is their profile URL.
        $whois = sprintf(_m('WHOIS',"%1\$s (%2\$s)"), $recipient->nickname,
                         $recipient->profileurl);
        if ($recipient->fullname) {
            // TRANS: Whois output. %s is the full name of the queried user.
            $whois .= "\n" . sprintf(_('Fullname: %s'), $recipient->fullname);
        }
        if ($recipient->location) {
            // TRANS: Whois output. %s is the location of the queried user.
            $whois .= "\n" . sprintf(_('Location: %s'), $recipient->location);
        }
        if ($recipient->homepage) {
            // TRANS: Whois output. %s is the homepage of the queried user.
            $whois .= "\n" . sprintf(_('Homepage: %s'), $recipient->homepage);
        }
        if ($recipient->bio) {
            // TRANS: Whois output. %s is the bio information of the queried user.
            $whois .= "\n" . sprintf(_('About: %s'), $recipient->bio);
        }
        $channel->output($this->user, $whois);
    }
}

class ReplyCommand extends Command
{
    var $other = null;
    var $text = null;
    function __construct($user, $other, $text)
    {
        parent::__construct($user);
        $this->other = $other;
        $this->text = $text;
    }

    function handle($channel)
    {
        $notice = $this->getNotice($this->other);
        $recipient = $notice->getProfile();

        $len = mb_strlen($this->text);

        if ($len == 0) {
            // TRANS: Command exception text shown when trying to reply to a notice without providing content for the reply.
            $channel->error($this->user, _('No content!'));
            return;
        }

        $this->text = $this->user->shortenLinks($this->text);

        if (Notice::contentTooLong($this->text)) {
            // XXX: i18n. Needs plural support.
            // TRANS: Message given if content of a notice for a reply is too long. %1$d is used for plural.
            // TRANS: %1$d is the maximum number of characters, %2$d is the number of submitted characters.
            $channel->error($this->user, sprintf(_m('Notice too long - maximum is %1$d character, you sent %2$d.',
                                                    'Notice too long - maximum is %1$d characters, you sent %2$d.',
                                                    Notice::maxContent()),
                                                 Notice::maxContent(), mb_strlen($this->text)));
            return;
        }

        $notice = Notice::saveNew($this->user->id, $this->text, $channel->source(),
                                  array('reply_to' => $notice->id));

        if ($notice) {
            // TRANS: Text shown having sent a reply to a notice successfully.
            // TRANS: %s is the nickname of the user of the notice the reply was sent to.
            $channel->output($this->user, sprintf(_('Reply to %s sent.'), $recipient->nickname));
        } else {
            // TRANS: Error text shown when a reply to a notice fails with an unknown reason.
            $channel->error($this->user, _('Error saving notice.'));
        }

    }
}

class GetCommand extends Command
{
    var $other = null;

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

    function handle($channel)
    {
        $target = $this->getProfile($this->other);

        $notice = $target->getCurrentNotice();
        if (!$notice) {
            // TRANS: Error text shown when a last user notice is requested and it does not exist.
            $channel->error($this->user, _('User has no last notice.'));
            return;
        }
        $notice_content = $notice->content;

        $channel->output($this->user, $target->nickname . ": " . $notice_content);
    }
}

class SubCommand extends Command
{
    var $other = null;

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

    function handle($channel)
    {

        if (!$this->other) {
            // TRANS: Error text shown when no username was provided when issuing a subscribe command.
            $channel->error($this->user, _('Specify the name of the user to subscribe to.'));
            return;
        }

        $target = $this->getProfile($this->other);

        try {
            Subscription::start($this->user->getProfile(), $target);
            // TRANS: Text shown after having subscribed to another user successfully.
            // TRANS: %s is the name of the user the subscription was requested for.
            $channel->output($this->user, sprintf(_('Subscribed to %s.'), $this->other));
        } catch (Exception $e) {
            $channel->error($this->user, $e->getMessage());
        }
    }
}

class UnsubCommand extends Command
{
    var $other = null;

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

    function handle($channel)
    {
        if(!$this->other) {
            // TRANS: Error text shown when no username was provided when issuing an unsubscribe command.
            $channel->error($this->user, _('Specify the name of the user to unsubscribe from.'));
            return;
        }

        $target = $this->getProfile($this->other);

        try {
            Subscription::cancel($this->user->getProfile(), $target);
            // TRANS: Text shown after having unsubscribed from another user successfully.
            // TRANS: %s is the name of the user the unsubscription was requested for.
            $channel->output($this->user, sprintf(_('Unsubscribed from %s.'), $this->other));
        } catch (Exception $e) {
            $channel->error($this->user, $e->getMessage());
        }
    }
}

class OffCommand extends Command
{
    var $other = null;

    function __construct($user, $other=null)
    {
        parent::__construct($user);
        $this->other = $other;
    }
    function handle($channel)
    {
        if ($this->other) {
            // TRANS: Error text shown when issuing the command "off" with a setting which has not yet been implemented.
            $channel->error($this->user, _("Command not yet implemented."));
        } else {
            if ($channel->off($this->user)) {
                // TRANS: Text shown when issuing the command "off" successfully.
                $channel->output($this->user, _('Notification off.'));
            } else {
                // TRANS: Error text shown when the command "off" fails for an unknown reason.
                $channel->error($this->user, _('Can\'t turn off notification.'));
            }
        }
    }
}

class OnCommand extends Command
{
    var $other = null;
    function __construct($user, $other=null)
    {
        parent::__construct($user);
        $this->other = $other;
    }

    function handle($channel)
    {
        if ($this->other) {
            // TRANS: Error text shown when issuing the command "on" with a setting which has not yet been implemented.
            $channel->error($this->user, _("Command not yet implemented."));
        } else {
            if ($channel->on($this->user)) {
                // TRANS: Text shown when issuing the command "on" successfully.
                $channel->output($this->user, _('Notification on.'));
            } else {
                // TRANS: Error text shown when the command "on" fails for an unknown reason.
                $channel->error($this->user, _('Can\'t turn on notification.'));
            }
        }
    }
}

class LoginCommand extends Command
{
    function handle($channel)
    {
        $disabled = common_config('logincommand','disabled');
        $disabled = isset($disabled) && $disabled;
        if($disabled) {
            // TRANS: Error text shown when issuing the login command while login is disabled.
            $channel->error($this->user, _('Login command is disabled.'));
            return;
        }

        try {
            $login_token = Login_token::makeNew($this->user);
        } catch (Exception $e) {
            $channel->error($this->user, $e->getMessage());
        }

        $channel->output($this->user,
            // TRANS: Text shown after issuing the login command successfully.
            // TRANS: %s is a logon link..
            sprintf(_('This link is useable only once and is valid for only 2 minutes: %s.'),
                    common_local_url('otp',
                        array('user_id' => $login_token->user_id, 'token' => $login_token->token))));
    }
}

class LoseCommand extends Command
{
    var $other = null;

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

    function execute($channel)
    {
        if(!$this->other) {
            // TRANS: Error text shown when no username was provided when issuing the command.
            $channel->error($this->user, _('Specify the name of the user to unsubscribe from.'));
            return;
        }

        $result = Subscription::cancel($this->getProfile($this->other), $this->user->getProfile());

        if ($result) {
            // TRANS: Text shown after issuing the lose command successfully (stop another user from following the current user).
            // TRANS: %s is the name of the user the unsubscription was requested for.
            $channel->output($this->user, sprintf(_('Unsubscribed %s.'), $this->other));
        } else {
            $channel->error($this->user, $result);
        }
    }
}

class SubscriptionsCommand extends Command
{
    function handle($channel)
    {
        $profile = $this->user->getSubscribed(0);
        $nicknames=array();
        while ($profile->fetch()) {
            $nicknames[]=$profile->nickname;
        }
        if(count($nicknames)==0){
            // TRANS: Text shown after requesting other users a user is subscribed to without having any subscriptions.
            $out=_('You are not subscribed to anyone.');
        }else{
            // TRANS: Text shown after requesting other users a user is subscribed to.
            // TRANS: This message supports plural forms. This message is followed by a
            // TRANS: hard coded space and a comma separated list of subscribed users.
            $out = _m('You are subscribed to this person:',
                'You are subscribed to these people:',
                count($nicknames));
            $out .= ' ';
            $out .= implode(', ',$nicknames);
        }
        $channel->output($this->user,$out);
    }
}

class SubscribersCommand extends Command
{
    function handle($channel)
    {
        $profile = $this->user->getSubscribers();
        $nicknames=array();
        while ($profile->fetch()) {
            $nicknames[]=$profile->nickname;
        }
        if(count($nicknames)==0){
            // TRANS: Text shown after requesting other users that are subscribed to a user
            // TRANS: (followers) without having any subscribers.
            $out=_('No one is subscribed to you.');
        }else{
            // TRANS: Text shown after requesting other users that are subscribed to a user (followers).
            // TRANS: This message supports plural forms. This message is followed by a
            // TRANS: hard coded space and a comma separated list of subscribing users.
            $out = _m('This person is subscribed to you:',
                'These people are subscribed to you:',
                count($nicknames));
            $out .= ' ';
            $out .= implode(', ',$nicknames);
        }
        $channel->output($this->user,$out);
    }
}

class GroupsCommand extends Command
{
    function handle($channel)
    {
        $group = $this->user->getGroups();
        $groups=array();
        while ($group instanceof User_group && $group->fetch()) {
            $groups[]=$group->nickname;
        }
        if(count($groups)==0){
            // TRANS: Text shown after requesting groups a user is subscribed to without having
            // TRANS: any group subscriptions.
            $out=_('You are not a member of any groups.');
        }else{
            // TRANS: Text shown after requesting groups a user is subscribed to.
            // TRANS: This message supports plural forms. This message is followed by a
            // TRANS: hard coded space and a comma separated list of subscribed groups.
            $out = _m('You are a member of this group:',
                'You are a member of these groups:',
                count($nicknames));
            $out.=implode(', ',$groups);
        }
        $channel->output($this->user,$out);
    }
}

class HelpCommand extends Command
{
    function handle($channel)
    {
        // TRANS: Header line of help text for commands.
        $out = array(_m('COMMANDHELP', "Commands:"));
        $commands = array(// TRANS: Help message for IM/SMS command "on".
                          "on" => _m('COMMANDHELP', "turn on notifications"),
                          // TRANS: Help message for IM/SMS command "off".
                          "off" => _m('COMMANDHELP', "turn off notifications"),
                          // TRANS: Help message for IM/SMS command "help".
                          "help" => _m('COMMANDHELP', "show this help"),
                          // TRANS: Help message for IM/SMS command "follow <nickname>".
                          "follow <nickname>" => _m('COMMANDHELP', "subscribe to user"),
                          // TRANS: Help message for IM/SMS command "groups".
                          "groups" => _m('COMMANDHELP', "lists the groups you have joined"),
                          // TRANS: Help message for IM/SMS command "tag".
                          "tag <nickname> <tags>" => _m('COMMANDHELP',"tag a user"),
                          // TRANS: Help message for IM/SMS command "untag".
                          "untag <nickname> <tags>" => _m('COMMANDHELP',"untag a user"),
                          // TRANS: Help message for IM/SMS command "subscriptions".
                          "subscriptions" => _m('COMMANDHELP', "list the people you follow"),
                          // TRANS: Help message for IM/SMS command "subscribers".
                          "subscribers" => _m('COMMANDHELP', "list the people that follow you"),
                          // TRANS: Help message for IM/SMS command "leave <nickname>".
                          "leave <nickname>" => _m('COMMANDHELP', "unsubscribe from user"),
                          // TRANS: Help message for IM/SMS command "d <nickname> <text>".
                          "d <nickname> <text>" => _m('COMMANDHELP', "direct message to user"),
                          // TRANS: Help message for IM/SMS command "get <nickname>".
                          "get <nickname>" => _m('COMMANDHELP', "get last notice from user"),
                          // TRANS: Help message for IM/SMS command "whois <nickname>".
                          "whois <nickname>" => _m('COMMANDHELP', "get profile info on user"),
                          // TRANS: Help message for IM/SMS command "lose <nickname>".
                          "lose <nickname>" => _m('COMMANDHELP', "force user to stop following you"),
                          // TRANS: Help message for IM/SMS command "reply #<notice_id>".
                          "reply #<notice_id>" => _m('COMMANDHELP', "reply to notice with a given id"),
                          // TRANS: Help message for IM/SMS command "reply <nickname>".
                          "reply <nickname>" => _m('COMMANDHELP', "reply to the last notice from user"),
                          // TRANS: Help message for IM/SMS command "join <group>".
                          "join <group>" => _m('COMMANDHELP', "join group"),
                          // TRANS: Help message for IM/SMS command "login".
                          "login" => _m('COMMANDHELP', "Get a link to login to the web interface"),
                          // TRANS: Help message for IM/SMS command "drop <group>".
                          "drop <group>" => _m('COMMANDHELP', "leave group"),
                          // TRANS: Help message for IM/SMS command "stats".
                          "stats" => _m('COMMANDHELP', "get your stats"),
                          // TRANS: Help message for IM/SMS command "stop".
                          "stop" => _m('COMMANDHELP', "same as 'off'"),
                          // TRANS: Help message for IM/SMS command "quit".
                          "quit" => _m('COMMANDHELP', "same as 'off'"),
                          // TRANS: Help message for IM/SMS command "sub <nickname>".
                          "sub <nickname>" => _m('COMMANDHELP', "same as 'follow'"),
                          // TRANS: Help message for IM/SMS command "unsub <nickname>".
                          "unsub <nickname>" => _m('COMMANDHELP', "same as 'leave'"),
                          // TRANS: Help message for IM/SMS command "last <nickname>".
                          "last <nickname>" => _m('COMMANDHELP', "same as 'get'"),
                          // TRANS: Help message for IM/SMS command "on <nickname>".
                          "on <nickname>" => _m('COMMANDHELP', "not yet implemented."),
                          // TRANS: Help message for IM/SMS command "off <nickname>".
                          "off <nickname>" => _m('COMMANDHELP', "not yet implemented."),
                          // TRANS: Help message for IM/SMS command "nudge <nickname>".
                          "nudge <nickname>" => _m('COMMANDHELP', "remind a user to update."),
                          // TRANS: Help message for IM/SMS command "invite <phone number>".
                          "invite <phone number>" => _m('COMMANDHELP', "not yet implemented."),
                          // TRANS: Help message for IM/SMS command "track <word>".
                          "track <word>" => _m('COMMANDHELP', "not yet implemented."),
                          // TRANS: Help message for IM/SMS command "untrack <word>".
                          "untrack <word>" => _m('COMMANDHELP', "not yet implemented."),
                          // TRANS: Help message for IM/SMS command "track off".
                          "track off" => _m('COMMANDHELP', "not yet implemented."),
                          // TRANS: Help message for IM/SMS command "untrack all".
                          "untrack all" => _m('COMMANDHELP', "not yet implemented."),
                          // TRANS: Help message for IM/SMS command "tracks".
                          "tracks" => _m('COMMANDHELP', "not yet implemented."),
                          // TRANS: Help message for IM/SMS command "tracking".
                          "tracking" => _m('COMMANDHELP', "not yet implemented."));

        // Give plugins a chance to add or override...
        Event::handle('HelpCommandMessages', array($this, &$commands));

        ksort($commands);
        foreach ($commands as $command => $help) {
            $out[] = "$command - $help";
        }
        $channel->output($this->user, implode("\n", $out));
    }
}


code:
<?php
/*
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2008, 2009, StatusNet, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

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

require_once INSTALLDIR . '/lib/util/command.php';

class CommandInterpreter
{
    function handle_command($user, $text)
    {
        // XXX: localise

        $text = preg_replace('/\s+/', ' ', trim($text));
        list($cmd, $arg) = self::split_arg($text);

        // We try to support all the same commands as Twitter, see
        // http://getsatisfaction.com/twitter/topics/what_are_the_twitter_commands
        // There are a few compatibility commands from earlier versions of
        // StatusNet

        $cmd = strtolower($cmd);
        $result = false;

        if (Event::handle('StartInterpretCommand', array($cmd, $arg, $user, &$result))) {
            switch($cmd) {
            case 'help':
                if ($arg) {
                    $result = null;
                } else {
                    $result = new HelpCommand($user);
                }
                break;
            case 'login':
                if ($arg) {
                    $result = null;
                } else {
                    $result = new LoginCommand($user);
                }
                break;
            case 'lose':
                if ($arg) {
                    list($other, $extra) = self::split_arg($arg);
                    if ($extra) {
                        $result = null;
                    } else {
                        $result = new LoseCommand($user, $other);
                    }
                } else {
                    $result = null;
                }
                break;
            case 'subscribers':
                if ($arg) {
                    $result = null;
                } else {
                    $result = new SubscribersCommand($user);
                }
                break;
            case 'subscriptions':
                if ($arg) {
                    $result = null;
                } else {
                    $result = new SubscriptionsCommand($user);
                }
                break;
            case 'groups':
                if ($arg) {
                    $result = null;
                } else {
                    $result = new GroupsCommand($user);
                }
                break;
            case 'on':
                if ($arg) {
                    list($other, $extra) = self::split_arg($arg);
                    if ($extra) {
                        $result = null;
                    } else {
                        $result = new OnCommand($user, $other);
                    }
                } else {
                    $result = new OnCommand($user);
                }
                break;
            case 'off':
                if ($arg) {
                    list($other, $extra) = self::split_arg($arg);
                    if ($extra) {
                        $result = null;
                    } else {
                        $result = new OffCommand($user, $other);
                    }
                } else {
                    $result = new OffCommand($user);
                }
                break;
            case 'stop':
            case 'quit':
                if ($arg) {
                    $result = null;
                } else {
                    $result = new OffCommand($user);
                }
                break;
            case 'join':
                if (!$arg) {
                    $result = null;
                } else {
                    list($other, $extra) = self::split_arg($arg);
                    if ($extra) {
                        $result = null;
                    } else {
                        $result = new JoinCommand($user, $other);
                    }
                }
                break;
            case 'drop':
                if (!$arg) {
                    $result = null;
                } else {
                    list($other, $extra) = self::split_arg($arg);
                    if ($extra) {
                        $result = null;
                    } else {
                        $result = new DropCommand($user, $other);
                    }
                }
                break;
            case 'follow':
            case 'sub':
                if (!$arg) {
                    $result = null;
                } else {
                    list($other, $extra) = self::split_arg($arg);
                    if ($extra) {
                        $result = null;
                    } else {
                        $result = new SubCommand($user, $other);
                    }
                }
                break;
            case 'leave':
            case 'unsub':
                if (!$arg) {
                    $result = null;
                } else {
                    list($other, $extra) = self::split_arg($arg);
                    if ($extra) {
                        $result = null;
                    } else {
                        $result = new UnsubCommand($user, $other);
                    }
                }
                break;
            case 'get':
            case 'last':
                if (!$arg) {
                    $result = null;
                }
                list($other, $extra) = self::split_arg($arg);
                if ($extra) {
                    $result = null;
                } else {
                    $result = new GetCommand($user, $other);
                }
                break;
            case 'r':
            case 'reply':
                if (!$arg) {
                    $result = null;
                }
                list($other, $extra) = self::split_arg($arg);
                if (!$extra) {
                    $result = null;
                } else {
                    $result = new ReplyCommand($user, $other, $extra);
                }
                break;
            case 'whois':
                if (!$arg) {
                    $result = null;
                } else {
                    list($other, $extra) = self::split_arg($arg);
                    if ($extra) {
                        $result = null;
                    } else {
                        $result = new WhoisCommand($user, $other);
                    }
                }
                break;
            case 'nudge':
                if (!$arg) {
                    $result = null;
                } else {
                    list($other, $extra) = self::split_arg($arg);
                    if ($extra) {
                        $result = null;
                    } else {
                        $result = new NudgeCommand($user, $other);
                    }
                }
                break;
            case 'stats':
                if ($arg) {
                    $result = null;
                } else {
                    $result = new StatsCommand($user);
                }
                break;
            case 'invite':
                if (!$arg) {
                    $result = null;
                } else {
                    list($other, $extra) = self::split_arg($arg);
                    if ($extra) {
                        $result = null;
                    } else {
                        $result = new InviteCommand($user, $other);
                    }
                }
                break;
             case 'list':
             case 'tag':
                if (!$arg) {
                    $result = null;
                    break;
                }
                list($other, $tags) = self::split_arg($arg);
                if (!$tags) {
                    $result = null;
                } else {
                    $result = new TagCommand($user, $other, $tags);
                }
                break;
             case 'unlist':
             case 'untag':
                if (!$arg) {
                    $result = null;
                    break;
                }
                list($other, $tags) = self::split_arg($arg);
                if (!$tags) {
                    $result = null;
                } else {
                    $result = new UntagCommand($user, $other, $tags);
                }
                break;
            case 'track':
                if (!$arg) {
                    $result = null;
                } else {
                    list($word, $extra) = self::split_arg($arg);
                    if ($extra) {
                        $result = null;
                    } else if ($word == 'off') {
                        $result = new TrackOffCommand($user);
                    } else {
                        $result = new TrackCommand($user, $word);
                    }
                }
                break;
            case 'untrack':
                if (!$arg) {
                    $result = null;
                } else {
                    list($word, $extra) = self::split_arg($arg);
                    if ($extra) {
                        $result = null;
                    } else if ($word == 'all') {
                        $result = new TrackOffCommand($user);
                    } else {
                        $result = new UntrackCommand($user, $word);
                    }
                }
                break;
            case 'tracks':
            case 'tracking':
                if ($arg) {
                    $result = null;
                } else {
                    $result = new TrackingCommand($user);
                }
                break;
            }

            Event::handle('EndInterpretCommand', array($cmd, $arg, $user, &$result));
        }

        return $result;
    }

    /**
     * Split arguments without triggering a PHP notice warning
     */
    static function split_arg($text)
    {
        $pieces = explode(' ', $text, 2);
        if (count($pieces) == 1) {
            $pieces[] = null;
        }
        return $pieces;
    }
}

repo_name: suraj/sloth

license: mit


code:
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social.  If not, see <http://www.gnu.org/licenses/>.

defined('GNUSOCIAL') || die();

/* Work internally in UTC */
date_default_timezone_set('UTC');

/* Work internally with UTF-8 */
mb_internal_encoding('UTF-8');

// All the fun stuff to actually initialize GNU social's framework code,
// without loading up a site configuration.
require_once INSTALLDIR . '/lib/util/framework.php';

try {
    GNUsocial::init(@$server, @$path, @$conffile);
} catch (NoConfigException $e) {
    // XXX: Throw a conniption if database not installed
    // XXX: Find a way to use htmlwriter for this instead of handcoded markup
    // TRANS: Error message displayed when no configuration file was found for a StatusNet installation.
    echo '
'. _('No configuration file found.') .'
';
    // TRANS: Error message displayed when no configuration file was found for a StatusNet installation.
    // TRANS: Is followed by a list of directories (separated by HTML breaks).
    echo '
'. _('I looked for configuration files in the following places:') .'
 ';
    echo implode($e->configFiles, '
');
    // TRANS: Error message displayed when no configuration file was found for a StatusNet installation.
    echo '
'. _('You may wish to run the installer to fix this.') .'
';
    // @todo FIXME Link should be in a para?
    // TRANS: Error message displayed when no configuration file was found for a StatusNet installation.
    // TRANS: The text is link text that leads to the installer page.
    echo ''. _('Go to the installer.') .'';
    exit;
}


code:
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social.  If not, see <http://www.gnu.org/licenses/>.

defined('GNUSOCIAL') || die();

class Daemon
{
    public $daemonize = true;
    public $_id = 'generic';

    public function __construct($daemonize = true)
    {
        $this->daemonize = $daemonize;
    }

    public function name()
    {
        return null;
    }

    public function get_id()
    {
        return $this->_id;
    }

    public function set_id($id)
    {
        $this->_id = $id;
    }

    /**
     * Reconnect to the database for each child process,
     * or they'll get very confused trying to use the
     * same socket.
     */
    protected function resetDb()
    {
        global $_DB_DATAOBJECT;

        // Can't be called statically
        $user = new User();
        $conn = $user->getDatabaseConnection();
        $conn->disconnect();

        // Remove the disconnected connection from the list
        foreach ($_DB_DATAOBJECT['CONNECTIONS'] as $k => $v) {
            if ($v === $conn) {
                unset($_DB_DATAOBJECT['CONNECTIONS'][$k]);
            }
        }

        // Reconnect main memcached, or threads will stomp on
        // each other and corrupt their requests.
        $cache = Cache::instance();
        if ($cache) {
            $cache->reconnect();
        }

        // Also reconnect memcached for status_network table.
        if (!empty(Status_network::$cache)) {
            Status_network::$cache->close();
            Status_network::$cache = null;
        }
    }

    public function background()
    {
        // Database connection will likely get lost after forking
        $this->resetDb();

        // Double-forking.
        foreach (['single', 'double'] as $v) {
            switch ($pid = pcntl_fork()) {
                case -1: // error
                    common_log(LOG_ERR, 'Could not fork.');
                    return false;
                case 0:  // child
                    if ($v === 'single') {
                        posix_setsid();
                    }
                    break;
                default: // parent
                    if ($v === 'double') {
                        common_log(LOG_INFO, 'Successfully forked.');
                    }
                    die();
            }
        }

        return true;
    }

    public function alreadyRunning()
    {
        $pidfilename = $this->pidFilename();

        if (!$pidfilename) {
            return false;
        }

        if (!file_exists($pidfilename)) {
            return false;
        }
        $contents = file_get_contents($pidfilename);
        if (posix_kill(trim($contents), 0)) {
            return true;
        } else {
            return false;
        }
    }

    public function writePidFile()
    {
        $pidfilename = $this->pidFilename();

        if (!$pidfilename) {
            return false;
        }

        return file_put_contents($pidfilename, posix_getpid() . "\n");
    }

    public function clearPidFile()
    {
        $pidfilename = $this->pidFilename();
        if (!$pidfilename) {
            return false;
        }
        return unlink($pidfilename);
    }

    public function pidFilename()
    {
        $piddir = common_config('daemon', 'piddir');
        if (!$piddir) {
            return null;
        }
        $name = $this->name();
        if (!$name) {
            return null;
        }
        return $piddir . '/' . $name . '.pid';
    }

    public function changeUser()
    {
        $groupname = common_config('daemon', 'group');

        if ($groupname) {
            $group_info = posix_getgrnam($groupname);
            if (!$group_info) {
                common_log(
                    LOG_WARNING,
                    'Ignoring unknown group for daemon: ' . $groupname
                );
            } else {
                common_log(LOG_INFO, "Setting group to " . $groupname);
                posix_setgid($group_info['gid']);
            }
        }

        $username = common_config('daemon', 'user');

        if ($username) {
            $user_info = posix_getpwnam($username);
            if (!$user_info) {
                common_log(
                    LOG_WARNING,
                    'Ignoring unknown user for daemon: ' . $username
                );
            } else {
                common_log(LOG_INFO, "Setting user to " . $username);
                posix_setuid($user_info['uid']);
            }
        }
    }

    public function runOnce()
    {
        if ($this->alreadyRunning()) {
            common_log(LOG_INFO, $this->name() . ' already running. Exiting.');
            exit(0);
        }

        if ($this->daemonize) {
            common_log(LOG_INFO, 'Backgrounding daemon "'.$this->name().'"');
            $this->background();
        }

        $this->writePidFile();
        $this->changeUser();
        $this->run();
        $this->clearPidFile();
    }

    public function run()
    {
        return true;
    }
}


code:
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social.  If not, see <http://www.gnu.org/licenses/>.

/**
 * @category  Config
 * @package   GNUsocial
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2008-2009, 2019 Free Software Foundation http://fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

defined('GNUSOCIAL') || die();

$default =
    array('site' =>
        array('name' => 'Just another GNU social node',
            'nickname' => 'gnusocial',
            'wildcard' => null,
            'server' => $_server,
            'theme' => 'neo-gnu',
            'path' => $_path,
            'logfile' => null,
            'logdebug' => false,
            'logo' => null,
            'ssllogo' => null,
            'logperf' => false, // Enable to dump performance counters to syslog
            'logperf_detail' => false, // Enable to dump every counter hit
            'fancy' => false,
            'locale_path' => INSTALLDIR . '/locale',
            'language' => 'en',
            'langdetect' => true,
            'languages' => get_all_languages(),
            'email' =>
                array_key_exists('SERVER_ADMIN', $_SERVER) ? $_SERVER['SERVER_ADMIN'] : null,
            'fakeaddressrecovery' => true,
            'broughtby' => null,
            'timezone' => 'UTC',
            'broughtbyurl' => null,
            'closed' => false,
            'inviteonly' => true,
            'private' => false,
            'ssl' => 'never',
            'sslproxy' => false,   // set to true to force GNU social to think it is HTTPS (i.e. using reverse proxy to enable it)
            'sslserver' => null,
            'dupelimit' => 60, // default for same person saying the same thing
            'textlimit' => 1000, // in chars; 0 == no limit
            'indent' => true,
            'use_x_sendfile' => false,
            'notice' => null, // site wide notice text
            'build' => 1, // build number, for code-dependent cache
        ),
        'security' =>
            array('hash_algos' => ['sha1', 'sha256', 'sha512'],   // set to null for anything that hash_hmac() can handle (and is in hash_algos())
            ),
        'db' =>
            array('database' => null, // must be set
                'schema_location' => INSTALLDIR . '/classes',
                'class_location' => INSTALLDIR . '/classes',
                'require_prefix' => 'classes/',
                'class_prefix' => '',
                'mirror' => null,
                'utf8' => true,
                'db_driver' => 'DB', # XXX: JanRain libs only work with DB
                'disable_null_strings' => true, // 'NULL' can be harmful
                'quote_identifiers' => true,
                'type' => 'mysql',
                'schemacheck' => 'runtime', // 'runtime' or 'script'
                'annotate_queries' => false, // true to add caller comments to queries, eg /* POST Notice::saveNew */
                'log_queries' => false, // true to log all DB queries
                'log_slow_queries' => 0, // if set, log queries taking over N seconds
                'mysql_foreign_keys' => false), // if set, enables experimental foreign key support on MySQL
        'fix' =>
            array('fancyurls' => true,   // makes sure aliases in WebFinger etc. are not f'd by index.php/ URLs
                'legacy_http' => false,   // set this to true if you have upgraded your site from http=>https
            ),
        'log' => [
            'debugtrace' => false,  // index.php handleError function, whether to include exception backtrace in log
        ],
        'syslog' =>
            array('appname' => 'statusnet', # for syslog
                'priority' => 'debug', # XXX: currently ignored
                'facility' => LOG_USER),
        'queue' =>
            array('enabled' => true,
                'daemon' => false, # Use queuedaemon. Default to false
                'threads' => null,    # an empty value here uses processor count to determine
                'subsystem' => 'db', # default to database, or 'stomp'
                'stomp_server' => null,
                'queue_basename' => '/queue/statusnet/',
                'control_channel' => '/topic/statusnet/control', // broadcasts to all queue daemons
                'stomp_username' => null,
                'stomp_password' => null,
                'stomp_persistent' => true, // keep items across queue server restart, if persistence is enabled
                'stomp_transactions' => true, // use STOMP transactions to aid in detecting failures (supported by ActiveMQ, but not by all)
                'stomp_acks' => true, // send acknowledgements after successful processing (supported by ActiveMQ, but not by all)
                'stomp_manual_failover' => true, // if multiple servers are listed, treat them as separate (enqueue on one randomly, listen on all)
                'monitor' => null, // URL to monitor ping endpoint (work in progress)
                'softlimit' => '90%', // total size or % of memory_limit at which to restart queue threads gracefully
                'spawndelay' => 1, // Wait at least N seconds between (re)spawns of child processes to avoid slamming the queue server with subscription startup
                'debug_memory' => false, // true to spit memory usage to log
                'breakout' => [], // List queue specifiers to break out when using Stomp queue.
                // Default will share all queues for all sites within each group.
                // Specify as <group>/<queue> or <group>/<queue>/<site>,
                // using nickname identifier as site.
                //
                // 'main/distrib' separate "distrib" queue covering all sites
                // 'xmpp/xmppout/mysite' separate "xmppout" queue covering just 'mysite'
                'max_retries' => 10, // drop messages after N failed attempts to process (Stomp)
                'dead_letter_dir' => false, // set to directory to save dropped messages into (Stomp)
            ),
        'license' =>
            array('type' => 'cc', # can be 'cc', 'allrightsreserved', 'private'
                'owner' => null, # can be name of content owner e.g. for enterprise
                'url' => 'https://creativecommons.org/licenses/by/3.0/',
                'title' => 'Creative Commons Attribution 3.0',
                'image' => $_path . '/theme/licenses/cc_by_3.0_80x15.png'),
        'mail' =>
            array('backend' => 'mail',
                'params' => null,
                'domain_check' => true),
        'nickname' =>
            array('blacklist' => [],
                'featured' => []),
        'profile' =>
            array('banned' => [],
                'biolimit' => null,
                'changenick' => false,
                'allowprivate' => false,  // whether to allow setting stream to private ("only followers can read")
                'backup' => false,    // can cause DoS, so should be done via CLI
                'restore' => false,
                'delete' => false,
                'move' => true),
        'image' =>
            array('jpegquality' => 85),
        'avatar' =>
            array('server' => null,
                'dir' => INSTALLDIR . '/file/avatar/',
                'url_base' => $_path . '/avatar/',
                'ssl' => null,
                'maxsize' => 300),
        'foaf' =>
            array(
                'mbox_sha1sum' => false,
            ),
        'public' =>
            array('localonly' => false,
                'blacklist' => [],
                'autosource' => []),
        'theme' =>
            array('server' => null,
                'dir' => null,
                'path' => null,
                'ssl' => null),
        'usertheme' =>
            array('linkcolor' => 'black',
                'backgroundcolor' => 'black'),
        'theme_upload' =>
            array('enabled' => extension_loaded('zip')),
        'javascript' =>
            array('server' => null,
                'path' => null,
                'ssl' => null,
                'bustframes' => true),
        'local' => // To override path/server for themes in 'local' dir (not currently applied to local plugins)
            array('server' => null,
                'dir' => null,
                'path' => null,
                'ssl' => null),
        'throttle' =>
            array('enabled' => false, // whether to throttle edits; false by default
                'count' => 20, // number of allowed messages in timespan
                'timespan' => 600), // timespan for throttling
        'invite' =>
            array('enabled' => true),
        'tag' =>
            array('dropoff' => 864000.0,   # controls weighting based on age
                'cutoff' => 86400 * 90), # only look at notices posted in last 90 days
        'popular' =>
            array('dropoff' => 864000.0,   # controls weighting based on age
                'cutoff' => 86400 * 90), # only look at notices favorited in last 90 days
        'daemon' =>
            array('piddir' => sys_get_temp_dir(),
                'user' => false,
                'group' => false),
        'emailpost' =>
            array('enabled' => false),
        'sms' =>
            array('enabled' => false),
        'twitterimport' =>
            array('enabled' => false),
        'integration' =>
            array('source' => 'StatusNet', # source attribute for Twitter
                'taguri' => null), # base for tag URIs
        'twitter' =>
            array('signin' => true,
                'consumer_key' => null,
                'consumer_secret' => null),
        'cache' =>
            array('base' => null),
        'ping' =>
            array('notify' => [],
                'timeout' => 2),
        'inboxes' =>
            array('enabled' => true), # ignored after 0.9.x
        'newuser' =>
            array('default' => null,
                'welcome' => null),
        'linkify' => array(
            // "bare" below means "without schema", like domain.com vs. https://domain.com
            'bare_domains' => false,  // convert domain.com to domain.com ?
            'bare_ipv4' => false,   // convert IPv4 addresses to hyperlinks?
            'bare_ipv6' => false,   // convert IPv6 addresses to hyperlinks?
        ),
        'attachments' =>
            array('server' => null,
                'dir' => INSTALLDIR . '/file/',
                'path' => $_path . '/file/',
                'sslserver' => null,
                'sslpath' => null,
                'ssl' => null,
                'supported' => array(
                    'application/vnd.oasis.opendocument.chart' => 'odc',
                    'application/vnd.oasis.opendocument.formula' => 'odf',
                    'application/vnd.oasis.opendocument.graphics' => 'odg',
                    'application/vnd.oasis.opendocument.graphics-template' => 'otg',
                    'application/vnd.oasis.opendocument.image' => 'odi',
                    'application/vnd.oasis.opendocument.presentation' => 'odp',
                    'application/vnd.oasis.opendocument.presentation-template' => 'otp',
                    'application/vnd.oasis.opendocument.spreadsheet' => 'ods',
                    'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots',
                    'application/vnd.oasis.opendocument.text' => 'odt',
                    'application/vnd.oasis.opendocument.text-master' => 'odm',
                    'application/vnd.oasis.opendocument.text-template' => 'ott',
                    'application/vnd.oasis.opendocument.text-web' => 'oth',
                    'application/pdf' => 'pdf',
                    'application/zip' => 'zip',
                    'application/x-bzip2' => 'bz2',
                    'application/x-go-sgf' => 'sgf',
                    'application/xml' => 'xml',
                    'application/gpx+xml' => 'gpx',
                    image_type_to_mime_type(IMAGETYPE_PNG) => image_type_to_extension(IMAGETYPE_PNG),
                    image_type_to_mime_type(IMAGETYPE_JPEG) => image_type_to_extension(IMAGETYPE_JPEG),
                    image_type_to_mime_type(IMAGETYPE_GIF) => image_type_to_extension(IMAGETYPE_GIF),
                    'image/svg+xml' => 'svg', // No built-in constant
                    image_type_to_mime_type(IMAGETYPE_ICO) => image_type_to_extension(IMAGETYPE_ICO),
                    'audio/ogg' => 'ogg',
                    'audio/mpeg' => 'mpg',
                    'audio/x-speex' => 'spx',
                    'application/ogg' => 'ogx',
                    'text/plain' => 'txt',
                    'video/mpeg' => 'mpeg',
                    'video/mp4' => 'mp4',
                    'video/ogg' => 'ogv',
                    'video/quicktime' => 'mov',
                    'video/webm' => 'webm',
                ),
                'file_quota' => common_get_preferred_php_upload_limit(),
                'user_quota' => 50000000,
                'monthly_quota' => 15000000,
                'uploads' => true,
                'show_html' => false,  // show (filtered) text/html attachments (and oEmbed HTML etc.). Doesn't affect AJAX calls.
                'show_thumbs' => true, // show thumbnails in notice lists for uploaded images, and photos and videos linked remotely that provide oEmbed info
                'process_links' => true, // check linked resources for embeddable photos and videos; this will hit referenced external web sites when processing new messages.
                'extblacklist' => [],
                'memory_limit' => '1024M' // PHP's memory limit to use temporarily when handling images
            ),
        'thumbnail' => [
            'dir' => null,    // falls back to File::path('thumb') (equivalent to ['attachments']['dir'] .  '/thumb/')
            'path' => null,   // falls back to generating a URL with File::url('thumb/$filename') (equivalent to ['attachments']['path'] . '/thumb/')
            'server' => null, // Only used if ['thumbnail']['path'] is NOT empty, and then it falls back to ['site']['server'], schema is decided from GNUsocial::useHTTPS()

            'crop' => false,      // overridden to true if thumb height === null
            'maxsize' => 1000,     // thumbs with an edge larger than this will not be generated
            'width' => 450,
            'height' => 600,
            'upscale' => false,
            'animated' => false, // null="UseFileAsThumbnail", false="can use still frame". true requires ImageMagickPlugin
        ],
        'application' =>
            array('desclimit' => null),
        'group' =>
            array('maxaliases' => 3,
                'desclimit' => null,
                'addtag' => true),
        'peopletag' =>
            array('maxtags' => 100, // maximum number of tags a user can create.
                'maxpeople' => 500, // maximum no. of people with the same tag by the same user
                'allow_tagging' => array('all' => true), // equivalent to array('local' => true, 'remote' => true)
                'desclimit' => null),
        'search' =>
            array('type' => 'like'),
        'sessions' =>
            array('handle' => false,   // whether to handle sessions ourselves
                'debug' => false,    // debugging output for sessions
                'gc_limit' => 1000), // max sessions to expire at a time
        'htmlfilter' => [  // remove tags from user/remotely generated HTML if they are === true
            'img' => true,
            'video' => true,
            'audio' => true,
        ],
        'htmlpurifier' => [ // configurable options for HTMLPurifier
            'Cache.DefinitionImpl' => 'Serializer',
            'Cache.SerializerPath' => implode(DIRECTORY_SEPARATOR, [sys_get_temp_dir(), 'gnusocial']),
        ],
        'notice' =>
            array('contentlimit' => null,
                'allowprivate' => false,  // whether to allow users to "check the padlock" to publish notices available for their subscribers.
                'defaultscope' => null, // null means 1 if site/private, 0 otherwise
                'hidespam' => true), // Whether to hide silenced users from timelines
        'message' =>
            array('contentlimit' => null),
        'location' =>
            array('share' => 'user', // whether to share location; 'always', 'user', 'never'
                'sharedefault' => false),
        'logincommand' =>
            array('disabled' => true),
        'plugins' =>[
            'core' => [
                'Activity' => [],
                'ActivityModeration' => [],
                'ActivityVerb' => [],
                'ActivityVerbPost' => [],
                'AuthCrypt' => [],
                'Favorite' => [],
                'HTMLPurifierSchemes' => [],
                'Share' => [],
                'TheFreeNetwork' => [],
            ],
            'default' => [
                'AccountManager' => [],
                'AntiBrute' => [],
                'Blacklist' => [],
                'Bookmark' => [],
                'ClientSideShorten' => [],
                'Cronish' => [],
                'DefaultLayout' => [],
                'DirectionDetector' => [],
                'DirectMessage' => [],
                'Directory' => [],
                'EmailAuthentication' => [],
                'Embed' => [],
                'Event' => [],
                'LRDD' => [],
                'Nodeinfo' => [],
                'OpenID' => [],
                'DBQueue' => [],
                'OpportunisticQM' => [],
                'RemoteFollow' => [],
                'ActivityPub' => [], // The order is important here (IT HAS TO COME BEFORE OSTATUS)
                'OStatus' => [],
                'Poll' => [],
                'SimpleCaptcha' => [],
                'TagSub' => [],
                'WebFinger' => [],
            ],
            'locale_path' => false, // Set to a path to use *instead of* each plugin's own locale subdirectories
            'server' => null,
            'sslserver' => null,
            'path' => null,
            'sslpath' => null,
        ],
        'admin' =>
            array('panels' => array('site', 'user', 'paths', 'access', 'sessions', 'sitenotice', 'license', 'plugins')),
        'singleuser' =>
            array('enabled' => false,
                'nickname' => null),
        'robotstxt' =>
            array('crawldelay' => 0,
                'disallow' => array('main', 'settings', 'admin', 'search', 'message')
            ),
        'api' =>
            array('realm' => null),
        'nofollow' =>
            array('subscribers' => true,
                'members' => true,
                'peopletag' => true,
                'external' => 'sometimes'), // Options: 'sometimes', 'never', default = 'sometimes'
        'url' =>
            array('shortener' => 'internal',
                'maxurllength' => 100,
                'maxnoticelength' => -1),
        'http' => // HTTP client settings when contacting other sites
            array('ssl_cafile' => false, // To enable SSL cert validation, point to a CA bundle (eg '/usr/lib/ssl/certs/ca-certificates.crt') (this activates "ssl_verify_peer")
                'ssl_verify_host' => true,    // HTTPRequest2 makes sure this is set to CURLOPT_SSL_VERIFYHOST==2 if using curl
                'curl' => false, // Use CURL backend for HTTP fetches if available. (If not, PHP's socket streams will be used.)
                'connect_timeout' => 5,
                'timeout' => intval(ini_get('default_socket_timeout')),   // effectively should be this by default already, but this makes it more explicitly configurable for you users .)
                'proxy_host' => null,
                'proxy_port' => null,
                'proxy_user' => null,
                'proxy_password' => null,
                'proxy_auth_scheme' => null,
            ),
        'router' =>
            array('cache' => true), // whether to cache the router object. Defaults to true, turn off for devel
        'discovery' =>
            array('cors' => false), // Allow Cross-Origin Resource Sharing for service discovery (host-meta, XRD, etc.)
        'performance' => array('high' => false) // disable some features for higher performance; default false
    );


code:
<?php
/*
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2008, 2009, StatusNet, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Recursively deletes a directory tree.
 *
 * @param string $folder The directory path.
 * @param bool $keepRootFolder Whether to keep the top-level folder.
 * @return bool TRUE on success, otherwise FALSE.
 */
function deleteTree(
    $folder,
    $keepRootFolder = false
) {
    // Handle bad arguments.
    if (empty($folder) || !file_exists($folder)) {
        return true; // No such file/folder exists.
    } elseif (is_file($folder) || is_link($folder)) {
        return @unlink($folder); // Delete file/link.
    }

    // Delete all children.
    $files = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($folder, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::CHILD_FIRST
    );

    foreach ($files as $fileinfo) {
        $action = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
        if (!@$action($fileinfo->getRealPath())) {
            return false; // Abort due to the failure.
        }
    }

    // Delete the root folder itself?
    return (!$keepRootFolder ? @rmdir($folder) : true);
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Utility for finding and parsing documentation files
 *
 * 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  Documentation
 * @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);
}

/**
 * Utility for finding and parsing documentation files
 *
 * @category  Documentation
 * @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 DocFile
{
    protected $filename;
    protected $contents;

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

    static function forTitle($title, $paths)
    {
        if (!is_array($paths)) {
            $paths = array($paths);
        }

        $filename = null;

        if (Event::handle('StartDocFileForTitle', array($title, &$paths, &$filename))) {

            foreach ($paths as $path) {

                $def = $path.'/'.$title;

                if (!file_exists($def)) {
                    $def = null;
                }

                $lang = glob($path.'/'.$title.'.*');

                if ($lang === false) {
                    $lang = array();
                }

                if (!empty($lang) || !empty($def)) {
                    $filename = self::negotiateLanguage($lang, $def);
                    break;
                }
            }

            Event::handle('EndDocFileForTitle', array($title, $paths, &$filename));
        }

        if (empty($filename)) {
            return null;
        } else {
            return new DocFile($filename);
        }
    }

    function toHTML($args=null)
    {
        if (is_null($args)) {
            $args = array();
        }

        if (empty($this->contents)) {
            $this->contents = file_get_contents($this->filename);
        }

        return common_markup_to_html($this->contents, $args);
    }

    static function defaultPaths()
    {
        $paths = array(INSTALLDIR.'/local/doc-src/',
                       INSTALLDIR.'/doc-src/');

        $site = GNUsocial::currentSite();

        if (!empty($site)) {
            array_unshift($paths, INSTALLDIR.'/local/doc-src/'.$site.'/');
        }

        return $paths;
    }

    static function mailPaths()
    {
        $paths = array(INSTALLDIR.'/local/mail-src/',
                       INSTALLDIR.'/mail-src/');

        $site = GNUsocial::currentSite();

        if (!empty($site)) {
            array_unshift($paths, INSTALLDIR.'/local/mail-src/'.$site.'/');
        }

        return $paths;
    }

    static function negotiateLanguage($filenames, $defaultFilename=null)
    {
        // XXX: do this better

        $langcode = common_language();

        foreach ($filenames as $filename) {
            if (preg_match('/\.'.$langcode.'$/', $filename)) {
                return $filename;
            }
        }

        return $defaultFilename;
    }
}


code:
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social.  If not, see <http://www.gnu.org/licenses/>.

defined('GNUSOCIAL') || die();

/**
 * Class for events
 *
 * This "class" two static functions for managing events in the StatusNet code.
 *
 * @category Event
 * @package  GNU social
 * @author   Evan Prodromou <evan@status.net>
 * @copyright 2010-2019 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 *
 * @todo     Define a system for using Event instances
 */
class Event {

    /* Global array of hooks, mapping eventname => array of callables */

    protected static $_handlers = array();

    /**
     * Add an event handler
     *
     * To run some code at a particular point in StatusNet processing.
     * Named events include receiving an XMPP message, adding a new notice,
     * or showing part of an HTML page.
     *
     * The arguments to the handler vary by the event. Handlers can return
     * two possible values: false means that the event has been replaced by
     * the handler completely, and no default processing should be done.
     * Non-false means successful handling, and that the default processing
     * should succeed. (Note that this only makes sense for some events.)
     *
     * Handlers can also abort processing by throwing an exception; these will
     * be caught by the closest code and displayed as errors.
     *
     * @param string   $name    Name of the event
     * @param callable $handler Code to run
     *
     * @return void
     */

    public static function addHandler($name, $handler) {
        if (array_key_exists($name, Event::$_handlers)) {
            Event::$_handlers[$name][] = $handler;
        } else {
            Event::$_handlers[$name] = array($handler);
        }
    }

    /**
     * Handle an event
     *
     * Events are any point in the code that we want to expose for admins
     * or third-party developers to use.
     *
     * We pass in an array of arguments (including references, for stuff
     * that can be changed), and each assigned handler gets run with those
     * arguments. Exceptions can be thrown to indicate an error.
     *
     * @param string $name Name of the event that's happening
     * @param array  $args Arguments for handlers
     *
     * @return bool flag saying whether to continue processing, based
     *                 on results of handlers.
     */

    public static function handle($name, array $args = []) {
        $result = null;
        if (array_key_exists($name, Event::$_handlers)) {
            foreach (Event::$_handlers[$name] as $handler) {
                $result = call_user_func_array($handler, $args);
                if ($result === false) {
                    break;
                }
            }
        }
        return ($result !== false);
    }

    /**
     * Check to see if an event handler exists
     *
     * Look to see if there's any handler for a given event, or narrow
     * by providing the name of a specific plugin class.
     *
     * @param string $name Name of the event to look for
     * @param string $plugin Optional name of the plugin class to look for
     *
     * @return bool flag saying whether such a handler exists
     *
     */

    public static function hasHandler($name, $plugin=null) {
        if (array_key_exists($name, Event::$_handlers)) {
            if (isset($plugin)) {
                foreach (Event::$_handlers[$name] as $handler) {
                    if (get_class($handler[0]) == $plugin) {
                        return true;
                    }
                }
            } else {
                return true;
            }
        }
        return false;
    }

    public static function getHandlers($name)
    {
        return Event::$_handlers[$name];
    }

    /**
     * Disables any and all handlers that have been set up so far;
     * use only if you know it's safe to reinitialize all plugins.
     */
    public static function clearHandlers() {
        Event::$_handlers = array();
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Base class for forms
 *
 * 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>
 * @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/ui/widget.php';

/**
 * Base class for forms
 *
 * We have a lot of common forms (subscribe, fave, delete) and this superclass
 * lets us abstract out the basic features of the form.
 *
 * @category Widget
 * @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      HTMLOutputter
 */

class Form extends Widget
{
    var $enctype = null;

    /**
     * Show the form
     *
     * Uses a recipe to output the form.
     *
     * @return void
     * @see Widget::show()
     */

    function show()
    {
        $attributes = array('id' => $this->id(),
            'class' => $this->formClass(),
            'method' => $this->method(),
            'action' => $this->action());

        if (!empty($this->enctype)) {
            $attributes['enctype'] = $this->enctype;
        }
        $this->out->elementStart('form', $attributes);
        $this->out->elementStart('fieldset');
        $this->formLegend();
        $this->sessionToken();
        $this->formData();
        $this->formActions();
        $this->out->elementEnd('fieldset');
        $this->out->elementEnd('form');
    }

    /**
     * Include a session token for CSRF protection
     *
     * @return void
     */

    function sessionToken()
    {
        if (strtolower($this->method()) == 'post') {
            $this->out->hidden('token-' . $this->id() ?: common_random_hexstr(3), common_session_token(), 'token');
        }
    }

    /**
     * Name of the form
     *
     * Sub-classes should overload this with the name of their form.
     *
     * @return void
     */

    function formLegend()
    {
    }

    /**
     * Visible or invisible data elements
     *
     * Display the form fields that make up the data of the form.
     * Sub-classes should overload this to show their data.
     *
     * @return void
     */

    function formData()
    {
    }

    /**
     * HTTP method used to submit the form
     *
     * Defaults to post. Subclasses can override if they need to.
     *
     * @return string the method to use for submitting
     */
     function method()
     {
         return 'post';
     }

    /**
     * Buttons for form actions
     *
     * Submit and cancel buttons (or whatever)
     * Sub-classes should overload this to show their own buttons.
     *
     * @return void
     */

    function formActions()
    {
    }

    /**
     * ID of the form
     *
     * Should be unique on the page. Sub-classes should overload this
     * to show their own IDs.
     *
     * @return int ID of the form
     */

    function id()
    {
        return null;
    }

    /**
     * Action of the form.
     *
     * URL to post to. Should be overloaded by subclasses to give
     * somewhere to post to.
     *
     * @return string URL to post to
     */

    function action()
    {
    }

    /**
     * Class of the form. May include space-separated list of multiple classes.
     *
     * If 'ajax' is included, the form will automatically be submitted with
     * an 'ajax=1' parameter added, and the resulting form or error message
     * will replace the form after submission.
     *
     * It's up to you to make sure that the target action supports this!
     *
     * @return string the form's class
     */

    function formClass()
    {
        return 'form';
    }

    function li($class=null)
    {
        $this->out->elementStart('li', $class);
    }

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


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

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

/**
 * Form action extendable class
 *
 * @category Action
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @author   Mikael Nordfeldth <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 */
class FormAction extends ManagedAction
{
    protected $form = null;
    protected $formOpts = array();
    protected $type = null;
    protected $needLogin = true;
    protected $canPost = true;

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

        $this->form = $this->form ?: ucfirst($this->action);
        $this->args['form'] = $this->form;

        $this->type = !is_null($this->type) ? $this->type : $this->trimmed('type');
        $this->args['context'] = $this->trimmed('action');  // reply for notice for example

        if (!$this->type) {
            $this->type = null;
        }

        return true;
    }

    public function isReadOnly($args) {
        return !$this->isPost();
    }

    public function showPageNotice()
    {
        $this->showInstructions();
        if ($msg = $this->getError()) {
            $this->element('div', 'error', $msg);
        }
        if ($msg = $this->getInfo()) {
            $this->element('div', 'info', $msg);
        }
    }

    /**
     * Outputs the instructions for the form
     *
     * SHOULD overload
     */
    public function showInstructions()
    {
        // instructions are nice, so users know what to do
        $this->raw(common_markup_to_html($this->getInstructions()));
    }

    /**
     * @return string with instructions to pass into common_markup_to_html()
     */
    protected function getInstructions()
    {
        return null;
    }

    public function showForm($msg=null, $success=false)
    {
        $this->msg = $msg;
        $this->success = $success;
        $this->showPage();
    }

    protected function showContent()
    {
        $form = $this->getForm();
        $form->show();
    }

    protected function getForm()
    {
        $class = $this->form.'Form';
        $form = new $class($this, $this->formOpts);
        return $form;
    }
}