code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Abstract class for i/o managers
 *
 * 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  QueueManager
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @author    Sarven Capadisli <csarven@status.net>
 * @author    Brion Vibber <brion@status.net>
 * @copyright 2009-2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

abstract class IoManager
{
    const SINGLE_ONLY = 0;
    const INSTANCE_PER_SITE = 1;
    const INSTANCE_PER_PROCESS = 2;

    /**
     * Factory function to get an appropriate subclass.
     */
    public static function get() {
        throw new MethodNotImplementedException(__METHOD__);
    }

    /**
     * Tell the i/o queue master if and how we can handle multi-site
     * processes.
     *
     * Return one of:
     *   IoManager::SINGLE_ONLY
     *   IoManager::INSTANCE_PER_SITE
     *   IoManager::INSTANCE_PER_PROCESS
     */
    public static function multiSite()
    {
        return IoManager::SINGLE_ONLY;
    }

    /**
     * If in a multisite configuration, the i/o master will tell
     * your manager about each site you'll have to handle so you
     * can do any necessary per-site setup.
     *
     * The new site will be the currently live configuration during
     * this call.
     */
    public function addSite()
    {
        /* no-op */
    }

    /**
     * This method is called when data is available on one of your
     * i/o manager's sockets. The socket with data is passed in,
     * in case you have multiple sockets.
     *
     * If your i/o manager is based on polling during idle processing,
     * you don't need to implement this.
     *
     * @param resource $socket
     * @return boolean true on success, false on failure
     */
    public function handleInput($socket)
    {
        return true;
    }

    /**
     * Return any open sockets that the run loop should listen
     * for input on. If input comes in on a listed socket,
     * the matching manager's handleInput method will be called.
     *
     * @return array of resources
     */
    function getSockets()
    {
        return array();
    }

    /**
     * Maximum planned time between poll() calls when input isn't waiting.
     * Actual time may vary!
     *
     * When we get a polling hit, the timeout will be cut down to 0 while
     * input is coming in, then will back off to this amount if no further
     * input shows up.
     *
     * By default polling is disabled; you must override this to enable
     * polling for this manager.
     *
     * @return int max poll interval in seconds, or 0 to disable polling
     */
    function pollInterval()
    {
        return 0;
    }

    /**
     * Request a maximum timeout for listeners before the next idle period.
     * Actual wait may be shorter, so don't go crazy in your idle()!
     * Wait could be longer if other handlers performed some slow activity.
     *
     * Return 0 to request that listeners return immediately if there's no
     * i/o and speed up the idle as much as possible; but don't do that all
     * the time as this will burn CPU.
     *
     * @return int seconds
     */
    function timeout()
    {
        return 60;
    }

    /**
     * Called by IoManager after each handled item or empty polling cycle.
     * This is a good time to e.g. service your XMPP connection.
     *
     * Doesn't need to be overridden if there's no maintenance to do.
     */
    function idle()
    {
        return true;
    }

    /**
     * The meat of a polling manager... check for something to do
     * and do it! Note that you should not take too long, as other
     * i/o managers may need to do some work too!
     *
     * On a successful hit, the next poll() call will come as soon
     * as possible followed by exponential backoff up to pollInterval()
     * if no more data is available.
     *
     * @return boolean true if events were hit
     */
    public function poll()
    {
        return false;
    }

    /**
     * Initialization, run when the queue manager starts.
     * If this function indicates failure, the handler run will be aborted.
     *
     * @param IoMaster $master process/event controller
     * @return boolean true on success, false on failure
     */
    public function start($master)
    {
        $this->master = $master;
        return true;
    }

    /**
     * Cleanup, run when the queue manager ends.
     * If this function indicates failure, a warning will be logged.
     *
     * @return boolean true on success, false on failure
     */
    public function finish()
    {
        return true;
    }

    /**
     * Ping iomaster's queue status monitor with a stats update.
     * Only valid during input loop!
     *
     * @param string $counter keyword for counter to increment
     */
    public function stats($counter, $owners=array())
    {
        $this->master->stats($counter, $owners);
    }
}


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/>.

/**
 * I/O manager to wrap around socket-reading and polling queue & connection managers.
 *
 * @category  QueueManager
 * @package   GNUsocial
 * @author    Brion Vibber <brion@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

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

abstract class IoMaster
{
    public $id;

    protected $multiSite = false;
    protected $managers = array();
    protected $singletons = array();

    protected $pollTimeouts = array();
    protected $lastPoll = array();

    public $shutdown = false; // Did we do a graceful shutdown?
    public $respawn = true; // Should we respawn after shutdown?

    /**
     * @param string $id process ID to use in logging/monitoring
     */
    public function __construct($id)
    {
        $this->id = $id;
        $this->monitor = new QueueMonitor();
    }

    public function init($multiSite=null)
    {
        if ($multiSite !== null) {
            $this->multiSite = $multiSite;
        }

        $this->initManagers();
    }

    /**
     * Initialize IoManagers which are appropriate to this instance;
     * pass class names or instances into $this->instantiate().
     *
     * If setup and configuration may vary between sites in multi-site
     * mode, it's the subclass's responsibility to set them up here.
     *
     * Switching site configurations is an acceptable side effect.
     */
    abstract public function initManagers();

    /**
     * Instantiate an i/o manager class for the current site.
     * If a multi-site capable handler is already present,
     * we don't need to build a new one.
     *
     * @param mixed $manager class name (to run $class::get()) or object
     */
    protected function instantiate($manager)
    {
        if (is_string($manager)) {
            $manager = call_user_func(array($class, 'get'));
        }

        $caps = $manager->multiSite();
        if ($caps == IoManager::SINGLE_ONLY) {
            if ($this->multiSite) {
                throw new Exception("$class can't run with --all; aborting.");
            }
        } elseif ($caps == IoManager::INSTANCE_PER_PROCESS) {
            $manager->addSite();
        }

        if (!in_array($manager, $this->managers, true)) {
            // Only need to save singletons once
            $this->managers[] = $manager;
        }
    }

    /**
     * Basic run loop...
     *
     * Initialize all io managers, then sit around waiting for input.
     * Between events or timeouts, pass control back to idle() method
     * to allow for any additional background processing.
     */
    public function service()
    {
        $this->logState('init');
        $this->start();
        $this->checkMemory(false);

        while (!$this->shutdown) {
            $timeouts = array_values($this->pollTimeouts);
            $timeouts[] = 60; // default max timeout

            // Wait for something on one of our sockets
            $sockets = array();
            $managers = array();
            foreach ($this->managers as $manager) {
                foreach ($manager->getSockets() as $socket) {
                    $sockets[] = $socket;
                    $managers[] = $manager;
                }
                $timeouts[] = intval($manager->timeout());
            }

            $timeout = min($timeouts);
            if ($sockets) {
                $read = $sockets;
                $write = array();
                $except = array();
                $this->logState('listening');
                //common_debug("Waiting up to $timeout seconds for socket data...");
                $ready = stream_select($read, $write, $except, $timeout, 0);

                if ($ready === false) {
                    common_log(LOG_ERR, "Error selecting on sockets");
                } elseif ($ready > 0) {
                    foreach ($read as $socket) {
                        $index = array_search($socket, $sockets, true);
                        if ($index !== false) {
                            $this->logState('queue');
                            $managers[$index]->handleInput($socket);
                        } else {
                            common_log(LOG_ERR, "Saw input on a socket we didn't listen to");
                        }
                    }
                }
            }

            if ($timeout > 0 && empty($sockets)) {
                // If we had no listeners, sleep until the pollers' next requested wakeup.
                common_log(LOG_DEBUG, "Sleeping $timeout seconds until next poll cycle...");
                $this->logState('sleep');
                sleep($timeout);
            }

            $this->logState('poll');
            $this->poll();

            $this->logState('idle');
            $this->idle();

            $this->checkMemory();
        }

        $this->logState('shutdown');
        $this->finish();
    }

    /**
     * Check runtime memory usage, possibly triggering a graceful shutdown
     * and thread respawn if we've crossed the soft limit.
     *
     * @param boolean $respawn if false we'll shut down instead of respawning
     */
    protected function checkMemory($respawn=true)
    {
        $memoryLimit = $this->softMemoryLimit();
        if ($memoryLimit > 0) {
            $usage = memory_get_usage();
            if ($usage > $memoryLimit) {
                common_log(LOG_INFO, "Queue thread hit soft memory limit ($usage > $memoryLimit); gracefully restarting.");
                if ($respawn) {
                    $this->requestRestart();
                } else {
                    $this->requestShutdown();
                }
            } elseif (common_config('queue', 'debug_memory')) {
                $fmt = number_format($usage);
                common_log(LOG_DEBUG, "Memory usage $fmt");
            }
        }
    }

    /**
     * Return fully-parsed soft memory limit in bytes.
     * @return intval 0 or -1 if not set
     */
    public function softMemoryLimit()
    {
        $softLimit = trim(common_config('queue', 'softlimit'));
        if (substr($softLimit, -1) == '%') {
            $limit = $this->parseMemoryLimit(ini_get('memory_limit'));
            if ($limit > 0) {
                return intval(substr($softLimit, 0, -1) * $limit / 100);
            } else {
                return -1;
            }
        } else {
            return $this->parseMemoryLimit($softLimit);
        }
        return $softLimit;
    }

    /**
     * Interpret PHP shorthand for memory_limit and friends.
     * Why don't they just expose the actual numeric value? :P
     * @param string $mem
     * @return int
     */
    public function parseMemoryLimit($mem)
    {
        // http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes
        $mem = strtolower(trim($mem));
        $size = array('k' => 1024,
                      'm' => 1024*1024,
                      'g' => 1024*1024*1024);
        if (empty($mem)) {
            return 0;
        } elseif (is_numeric($mem)) {
            return intval($mem);
        } else {
            $mult = substr($mem, -1);
            if (isset($size[$mult])) {
                return substr($mem, 0, -1) * $size[$mult];
            } else {
                return intval($mem);
            }
        }
    }

    public function start()
    {
        foreach ($this->managers as $index => $manager) {
            $manager->start($this);
            // @fixme error check
            if ($manager->pollInterval()) {
                // We'll want to check for input on the first pass
                $this->pollTimeouts[$index] = 0;
                $this->lastPoll[$index] = 0;
            }
        }
    }

    public function finish()
    {
        foreach ($this->managers as $manager) {
            $manager->finish();
            // @fixme error check
        }
    }

    /**
     * Called during the idle portion of the runloop to see which handlers
     */
    public function poll()
    {
        foreach ($this->managers as $index => $manager) {
            $interval = $manager->pollInterval();
            if ($interval <= 0) {
                // Not a polling manager.
                continue;
            }

            if (isset($this->pollTimeouts[$index])) {
                $timeout = $this->pollTimeouts[$index];
                if (hrtime(true) - $this->lastPoll[$index] < $timeout * 1000000000) {
                    // Not time to poll yet.
                    continue;
                }
            } else {
                $timeout = 0;
            }
            $hit = $manager->poll();

            $this->lastPoll[$index] = hrtime(true);
            if ($hit) {
                // Do the next poll quickly, there may be more input!
                $this->pollTimeouts[$index] = 0;
            } else {
                // Empty queue. Exponential backoff up to the maximum poll interval.
                if ($timeout > 0) {
                    $timeout = min($timeout * 2, $interval);
                } else {
                    $timeout = 1;
                }
                $this->pollTimeouts[$index] = $timeout;
            }
        }
    }

    /**
     * Called after each handled item or empty polling cycle.
     * This is a good time to e.g. service your XMPP connection.
     */
    public function idle()
    {
        foreach ($this->managers as $manager) {
            $manager->idle();
        }
    }

    /**
     * Send thread state update to the monitoring server, if configured.
     *
     * @param string $state ('init', 'queue', 'shutdown' etc)
     * @param string $substate (optional, eg queue name 'omb' 'sms' etc)
     */
    protected function logState($state, $substate='')
    {
        $this->monitor->logState($this->id, $state, $substate);
    }

    /**
     * Send thread stats.
     * Thread ID will be implicit; other owners can be listed as well
     * for per-queue and per-site records.
     *
     * @param string $key counter name
     * @param array $owners list of owner keys like 'queue:xmpp' or 'site:stat01'
     */
    public function stats($key, $owners=array())
    {
        $owners[] = "thread:" . $this->id;
        $this->monitor->stats($key, $owners);
    }

    /**
     * For IoManagers to request a graceful shutdown at end of event loop.
     */
    public function requestShutdown()
    {
        $this->shutdown = true;
        $this->respawn = false;
    }

    /**
     * For IoManagers to request a graceful restart at end of event loop.
     */
    public function requestRestart()
    {
        $this->shutdown = true;
        $this->respawn = true;
    }
}

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/>.

/**
 * Utility functions for i18n
 *
 * @category  I18n
 * @package   GNU social
 * @author    Matthew Gregg <matthew.gregg@gmail.com>
 * @author    Ciaran Gultnieks <ciaran@ciarang.com>
 * @author    Evan Prodromou <evan@status.net>
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 * @copyright 2010-2019 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

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

// Locale category constants are usually predefined, but may not be
// on some systems such as Win32.
$LC_CATEGORIES = ['LC_CTYPE',
                  'LC_NUMERIC',
                  'LC_TIME',
                  'LC_COLLATE',
                  'LC_MONETARY',
                  'LC_MESSAGES',
                  'LC_ALL'];
foreach ($LC_CATEGORIES as $key => $name) {
    if (!defined($name)) {
        define($name, $key);
    }
}

if (!function_exists('dpgettext')) {
    /**
     * Context-aware dgettext wrapper; use when messages in different contexts
     * won't be distinguished from the English source but need different translations.
     * The context string will appear as msgctxt in the .po files.
     *
     * Not currently exposed in PHP's gettext module; implemented to be compat
     * with gettext.h's macros.
     *
     * @param string $domain domain identifier
     * @param string $context context identifier, should be some key like "menu|file"
     * @param string $msg English source text
     * @return string original or translated message
     */
    function dpgettext($domain, $context, $msg)
    {
        $msgid = $context . "\004" . $msg;
        $out = dcgettext($domain, $msgid, LC_MESSAGES);
        if ($out == $msgid) {
            return $msg;
        } else {
            return $out;
        }
    }
}

if (!function_exists('pgettext')) {
    /**
     * Context-aware gettext wrapper; use when messages in different contexts
     * won't be distinguished from the English source but need different translations.
     * The context string will appear as msgctxt in the .po files.
     *
     * Not currently exposed in PHP's gettext module; implemented to be compat
     * with gettext.h's macros.
     *
     * @param string $context context identifier, should be some key like "menu|file"
     * @param string $msgid English source text
     * @return string original or translated message
     */
    function pgettext($context, $msgid)
    {
        return dpgettext(textdomain(NULL), $context, $msgid);
    }
}

if (!function_exists('dnpgettext')) {
    /**
     * Context-aware dngettext wrapper; use when messages in different contexts
     * won't be distinguished from the English source but need different translations.
     * The context string will appear as msgctxt in the .po files.
     *
     * Not currently exposed in PHP's gettext module; implemented to be compat
     * with gettext.h's macros.
     *
     * @param string $domain domain identifier
     * @param string $context context identifier, should be some key like "menu|file"
     * @param string $msg singular English source text
     * @param string $plural plural English source text
     * @param int $n number of items to control plural selection
     * @return string original or translated message
     */
    function dnpgettext($domain, $context, $msg, $plural, $n)
    {
        $msgid = $context . "\004" . $msg;
        $out = dcngettext($domain, $msgid, $plural, $n, LC_MESSAGES);
        if ($out == $msgid) {
            return $msg;
        } else {
            return $out;
        }
    }
}

if (!function_exists('npgettext')) {
    /**
     * Context-aware ngettext wrapper; use when messages in different contexts
     * won't be distinguished from the English source but need different translations.
     * The context string will appear as msgctxt in the .po files.
     *
     * Not currently exposed in PHP's gettext module; implemented to be compat
     * with gettext.h's macros.
     *
     * @param string $context context identifier, should be some key like "menu|file"
     * @param string $msgid singular English source text
     * @param string $plural plural English source text
     * @param int $n number of items to control plural selection
     * @return string original or translated message
     */
    function npgettext($context, $msgid, $plural, $n)
    {
        return dnpgettext(textdomain(NULL), $msgid, $plural, $n, LC_MESSAGES);
    }
}

/**
 * Shortcut for *gettext functions with smart domain detection.
 *
 * If calling from a plugin, this function checks which plugin was
 * being called from and uses that as text domain, which will have
 * been set up during plugin initialization.
 *
 * Also handles plurals and contexts depending on what parameters
 * are passed to it:
 *
 *   gettext -> _m($msg)
 *  ngettext -> _m($msg1, $msg2, $n)
 *  pgettext -> _m($ctx, $msg)
 * npgettext -> _m($ctx, $msg1, $msg2, $n)
 *
 * @fixme may not work properly in eval'd code
 *
 * @param string $msg
 * @return string
 * @throws Exception
 */
function _m($msg/*, ...*/)
{
    $domain = _mdomain(debug_backtrace());
    $args = func_get_args();
    switch (count($args)) {
        case 1:
            return dgettext($domain, $msg);
        case 2:
            return dpgettext($domain, $args[0], $args[1]);
        case 3:
            return dngettext($domain, $args[0], $args[1], $args[2]);
        case 4:
            return dnpgettext($domain, $args[0], $args[1], $args[2], $args[3]);
        default:
            throw new Exception("Bad parameter count to _m()");
    }
}

/**
 * Looks for which plugin we've been called from to set the gettext domain;
 * if not in a plugin subdirectory, we'll use the default 'statusnet'.
 *
 * Note: we can't return null for default domain since most of the PHP gettext
 * wrapper functions turn null into "" before passing to the backend library.
 *
 * @param array $backtrace debug_backtrace() output
 * @return string
 * @private
 * @fixme could explode if SN is under a 'plugins' folder or share name.
 */
function _mdomain($backtrace)
{
    /*
      0 =>
        array
          'file' => string '/var/www/mublog/plugins/FeedSub/FeedSubPlugin.php' (length=49)
          'line' => int 77
          'function' => string '_m' (length=2)
          'args' =>
            array
              0 => &string 'Feeds' (length=5)
    */
    static $cached;
    $path = $backtrace[0]['file'];
    if (!isset($cached[$path])) {
        if (DIRECTORY_SEPARATOR !== '/') {
            $path = strtr($path, DIRECTORY_SEPARATOR, '/');
        }
        $plug = strpos($path, '/plugins/');
        if ($plug === false) {
            // We're not in a plugin; return default domain.
            $final = 'statusnet';
        } else {
            $cut = $plug + 9;
            $cut2 = strpos($path, '/', $cut);
            if ($cut2) {
                $final = substr($path, $cut, $cut2 - $cut);
            } else {
                // We might be running directly from the plugins dir?
                // If so, there's no place to store locale info.
                $final = 'statusnet';
            }
        }
        $cached[$path] = $final;
    }
    return $cached[$path];
}

/**
 * Content negotiation for language codes
 *
 * @param $http_accept_lang_header string HTTP Accept-Language header
 * @return string language code for best language match, false otherwise
 */

function client_preferred_language($http_accept_lang_header)
{
    $client_langs = [];

    $all_languages = common_config('site', 'languages');

    preg_match_all('"(((\S\S)-?(\S\S)?)(;q=([0-9.]+))?)\s*(,\s*|$)"',
        strtolower($http_accept_lang_header), $http_langs);

    for ($i = 0; $i < count($http_langs); ++$i) {
        if (!empty($http_langs[2][$i])) {
            // if no q default to 1.0
            $client_langs[$http_langs[2][$i]] =
                ($http_langs[6][$i] ? (float)$http_langs[6][$i] : 1.0 - ($i * 0.01));
        }
        if (!empty($http_langs[3][$i]) && empty($client_langs[$http_langs[3][$i]])) {
            // if a catchall default 0.01 lower
            $client_langs[$http_langs[3][$i]] =
                ($http_langs[6][$i] ? (float)$http_langs[6][$i] - 0.01 : 0.99);
        }
    }
    // sort in descending q
    arsort($client_langs);

    foreach ($client_langs as $lang => $q) {
        if (isset($all_languages[$lang])) {
            return ($all_languages[$lang]['lang']);
        }
    }
    return false;
}

/**
 * returns a simple code -> name mapping for languages
 *
 * @return array map of available languages by code to language name.
 */

function get_nice_language_list()
{
    $nice_lang = [];

    $all_languages = common_config('site', 'languages');

    foreach ($all_languages as $lang) {
        $nice_lang = $nice_lang + array($lang['lang'] => $lang['name']);
    }
    return $nice_lang;
}

/*
 * Check whether a language is right-to-left
 *
 * @param string $lang language code of the language to check
 *
 * @return boolean true if language is rtl
 */

function is_rtl($lang_value)
{
        foreach (common_config('site', 'languages') as $code => $info) {
                if ($lang_value == $info['lang']) {
                        return $info['direction'] == 'rtl';
                }
        }
}

/**
 * Get a list of all languages that are enabled in the default config
 *
 * This should ONLY be called when setting up the default config in common.php.
 * Any other attempt to get a list of languages should instead call
 * common_config('site','languages')
 *
 * @return array mapping of language codes to language info
 */
function get_all_languages()
{
    return [
        'af'        => ['q' => 0.8, 'lang' => 'af', 'name' => 'Afrikaans', 'direction' => 'ltr'],
        'ar'        => ['q' => 0.8, 'lang' => 'ar', 'name' => 'Arabic', 'direction' => 'rtl'],
        'ast'       => ['q' => 1, 'lang' => 'ast', 'name' => 'Asturian', 'direction' => 'ltr'],
        'eu'        => ['q' => 1, 'lang' => 'eu', 'name' => 'Basque', 'direction' => 'ltr'],
        'be-tarask' => ['q' => 0.5, 'lang' => 'be-tarask', 'name' => 'Belarusian (Taraškievica orthography)', 'direction' => 'ltr'],
        'br'        => ['q' => 0.8, 'lang' => 'br', 'name' => 'Breton', 'direction' => 'ltr'],
        'bg'        => ['q' => 0.8, 'lang' => 'bg', 'name' => 'Bulgarian', 'direction' => 'ltr'],
        'my'        => ['q' => 1, 'lang' => 'my', 'name' => 'Burmese', 'direction' => 'ltr'],
        'ca'        => ['q' => 0.5, 'lang' => 'ca', 'name' => 'Catalan', 'direction' => 'ltr'],
        'zh-cn'     => ['q' => 0.9, 'lang' => 'zh_CN', 'name' => 'Chinese (Simplified)', 'direction' => 'ltr'],
        'zh-hant'   => ['q' => 0.2, 'lang' => 'zh_TW', 'name' => 'Chinese (Taiwanese)', 'direction' => 'ltr'],
        'ksh'       => ['q' => 1, 'lang' => 'ksh', 'name' => 'Colognian', 'direction' => 'ltr'],
        'cs'        => ['q' => 0.5, 'lang' => 'cs', 'name' => 'Czech', 'direction' => 'ltr'],
        'da'        => ['q' => 0.8, 'lang' => 'da', 'name' => 'Danish', 'direction' => 'ltr'],
        'nl'        => ['q' => 0.5, 'lang' => 'nl', 'name' => 'Dutch', 'direction' => 'ltr'],
        'arz'       => ['q' => 0.8, 'lang' => 'arz', 'name' => 'Egyptian Spoken Arabic', 'direction' => 'rtl'],
        'en'        => ['q' => 1, 'lang' => 'en', 'name' => 'English', 'direction' => 'ltr'],
        'en-us'     => ['q' => 1, 'lang' => 'en', 'name' => 'English (US)', 'direction' => 'ltr'],
        'en-gb'     => ['q' => 1, 'lang' => 'en_GB', 'name' => 'English (UK)', 'direction' => 'ltr'],
        'eo'        => ['q' => 0.8, 'lang' => 'eo', 'name' => 'Esperanto', 'direction' => 'ltr'],
        'fi'        => ['q' => 1, 'lang' => 'fi', 'name' => 'Finnish', 'direction' => 'ltr'],
        'fr'        => ['q' => 1, 'lang' => 'fr', 'name' => 'French', 'direction' => 'ltr'],
        'fr-fr'     => ['q' => 1, 'lang' => 'fr', 'name' => 'French (France)', 'direction' => 'ltr'],
        'fur'       => ['q' => 0.8, 'lang' => 'fur', 'name' => 'Friulian', 'direction' => 'ltr'],
        'gl'        => ['q' => 0.8, 'lang' => 'gl', 'name' => 'Galician', 'direction' => 'ltr'],
        'ka'        => ['q' => 0.8, 'lang' => 'ka', 'name' => 'Georgian', 'direction' => 'ltr'],
        'de'        => ['q' => 0.8, 'lang' => 'de', 'name' => 'German', 'direction' => 'ltr'],
        'el'        => ['q' => 0.1, 'lang' => 'el', 'name' => 'Greek', 'direction' => 'ltr'],
        'he'        => ['q' => 0.5, 'lang' => 'he', 'name' => 'Hebrew', 'direction' => 'rtl'],
        'hu'        => ['q' => 0.8, 'lang' => 'hu', 'name' => 'Hungarian', 'direction' => 'ltr'],
        'is'        => ['q' => 0.1, 'lang' => 'is', 'name' => 'Icelandic', 'direction' => 'ltr'],
        'id'        => ['q' => 1, 'lang' => 'id', 'name' => 'Indonesian', 'direction' => 'ltr'],
        'ia'        => ['q' => 0.8, 'lang' => 'ia', 'name' => 'Interlingua', 'direction' => 'ltr'],
        'ga'        => ['q' => 0.5, 'lang' => 'ga', 'name' => 'Irish', 'direction' => 'ltr'],
        'it'        => ['q' => 1, 'lang' => 'it', 'name' => 'Italian', 'direction' => 'ltr'],
        'ja'        => ['q' => 0.5, 'lang' => 'ja', 'name' => 'Japanese', 'direction' => 'ltr'],
        'ko'        => ['q' => 0.9, 'lang' => 'ko', 'name' => 'Korean', 'direction' => 'ltr'],
        'lv'        => ['q' => 1, 'lang' => 'lv', 'name' => 'Latvian', 'direction' => 'ltr'],
        'lt'        => ['q' => 1, 'lang' => 'lt', 'name' => 'Lithuanian', 'direction' => 'ltr'],
        'lb'        => ['q' => 1, 'lang' => 'lb', 'name' => 'Luxembourgish', 'direction' => 'ltr'],
        'mk'        => ['q' => 0.5, 'lang' => 'mk', 'name' => 'Macedonian', 'direction' => 'ltr'],
        'mg'        => ['q' => 1, 'lang' => 'mg', 'name' => 'Malagasy', 'direction' => 'ltr'],
        'ms'        => ['q' => 1, 'lang' => 'ms', 'name' => 'Malay', 'direction' => 'ltr'],
        'ml'        => ['q' => 0.5, 'lang' => 'ml', 'name' => 'Malayalam', 'direction' => 'ltr'],
        'ne'        => ['q' => 1, 'lang' => 'ne', 'name' => 'Nepali', 'direction' => 'ltr'],
        'nb'        => ['q' => 0.1, 'lang' => 'nb', 'name' => 'Norwegian (Bokmål)', 'direction' => 'ltr'],
        'no'        => ['q' => 0.1, 'lang' => 'nb', 'name' => 'Norwegian (Bokmål)', 'direction' => 'ltr'],
        'nn'        => ['q' => 1, 'lang' => 'nn', 'name' => 'Norwegian (Nynorsk)', 'direction' => 'ltr'],
        'fa'        => ['q' => 1, 'lang' => 'fa', 'name' => 'Persian', 'direction' => 'rtl'],
        'pl'        => ['q' => 0.5, 'lang' => 'pl', 'name' => 'Polish', 'direction' => 'ltr'],
        'pt'        => ['q' => 1, 'lang' => 'pt', 'name' => 'Portuguese', 'direction' => 'ltr'],
        'pt-br'     => ['q' => 0.9, 'lang' => 'pt_BR', 'name' => 'Brazilian Portuguese', 'direction' => 'ltr'],
        'ru'        => ['q' => 0.9, 'lang' => 'ru', 'name' => 'Russian', 'direction' => 'ltr'],
        'sr-ec'     => ['q' => 1, 'lang' => 'sr-ec', 'name' => 'Serbian', 'direction' => 'ltr'],
        'es'        => ['q' => 1, 'lang' => 'es', 'name' => 'Spanish', 'direction' => 'ltr'],
        'sv'        => ['q' => 0.8, 'lang' => 'sv', 'name' => 'Swedish', 'direction' => 'ltr'],
        'tl'        => ['q' => 0.8, 'lang' => 'tl', 'name' => 'Tagalog', 'direction' => 'ltr'],
        'ta'        => ['q' => 1, 'lang' => 'ta', 'name' => 'Tamil', 'direction' => 'ltr'],
        'te'        => ['q' => 0.3, 'lang' => 'te', 'name' => 'Telugu', 'direction' => 'ltr'],
        'tr'        => ['q' => 0.5, 'lang' => 'tr', 'name' => 'Turkish', 'direction' => 'ltr'],
        'uk'        => ['q' => 1, 'lang' => 'uk', 'name' => 'Ukrainian', 'direction' => 'ltr'],
        'hsb'       => ['q' => 0.8, 'lang' => 'hsb', 'name' => 'Upper Sorbian', 'direction' => 'ltr'],
        'ur'        => ['q' => 1, 'lang' => 'ur_PK', 'name' => 'Urdu (Pakistan)', 'direction' => 'rtl'],
        'vi'        => ['q' => 0.8, 'lang' => 'vi', 'name' => 'Vietnamese', 'direction' => 'ltr'],
    ];
}


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

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

/**
 * class for locations
 *
 * These are stored in the DB as part of notice and profile records,
 * but since they're about the same in both, we have a separate class
 * for them.
 *
 * @category Location
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */

class Location
{
    public  $lat;
    public  $lon;
    public  $location_id;
    public  $location_ns;
    private $_url;
    private $_rdfurl;

    var $names = array();

    /**
     * Constructor that makes a Location from Notice::locationOptions(...)
     *
     * @param array $options    an array for example provided by Notice::locationOptions(...)
     *
     * @return Location Location with the given options (lat, lon, id, name)
     */
    static function fromOptions(array $options) {
        $location = new Location();
        foreach (['lat', 'lon', 'location_id', 'location_ns'] as $opt) {
            if (isset($options[$opt])) {
                $location->$opt = $options[$opt];
            }
        }
        return $location;
    }

    /**
     * Constructor that makes a Location from a string name
     *
     * @param string $name     Human-readable name (any kind)
     * @param string $language Language, default = common_language()
     *
     * @return Location Location with that name (or null if not found)
     */

    static function fromName($name, $language=null)
    {
        if (is_null($language)) {
            $language = common_language();
        }

        $location = null;

        // Let a third-party handle it

        Event::handle('LocationFromName', array($name, $language, &$location));

        return $location;
    }

    /**
     * Constructor that makes a Location from an ID
     *
     * @param integer $id       Identifier ID
     * @param integer $ns       Namespace of the identifier
     * @param string  $language Language to return name in (default is common)
     *
     * @return Location The location with this ID (or null if none)
     */

    static function fromId($id, $ns, $language=null)
    {
        if (is_null($language)) {
            $language = common_language();
        }

        $location = null;

        // Let a third-party handle it

        Event::handle('LocationFromId', array($id, $ns, $language, &$location));

        return $location;
    }

    /**
     * Constructor that finds the nearest location to a lat/lon pair
     *
     * @param float  $lat      Latitude
     * @param float  $lon      Longitude
     * @param string $language Language for results, default = current
     *
     * @return Location the location found, or null if none found
     */

    static function fromLatLon($lat, $lon, $language=null)
    {
        if (is_null($language)) {
            $language = common_language();
        }

        $location = null;

        // Let a third-party handle it

        if (Event::handle('LocationFromLatLon',
                          array($lat, $lon, $language, &$location))) {
            // Default is just the lat/lon pair

            $location = new Location();

            $location->lat = $lat;
            $location->lon = $lon;
        }

        return $location;
    }

    /**
     * Get the name for this location in the given language
     *
     * @param string $language language to use, default = current
     *
     * @return string location name or null if not found
     */

    function getName($language=null)
    {
        if (is_null($language)) {
            $language = common_language();
        }

        if (array_key_exists($language, $this->names)) {
            return $this->names[$language];
        } else {
            $name = null;
            Event::handle('LocationNameLanguage', array($this, $language, &$name));
            if (!empty($name)) {
                $this->names[$language] = $name;
                return $name;
            }
        }
    }

    /**
     * Get an URL suitable for this location
     *
     * @return string URL for this location or NULL
     */

    function getURL()
    {
        // Keep one cached

        if (is_string($this->_url)) {
            return $this->_url;
        }

        $url = null;

        Event::handle('LocationUrl', array($this, &$url));

        $this->_url = $url;

        return $url;
    }

    /**
     * Get an URL for this location, suitable for embedding in RDF
     *
     * @return string URL for this location or NULL
     */

    function getRdfURL()
    {
        // Keep one cached

        if (is_string($this->_rdfurl)) {
            return $this->_rdfurl;
        }

        $url = null;

        Event::handle('LocationRdfUrl', array($this, &$url));

        $this->_rdfurl = $url;

        return $url;
    }
}


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/>.

/**
 * Utilities for sending email
 *
 * @category  Mail
 * @package   GNUsocial
 * @author    Evan Prodromou <evan@status.net>
 * @author    Zach Copley <zach@status.net>
 * @author    Robin Millette <millette@status.net>
 * @author    Sarven Capadisli <csarven@status.net>
 * @copyright 2008 StatusNet, Inc.
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

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

require_once 'Mail.php';

/**
 * return the configured mail backend
 *
 * Uses the $config array to make a mail backend. Cached so it is safe to call
 * more than once.
 *
 * @return Mail backend
 */
function mail_backend()
{
    static $backend = null;
    global $_PEAR;

    if (!$backend) {
        $mail = new Mail();
        $backend = $mail->factory(
            common_config('mail', 'backend'),
            common_config('mail', 'params') ?: []
        );
        if ($_PEAR->isError($backend)) {
            throw new EmailException($backend->getMessage(), $backend->getCode());
        }
    }
    return $backend;
}

/**
 * send an email to one or more recipients
 *
 * @param array  $recipients array of strings with email addresses of recipients
 * @param array  $headers    array mapping strings to strings for email headers
 * @param string $body       body of the email
 *
 * @return boolean success flag
 */
function mail_send($recipients, $headers, $body)
{
    global $_PEAR;

    try {
        // XXX: use Mail_Queue... maybe
        $backend = mail_backend();

        if (!isset($headers['Content-Type'])) {
            $headers['Content-Type'] = 'text/plain; charset=UTF-8';
        }

        assert($backend); // throws an error if it's bad
        $sent = $backend->send($recipients, $headers, $body);
        if ($_PEAR->isError($sent)) {
            throw new EmailException($sent->getMessage(), $sent->getCode());
        }
        return true;
    } catch (PEAR_Exception $e) {
        common_log(
            LOG_ERR,
            "Unable to send email - '{$e->getMessage()}'. "
            . 'Is your mail subsystem set up correctly?'
        );
        return false;
    }
}

/**
 * returns the configured mail domain
 *
 * Defaults to the server name.
 *
 * @return string mail domain, suitable for making email addresses.
 */
function mail_domain()
{
    $maildomain = common_config('mail', 'domain');
    if (!$maildomain) {
        $maildomain = common_config('site', 'server');
    }
    return $maildomain;
}

/**
 * returns a good address for sending email from this server
 *
 * Uses either the configured value or a faked-up value made
 * from the mail domain.
 *
 * @return string notify from address
 */
function mail_notify_from()
{
    $notifyfrom = common_config('mail', 'notifyfrom');

    if (!$notifyfrom) {
        $domain = mail_domain();

        $notifyfrom = '"'. str_replace('"', '\\"', common_config('site', 'name')) .'" <noreply@'.$domain.'>';
    }

    return $notifyfrom;
}

/**
 * sends email to a user
 *
 * @param User   &$user   user to send email to
 * @param string $subject subject of the email
 * @param string $body    body of the email
 * @param array  $headers optional list of email headers
 * @param string $address optional specification of email address
 *
 * @return boolean success flag
 */
function mail_to_user($user, $subject, $body, $headers=array(), $address=null)
{
    if (!$address) {
        $address = $user->email;
    }

    $recipients = $address;
    $profile    = $user->getProfile();

    $headers['Date']    = date("r", time());
    $headers['From']    = mail_notify_from();
    $headers['To']      = $profile->getBestName() . ' <' . $address . '>';
    $headers['Subject'] = $subject;

    return mail_send($recipients, $headers, $body);
}

/**
 * notify a user of subscription by another user
 *
 * This is just a wrapper around the profile-based version.
 *
 * @param User $listenee user who is being subscribed to
 * @param User $listener user who is subscribing
 *
 * @see mail_subscribe_notify_profile()
 *
 * @return void
 */
function mail_subscribe_notify($listenee, $listener)
{
    $other = $listener->getProfile();
    mail_subscribe_notify_profile($listenee, $other);
}

/**
 * notify a user of subscription by a profile (remote or local)
 *
 * This function checks to see if the listenee has an email
 * address and wants subscription notices.
 *
 * @param User    $listenee user who's being subscribed to
 * @param Profile $other    profile of person who's listening
 *
 * @return void
 */
function mail_subscribe_notify_profile($listenee, $other)
{
    if ($other->hasRight(Right::EMAILONSUBSCRIBE) &&
        $listenee->email && $listenee->emailnotifysub) {
        $profile = $listenee->getProfile();

        $name = $profile->getBestName();

        $long_name = $other->getFancyName();

        $recipients = $listenee->email;

        // use the recipient's localization
        common_switch_locale($listenee->language);

        $headers = _mail_prepare_headers('subscribe', $listenee->nickname, $other->nickname);
        $headers['From']    = mail_notify_from();
        $headers['To']      = $name . ' <' . $listenee->email . '>';
        // TRANS: Subject of new-subscriber notification e-mail.
        // TRANS: %1$s is the subscribing user's nickname, %2$s is the StatusNet sitename.
        $headers['Subject'] = sprintf(
            _('%1$s is now following you on %2$s.'),
            $other->getBestName(),
            common_config('site', 'name')
        );

        // TRANS: Main body of new-subscriber notification e-mail.
        // TRANS: %1$s is the subscriber's long name, %2$s is the StatusNet sitename.
        $body = sprintf(
            _('%1$s is now following you on %2$s.'),
            $long_name,
            common_config('site', 'name')
        ) .
                mail_profile_block($other) .
                mail_footer_block();

        // reset localization
        common_switch_locale();
        mail_send($recipients, $headers, $body);
    }
}

function mail_subscribe_pending_notify_profile($listenee, $other)
{
    if ($other->hasRight(Right::EMAILONSUBSCRIBE) &&
        $listenee->email && $listenee->emailnotifysub) {
        $profile = $listenee->getProfile();

        $name = $profile->getBestName();

        $long_name = ($other->fullname) ?
          ($other->fullname . ' (' . $other->nickname . ')') : $other->nickname;

        $recipients = $listenee->email;

        // use the recipient's localization
        common_switch_locale($listenee->language);

        $headers = _mail_prepare_headers('subscribe', $listenee->nickname, $other->nickname);
        $headers['From']    = mail_notify_from();
        $headers['To']      = $name . ' <' . $listenee->email . '>';
        // TRANS: Subject of pending new-subscriber notification e-mail.
        // TRANS: %1$s is the subscribing user's nickname, %2$s is the StatusNet sitename.
        $headers['Subject'] = sprintf(
            _('%1$s would like to listen to '.
                                        'your notices on %2$s.'),
            $other->getBestName(),
            common_config('site', 'name')
        );

        // TRANS: Main body of pending new-subscriber notification e-mail.
        // TRANS: %1$s is the subscriber's long name, %2$s is the StatusNet sitename.
        $body = sprintf(
            _('%1$s would like to listen to your notices on %2$s. ' .
                          'You may approve or reject their subscription at %3$s'),
            $long_name,
            common_config('site', 'name'),
            common_local_url('subqueue', ['nickname' => $listenee->nickname])
        ) .
                mail_profile_block($other) .
                mail_footer_block();

        // reset localization
        common_switch_locale();
        mail_send($recipients, $headers, $body);
    }
}

function mail_footer_block()
{
    // TRANS: Common footer block for StatusNet notification emails.
    // TRANS: %1$s is the StatusNet sitename,
    // TRANS: %2$s is a link to the addressed user's e-mail settings.
    return "\n\n" . sprintf(
        _('Faithfully yours,'.
          "\n".'%1$s.'."\n\n".
          "----\n".
          "Change your email address or ".
          "notification options at ".'%2$s'),
        common_config('site', 'name'),
        common_local_url('emailsettings')
    ) . "\n";
}

/**
 * Format a block of profile info for a plaintext notification email.
 *
 * @param Profile $profile
 * @return string
 */
function mail_profile_block($profile)
{
    // TRANS: Layout for
    // TRANS: %1$s is the subscriber's profile URL, %2$s is the subscriber's location (or empty)
    // TRANS: %3$s is the subscriber's homepage URL (or empty), %4%s is the subscriber's bio (or empty)
    $out = array();
    $out[] = "";
    $out[] = "";
    // TRANS: Profile info line in notification e-mail.
    // TRANS: %s is a URL.
    $out[] = sprintf(_("Profile: %s"), $profile->profileurl);
    if ($profile->location) {
        // TRANS: Profile info line in notification e-mail.
        // TRANS: %s is a location.
        $out[] = sprintf(_("Location: %s"), $profile->location);
    }
    if ($profile->homepage) {
        // TRANS: Profile info line in notification e-mail.
        // TRANS: %s is a homepage.
        $out[] = sprintf(_("Homepage: %s"), $profile->homepage);
    }
    if ($profile->bio) {
        // TRANS: Profile info line in notification e-mail.
        // TRANS: %s is biographical information.
        $out[] = sprintf(_("Bio: %s"), $profile->bio);
    }

    $blocklink = common_local_url('block', array('profileid' => $profile->id));
    // This'll let ModPlus add the remote profile info so it's possible
    // to block remote users directly...
    Event::handle('MailProfileInfoBlockLink', array($profile, &$blocklink));

    // TRANS: This is a paragraph in a new-subscriber e-mail.
    // TRANS: %s is a URL where the subscriber can be reported as abusive.
    $out[] = sprintf(
        _('If you believe this account is being used abusively, ' .
          'you can block them from your subscribers list and ' .
          'report as spam to site administrators at %s.'),
        $blocklink
    );
    $out[] = "";

    return implode("\n", $out);
}

/**
 * notify a user of their new incoming email address
 *
 * User's email and incoming fields should already be updated.
 *
 * @param User $user user with the new address
 *
 * @return void
 */
function mail_new_incoming_notify($user)
{
    $profile = $user->getProfile();

    $name = $profile->getBestName();

    $headers['From']    = $user->incomingemail;
    $headers['To']      = $name . ' <' . $user->email . '>';
    // TRANS: Subject of notification mail for new posting email address.
    // TRANS: %s is the StatusNet sitename.
    $headers['Subject'] = sprintf(
        _('New email address for posting to %s'),
        common_config('site', 'name')
    );

    // TRANS: Body of notification mail for new posting email address.
    // TRANS: %1$s is the StatusNet sitename, %2$s is the e-mail address to send
    // TRANS: to to post by e-mail, %3$s is a URL to more instructions.
    $body = sprintf(
        _("You have a new posting address on %1\$s.\n\n".
          "Send email to %2\$s to post new messages.\n\n".
          "More email instructions at %3\$s."),
        common_config('site', 'name'),
        $user->incomingemail,
        common_local_url('doc', ['title' => 'email'])
    ) .
            mail_footer_block();

    mail_send($user->email, $headers, $body);
}

/**
 * generate a new address for incoming messages
 *
 * @todo check the database for uniqueness
 *
 * @return string new email address for incoming messages
 */
function mail_new_incoming_address()
{
    $prefix = common_confirmation_code(64);
    $suffix = mail_domain();
    return $prefix . '@' . $suffix;
}

/**
 * broadcast a notice to all subscribers with SMS notification on
 *
 * This function sends SMS messages to all users who have sms addresses;
 * have sms notification on; and have sms enabled for this particular
 * subscription.
 *
 * @param Notice $notice The notice to broadcast
 *
 * @return success flag
 */
function mail_broadcast_notice_sms($notice)
{
    // Now, get users subscribed to this profile

    $user = new User();

    $replies = $notice->getReplies();

    $repliesQry = '';
    if (!empty($replies)) {
        $repliesQry = sprintf(
            'OR %s.id IN (%s)',
            $user->escapedTableName(),
            implode(',', $replies)
        );
    }

    $user->query(sprintf(
        'SELECT nickname, smsemail, incomingemail ' .
        'FROM %1$s LEFT JOIN subscription ' .
        'ON %1$s.id = subscription.subscriber ' .
        'AND subscription.subscribed = %2$d ' .
        'AND subscription.subscribed <> subscription.subscriber ' .
        // Users (other than the sender) who `want SMS notices':
        'WHERE %1$s.id <> %2$d ' .
        'AND %1$s.smsemail IS NOT NULL ' .
        'AND %1$s.smsnotify = TRUE ' .
        // ... where either the user _is_ subscribed to the sender
        // (any of the "subscription" fields IS NOT NULL)
        // and wants to get SMS for all of this scribe's notices...
        'AND (subscription.sms = TRUE ' .
        // ... or where the user was mentioned in
        // or replied-to with the notice:
        $repliesQry .
        ')',
        $user->escapedTableName(),
        $notice->profile_id
    ));

    while ($user->fetch()) {
        common_log(
            LOG_INFO,
            'Sending notice ' . $notice->id . ' to ' . $user->smsemail,
            __FILE__
        );
        $success = mail_send_sms_notice_address(
            $notice,
            $user->smsemail,
            $user->incomingemail,
            $user->nickname
        );
        if (!$success) {
            // XXX: Not sure, but I think that's the right thing to do
            common_log(
                LOG_WARNING,
                'Sending notice ' . $notice->id . ' to ' . $user->smsemail . ' FAILED, canceling.',
                __FILE__
            );
            return false;
        }
    }

    $user->free();
    unset($user);

    return true;
}

/**
 * send a notice to a user via SMS
 *
 * A convenience wrapper around mail_send_sms_notice_address()
 *
 * @param Notice $notice notice to send
 * @param User   $user   user to receive notice
 *
 * @see mail_send_sms_notice_address()
 *
 * @return boolean success flag
 */
function mail_send_sms_notice($notice, $user)
{
    return mail_send_sms_notice_address(
        $notice,
        $user->smsemail,
        $user->incomingemail,
        $user->nickname
    );
}

/**
 * send a notice to an SMS email address from a given address
 *
 * We use the user's incoming email address as the "From" address to make
 * replying to notices easier.
 *
 * @param Notice $notice        notice to send
 * @param string $smsemail      email address to send to
 * @param string $incomingemail email address to set as 'from'
 * @param string $nickname      nickname to add to beginning
 *
 * @return boolean success flag
 */
function mail_send_sms_notice_address($notice, $smsemail, $incomingemail, $nickname)
{
    $to = $nickname . ' <' . $smsemail . '>';

    $other = $notice->getProfile();

    common_log(LOG_INFO, 'Sending notice ' . $notice->id .
               ' to ' . $smsemail, __FILE__);

    $headers = array();

    $headers['From']    = ($incomingemail) ? $incomingemail : mail_notify_from();
    $headers['To']      = $to;
    // TRANS: Subject line for SMS-by-email notification messages.
    // TRANS: %s is the posting user's nickname.
    $headers['Subject'] = sprintf(_('%s status'), $other->getBestName());

    $body = $notice->content;

    return mail_send($smsemail, $headers, $body);
}

/**
 * send a message to confirm a claim for an SMS number
 *
 * @param string $code     confirmation code
 * @param string $nickname nickname of user claiming number
 * @param string $address  email address to send the confirmation to
 *
 * @see common_confirmation_code()
 *
 * @return void
 */
function mail_confirm_sms($code, $nickname, $address)
{
    $recipients = $address;

    $headers['From']    = mail_notify_from();
    $headers['To']      = $nickname . ' <' . $address . '>';
    // TRANS: Subject line for SMS-by-email address confirmation message.
    $headers['Subject'] = _('SMS confirmation');

    // TRANS: Main body heading for SMS-by-email address confirmation message.
    // TRANS: %s is the addressed user's nickname.
    $body  = sprintf(_('%s: confirm you own this phone number with this code:'), $nickname);
    $body .= "\n\n";
    $body .= $code;
    $body .= "\n\n";

    mail_send($recipients, $headers, $body);
}

/**
 * send a mail message to notify a user of a 'nudge'
 *
 * @param User $from user nudging
 * @param User $to   user being nudged
 *
 * @return boolean success flag
 */
function mail_notify_nudge($from, $to)
{
    common_switch_locale($to->language);
    // TRANS: Subject for 'nudge' notification email.
    // TRANS: %s is the nudging user.
    $subject = sprintf(_('You have been nudged by %s'), $from->nickname);

    $from_profile = $from->getProfile();

    // TRANS: Body for 'nudge' notification email.
    // TRANS: %1$s is the nuding user's long name, $2$s is the nudging user's nickname,
    // TRANS: %3$s is a URL to post notices at.
    $body = sprintf(
        _('%1$s (%2$s) is wondering what you are up to ' .
          "these days and is inviting you to post some news.\n\n" .
          "So let's hear from you :)\n\n" .
          "%3\$s\n\n" .
          "Don't reply to this email; it won't get to them."),
        $from_profile->getBestName(),
        $from->nickname,
        common_local_url('all', ['nickname' => $to->nickname])
    ) .
            mail_footer_block();
    common_switch_locale();

    $headers = _mail_prepare_headers('nudge', $to->nickname, $from->nickname);

    return mail_to_user($to, $subject, $body, $headers);
}

/**
 * send a message to notify a user of a direct message (DM)
 *
 * This function checks to see if the recipient wants notification
 * of DMs and has a configured email address.
 *
 * @param Notice $message message to notify about
 * @param User   $from    user sending message
 * @param array  $to      users receiving the message
 *
 * @return boolean success code
 */
function mail_notify_message(Notice $message, Profile $from = null, ?array $to = null)
{
    if (is_null($from)) {
        $from = $message->getProfile();
    }

    if (is_null($to)) {
        $to = [];
        foreach ($message->getAttentionProfiles() as $attention) {
            if ($attention->isLocal()) {
                $to[] = $attention;
            }
        }
    }

    $success = true;

    foreach ($to as $t) {
        if (is_null($t->email) || !$t->emailnotifymsg) {
            continue;
        }

        common_switch_locale($t->language);
        // TRANS: Subject for direct-message notification email.
        // TRANS: %s is the sending user's nickname.
        $subject = sprintf(_('New private message from %s'), $from->getNickname());

        // TRANS: Body for direct-message notification email.
        // TRANS: %1$s is the sending user's long name, %2$s is the sending user's nickname,
        // TRANS: %3$s is the message content, %4$s a URL to the message,
        $body = sprintf(
            _("%1\$s (%2\$s) sent you a private message:\n\n".
              "------------------------------------------------------\n".
              "%3\$s\n".
              "------------------------------------------------------\n\n".
              "You can reply to their message here:\n\n".
              "%4\$s\n\n".
              "Don't reply to this email; it won't get to them."),
            $from->getBestName(),
            $from->getNickname(),
            $message->getContent(),
            common_local_url('newmessage', ['to' => $from->getID()])
        ) .
                mail_footer_block();

        $headers = _mail_prepare_headers('message', $t->getNickname(), $from->getNickname());

        common_switch_locale();

        if (!mail_to_user($t, $subject, $body, $headers)) {
            common_log(LOG_ERR, "Failed to notify user:{$t->getID()} about the new message:{$message->getID()} sent by user:{$from->getID()}");
            $success = false;
        }
    }

    return $success;
}

/**
 * Notify a user that they have received an "attn:" message AKA "@-reply"
 *
 * @param Profile $rcpt  The Profile who recevied the notice, should be a local user
 * @param Notice $notice The notice that was sent
 *
 * @return void
 */
function mail_notify_attn(Profile $rcpt, Notice $notice)
{
    if (!$rcpt->isLocal()) {
        return;
    }

    $sender = $notice->getProfile();
    if ($rcpt->sameAs($sender)) {
        return;
    }

    // See if the notice's author who mentions this user is sandboxed
    if (!$sender->hasRight(Right::EMAILONREPLY)) {
        return;
    }

    // If the author has blocked the author, don't spam them with a notification.
    if ($rcpt->hasBlocked($sender)) {
        return;
    }

    $user = $rcpt->getUser();
    if (!$user->receivesEmailNotifications()) {
        return;
    }

    common_switch_locale($user->language);

    if ($notice->hasConversation()) {
        $conversationUrl = common_local_url(
            'conversation',
            ['id' => $notice->conversation]
        ) . '#notice-'.$notice->getID();

        // TRANS: Line in @-reply notification e-mail. %s is conversation URL.
        $conversationEmailText = sprintf(_("The full conversation can be read here:\n\n".
                                           "\t%s"), $conversationUrl) . "\n\n";
    } else {
        $conversationEmailText = '';
    }

    // TRANS: E-mail subject for notice notification.
    // TRANS: %1$s is the "fancy name" for a profile.
    $subject = sprintf(_('%1$s sent a notice to your attention'), $sender->getFancyName());

    // TRANS: Body of @-reply notification e-mail.
    // TRANS: %1$s is the sending user's name, $2$s is the GNU social sitename,
    // TRANS: %3$s is a URL to the notice, %4$s is the notice text,
    // TRANS: %5$s is the text "The full conversation can be read here:" and a URL to the full conversion if it exists (otherwise empty),
    // TRANS: %6$s is a URL to reply to the notice, %7$s is a URL to all @-replies for the addressed user,
    $body = sprintf(
        _("%1\$s just sent a notice to your attention (an '@-reply') on %2\$s.\n\n".
          "The notice is here:\n\n".
          "\t%3\$s\n\n" .
          "It reads:\n\n".
          "\t%4\$s\n\n" .
          "%5\$s" .
          "You can reply back here:\n\n".
          "\t%6\$s\n\n" .
          "The list of all @-replies for you here:\n\n" .
          "%7\$s"),
        $sender->getFancyName(), //%1
        common_config('site', 'name'), //%2
        common_local_url(
            'shownotice',
            ['notice' => $notice->getID()]
        ), //%3
        $notice->getContent(), //%4
        $conversationEmailText, //%5
        common_local_url(
            'newnotice',
            ['replyto' => $sender->getNickname(), 'inreplyto' => $notice->getID()]
        ), //%6
        common_local_url(
            'replies',
            ['nickname' => $rcpt->getNickname()]
        )
    ) . //%7
            mail_footer_block();
    $headers = _mail_prepare_headers('mention', $rcpt->getNickname(), $sender->getNickname());

    common_switch_locale();
    mail_to_user($user, $subject, $body, $headers);
}

/**
 * Prepare the common mail headers used in notification emails
 *
 * @param string $msg_type type of message being sent to the user
 * @param string $to       nickname of the receipient
 * @param string $from     nickname of the user triggering the notification
 *
 * @return array list of mail headers to include in the message
 */
function _mail_prepare_headers($msg_type, $to, $from)
{
    $headers = array(
        'X-StatusNet-MessageType' => $msg_type,
        'X-StatusNet-TargetUser'  => $to,
        'X-StatusNet-SourceUser'  => $from,
        'X-StatusNet-Domain'      => common_config('site', 'server')
    );

    return $headers;
}

/**
 * Send notification emails to group administrator.
 *
 * @param User_group $group
 * @param Profile $joiner
 */
function mail_notify_group_join($group, $joiner)
{
    // This returns a Profile query...
    $admin = $group->getAdmins();
    while ($admin->fetch()) {
        // We need a local user for email notifications...
        $adminUser = User::getKV('id', $admin->id);
        // @fixme check for email preference?
        if ($adminUser && $adminUser->email) {
            // use the recipient's localization
            common_switch_locale($adminUser->language);

            $headers = _mail_prepare_headers('join', $admin->nickname, $joiner->nickname);
            $headers['From']    = mail_notify_from();
            $headers['To']      = $admin->getBestName() . ' <' . $adminUser->email . '>';
            // TRANS: Subject of group join notification e-mail.
            // TRANS: %1$s is the joining user's nickname, %2$s is the group name, and %3$s is the StatusNet sitename.
            $headers['Subject'] = sprintf(
                _('%1$s has joined your group %2$s on %3$s'),
                $joiner->getBestName(),
                $group->getBestName(),
                common_config('site', 'name')
            );

            // TRANS: Main body of group join notification e-mail.
            // TRANS: %1$s is the subscriber's long name, %2$s is the group name, and %3$s is the StatusNet sitename,
            // TRANS: %4$s is a block of profile info about the subscriber.
            // TRANS: %5$s is a link to the addressed user's e-mail settings.
            $body = sprintf(
                _('%1$s has joined your group %2$s on %3$s.'),
                $joiner->getFancyName(),
                $group->getFancyName(),
                common_config('site', 'name')
            ) .
                    mail_profile_block($joiner) .
                    mail_footer_block();

            // reset localization
            common_switch_locale();
            mail_send($adminUser->email, $headers, $body);
        }
    }
}

/**
 * Send notification emails to group administrator.
 *
 * @param User_group $group
 * @param Profile $joiner
 */
function mail_notify_group_join_pending($group, $joiner)
{
    $admin = $group->getAdmins();
    while ($admin->fetch()) {
        // We need a local user for email notifications...
        $adminUser = User::getKV('id', $admin->id);
        // @fixme check for email preference?
        if ($adminUser && $adminUser->email) {
            // use the recipient's localization
            common_switch_locale($adminUser->language);

            $headers = _mail_prepare_headers('join', $admin->nickname, $joiner->nickname);
            $headers['From']    = mail_notify_from();
            $headers['To']      = $admin->getBestName() . ' <' . $adminUser->email . '>';
            // TRANS: Subject of pending group join request notification e-mail.
            // TRANS: %1$s is the joining user's nickname, %2$s is the group name, and %3$s is the StatusNet sitename.
            $headers['Subject'] = sprintf(
                _('%1$s wants to join your group %2$s on %3$s.'),
                $joiner->getBestName(),
                $group->getBestName(),
                common_config('site', 'name')
            );

            // TRANS: Main body of pending group join request notification e-mail.
            // TRANS: %1$s is the subscriber's long name, %2$s is the group name, and %3$s is the StatusNet sitename,
            // TRANS: %4$s is the URL to the moderation queue page.
            $body = sprintf(
                _('%1$s would like to join your group %2$s on %3$s. ' .
                  'You may approve or reject their group membership at %4$s'),
                $joiner->getFancyName(),
                $group->getFancyName(),
                common_config('site', 'name'),
                common_local_url('groupqueue', ['nickname' => $group->nickname])
            ) .
                    mail_profile_block($joiner) .
                    mail_footer_block();

            // reset localization
            common_switch_locale();
            mail_send($adminUser->email, $headers, $body);
        }
    }
}


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/>.
 */

require_once INSTALLDIR . '/lib/util/mail.php';
require_once('Mail/mimeDecode.php');

// @todo FIXME: we use both Mail_mimeDecode and mailparse
// Need to move everything to mailparse

class MailHandler
{
    function __construct()
    {
    }

    function handle_message($rawmessage)
    {
        list($from, $to, $msg, $attachments) = $this->parse_message($rawmessage);
        if (!$from || !$to || !$msg) {
            // TRANS: Error message in incoming mail handler used when an incoming e-mail cannot be processed.
            $this->error(null, _('Could not parse message.'));
        }
        common_log(LOG_INFO, "Mail from $from to $to with ".count($attachments) .' attachment(s): ' .substr($msg, 0, 20));
        $user = $this->user_from_header($from);
        if (!$user) {
            // TRANS: Error message in incoming mail handler used when an incoming e-mail is not from a registered user.
            $this->error($from, _('Not a registered user.'));
            return false;
        }
        if (!$this->user_match_to($user, $to)) {
            // TRANS: Error message in incoming mail handler used when an incoming e-mail is not from a user's incoming e-mail address.
            $this->error($from, _('Sorry, that is not your incoming email address.'));
            return false;
        }
        if (!$user->emailpost) {
            // TRANS: Error message in incoming mail handler used when no incoming e-mail is allowed.
            $this->error($from, _('Sorry, no incoming email allowed.'));
            return false;
        }
        $response = $this->handle_command($user, $from, $msg);
        if ($response) {
            return true;
        }
        $msg = $this->cleanup_msg($msg);
        $msg = $user->shortenLinks($msg);
        if (Notice::contentTooLong($msg)) {
            // TRANS: Error message in incoming mail handler used when an incoming e-mail contains too many characters.
            $this->error($from, sprintf(_m('That\'s too long. Maximum notice size is %d character.',
                                          'That\'s too long. Maximum notice size is %d characters.',
                                          Notice::maxContent()),
                                        Notice::maxContent()));
        }

        $mediafiles = array();

        foreach($attachments as $attachment){
            $mf = null;

            try {
                $mf = MediaFile::fromFilehandle($attachment, $user->getProfile());
            } catch(ClientException $ce) {
                $this->error($from, $ce->getMessage());
            }

            $msg .= ' ' . $mf->shortUrl();

            array_push($mediafiles, $mf);
            fclose($attachment);
        }

        $err = $this->add_notice($user, $msg, $mediafiles);

        if (is_string($err)) {
            $this->error($from, $err);
            return false;
        } else {
            return true;
        }
    }

    function error($from, $msg)
    {
        file_put_contents("php://stderr", $msg . "\n");
        exit(1);
    }

    function user_from_header($from_hdr)
    {
        $froms = mailparse_rfc822_parse_addresses($from_hdr);
        if (!$froms) {
            return null;
        }
        $from = $froms[0];
        $addr = common_canonical_email($from['address']);
        $user = User::getKV('email', $addr);
        if (!$user) {
            $user = User::getKV('smsemail', $addr);
        }
        return $user;
    }

    function user_match_to($user, $to_hdr)
    {
        $incoming = $user->incomingemail;
        $tos = mailparse_rfc822_parse_addresses($to_hdr);
        foreach ($tos as $to) {
            if (strcasecmp($incoming, $to['address']) == 0) {
                return true;
            }
        }
        return false;
    }

    function handle_command($user, $from, $msg)
    {
        $inter = new CommandInterpreter();
        $cmd = $inter->handle_command($user, $msg);
        if ($cmd) {
            $cmd->execute(new MailChannel($from));
            return true;
        }
        return false;
    }

    function respond($from, $to, $response)
    {
        $headers['From'] = $to;
        $headers['To'] = $from;
        // TRANS: E-mail subject for reply to an e-mail command.
        $headers['Subject'] = _('Command complete');

        return mail_send(array($from), $headers, $response);
    }

    function log($level, $msg)
    {
        common_log($level, 'MailDaemon: '.$msg);
    }

    function add_notice($user, $msg, $mediafiles)
    {
        try {
            $notice = Notice::saveNew($user->id, $msg, 'mail');
        } catch (Exception $e) {
            $this->log(LOG_ERR, $e->getMessage());
            return $e->getMessage();
        }
        foreach($mediafiles as $mf){
            $mf->attachToNotice($notice);
        }

        $this->log(LOG_INFO,
                   'Added notice ' . $notice->id . ' from user ' . $user->nickname);
        return true;
    }

    function parse_message($contents)
    {
        $parsed = Mail_mimeDecode::decode(array('input' => $contents,
                                                'include_bodies' => true,
                                                'decode_headers' => true,
                                                'decode_bodies' => true));
        if (!$parsed) {
            return null;
        }

        $from = $parsed->headers['from'];

        $to = $parsed->headers['to'];

        $type = $parsed->ctype_primary . '/' . $parsed->ctype_secondary;

        $attachments = array();

        $this->extract_part($parsed,$msg,$attachments);

        return array($from, $to, $msg, $attachments);
    }

    function extract_part($parsed,&$msg,&$attachments){
        if ($parsed->ctype_primary == 'multipart') {
            if($parsed->ctype_secondary == 'alternative'){
                $altmsg = $this->extract_msg_from_multipart_alternative_part($parsed);
                if(!empty($altmsg)) $msg = $altmsg;
            }else{
                foreach($parsed->parts as $part){
                    $this->extract_part($part,$msg,$attachments);
                }
            }
        } else if ($parsed->ctype_primary == 'text'
            && $parsed->ctype_secondary=='plain') {
            $msg = $parsed->body;
            if(strtolower($parsed->ctype_parameters['charset']) != "utf-8"){
                $msg = utf8_encode($msg);
            }
        }else if(!empty($parsed->body)){
            if(common_config('attachments', 'uploads')){
                //only save attachments if uploads are enabled
                $attachment = tmpfile();
                fwrite($attachment, $parsed->body);
                $attachments[] = $attachment;
            }
        }
    }

    function extract_msg_from_multipart_alternative_part($parsed){
        foreach ($parsed->parts as $part) {
            $this->extract_part($part,$msg,$attachments);
        }
        //we don't want any attachments that are a result of this parsing
        return $msg;
    }

    function unsupported_type($type)
    {
        // TRANS: Error message in incoming mail handler used when an incoming e-mail is of an unsupported type.
        // TRANS: %s is the unsupported type.
        $this->error(null, sprintf(_('Unsupported message type: %s.'), $type));
    }

    function cleanup_msg($msg)
    {
        $lines = explode("\n", $msg);

        $output = '';

        foreach ($lines as $line) {
            // skip quotes
            if (preg_match('/^\s*>.*$/', $line)) {
                continue;
            }
            // skip start of quote
            if (preg_match('/^\s*On.*wrote:\s*$/', $line)) {
                continue;
            }
            // probably interesting to someone, not us
            if (preg_match('/^\s*Sent via/', $line)) {
                continue;
            }
            if (preg_match('/^\s*Sent from my/', $line)) {
                continue;
            }

            // skip everything after a sig
            if (preg_match('/^\s*--+\s*$/', $line) ||
                preg_match('/^\s*__+\s*$/', $line))
            {
                break;
            }
            // skip everything after Outlook quote
            if (preg_match('/^\s*-+\s*Original Message\s*-+\s*$/', $line)) {
                break;
            }
            // skip everything after weird forward
            if (preg_match('/^\s*Begin\s+forward/', $line)) {
                break;
            }
            // skip everything after a blank line if we already have content
            if ($output !== '' && $line === '') {
                break;
            }

            $output .= ' ' . $line;
        }

        preg_replace('/\s+/', ' ', $output);
        return trim($output);
    }
}


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

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

class ManagedAction extends Action
{
    protected function prepare(array $args = [])
    {
        if (!parent::prepare($args)) {
            return false;
        }
        $this->doPreparation();
        return true;
    }

    protected function doPreparation()
    {
        // pass by default
    }

    /**
     * Handler method
     */
    protected function handle()
    {
        parent::handle();

        if ($this->canPost && $this->isPost()) {
            try {
                $this->msg = $this->handlePost();
            } catch (Exception $e) {
                $this->error = $e->getMessage();
            }
        }

        $this->showPage();
    }

    /**
     * If this is extended in child classes, they should
     * end with 'return parent::handlePost();' - and they
     * should only extend this function if what they do
     * cannot be handled in ->doPost()
     */
    protected function handlePost()
    {
        // This will only be run if the Action has the property canPost==true
        assert($this->canPost);

        // check for this before token since all POST and FILES data
        // is losts when size is exceeded
        if (empty($_POST) && $_SERVER['CONTENT_LENGTH']>0) {
            // TRANS: Client error displayed when the number of bytes in a POST request exceeds a limit.
            // TRANS: %s is the number of bytes of the CONTENT_LENGTH.
            $msg = sprintf(_m('The server was unable to handle that much POST data (%s MiB) due to its current configuration.'),
                      round($_SERVER['CONTENT_LENGTH']/1024/1024,2));
            throw new ClientException($msg);
        }

        $this->checkSessionToken();
        return $this->doPost();
    }

    /**
     * Do Post stuff. Return a string if successful,
     * describing what has been done. Always throw an
     * exception on failure, with a descriptive message.
     */
    protected function doPost() {
        return false;
    }
}


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

/**
 * Exception wrapper for cURL errors
 *
 * @category Integration
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 *
 */
class OAuthClientException extends Exception
{
}

/**
 * Base class for doing OAuth calls as a consumer
 *
 * @category Integration
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 *
 */
class OAuthClient
{
    var $consumer;
    var $token;

    /**
     * Constructor
     *
     * Can be initialized with just consumer key and secret for requesting new
     * tokens or with additional request token or access token
     *
     * @param string $consumer_key       consumer key
     * @param string $consumer_secret    consumer secret
     * @param string $oauth_token        user's token
     * @param string $oauth_token_secret user's secret
     *
     * @return nothing
     */
    function __construct($consumer_key, $consumer_secret,
                         $oauth_token = null, $oauth_token_secret = null)
    {
        $this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1();
        $this->consumer    = new OAuthConsumer($consumer_key, $consumer_secret);
        $this->token       = null;

        if (isset($oauth_token) && isset($oauth_token_secret)) {
            $this->token = new OAuthToken($oauth_token, $oauth_token_secret);
        }
    }

    /**
     * Gets a request token from the given url
     *
     * @param string $url      OAuth endpoint for grabbing request tokens
     * @param string $callback authorized request token callback
     *
     * @return OAuthToken $token the request token
     */
    function getRequestToken($url, $callback = null)
    {
        $params = null;

        if (!is_null($callback)) {
            $params['oauth_callback'] = $callback;
        }

        $response = $this->oAuthGet($url, $params);

        $arr = array();
        parse_str($response, $arr);

        $token   = $arr['oauth_token'];
        $secret  = $arr['oauth_token_secret'];
        $confirm = $arr['oauth_callback_confirmed'];

        if (isset($token) && isset($secret)) {

            $token = new OAuthToken($token, $secret);

            if (isset($confirm)) {
                if ($confirm == 'true') {
                    return $token;
                } else {
                    throw new OAuthClientException(
                        'Callback was not confirmed by remote OAuth side.'
                    );
                }
            }
            return $token;
        } else {
            throw new OAuthClientException(
                'Could not get a request token from remote OAuth side.'
            );
        }
    }

    /**
     * Builds a link that can be redirected to in order to
     * authorize a request token.
     *
     * @param string     $url            endpoint for authorizing request tokens
     * @param OAuthToken $request_token  the request token to be authorized
     *
     * @return string $authorize_url the url to redirect to
     */
    function getAuthorizeLink($url, $request_token)
    {
        $authorize_url = $url . '?oauth_token=' .
            $request_token->key;

        return $authorize_url;
    }

    /**
     * Fetches an access token
     *
     * @param string $url      OAuth endpoint for exchanging authorized request tokens
     *                         for access tokens
     * @param string $verifier 1.0a verifier
     *
     * @return OAuthToken $token the access token
     */
    function getAccessToken($url, $verifier = null)
    {
        $params = array();

        if (!is_null($verifier)) {
            $params['oauth_verifier'] = $verifier;
        }

        $response = $this->oAuthPost($url, $params);

        $arr = array();
        parse_str($response, $arr);

        $token  = $arr['oauth_token'];
        $secret = $arr['oauth_token_secret'];

        if (isset($token) && isset($secret)) {
            $token = new OAuthToken($token, $secret);
            return $token;
        } else {
            throw new OAuthClientException(
                'Could not get a access token from remote OAuth side.'
            );
        }
    }

    /**
     * Use HTTP GET to make a signed OAuth requesta
     *
     * @param string $url    OAuth request token endpoint
     * @param array  $params additional parameters
     *
     * @return mixed the request
     */
    function oAuthGet($url, $params = null)
    {
        $request = OAuthRequest::from_consumer_and_token($this->consumer,
            $this->token, 'GET', $url, $params);
        $request->sign_request($this->sha1_method,
            $this->consumer, $this->token);

        return $this->httpRequest($request->to_url());
    }

    /**
     * Use HTTP POST to make a signed OAuth request
     *
     * @param string $url    OAuth endpoint
     * @param array  $params additional post parameters
     *
     * @return mixed the request
     */
    function oAuthPost($url, $params = null)
    {
        $request = OAuthRequest::from_consumer_and_token($this->consumer,
            $this->token, 'POST', $url, $params);
        $request->sign_request($this->sha1_method,
            $this->consumer, $this->token);

        return $this->httpRequest($request->get_normalized_http_url(),
            $request->to_postdata());
    }

    /**
     * Make a HTTP request.
     *
     * @param string $url    Where to make the
     * @param array  $params post parameters
     *
     * @return mixed the request
     */
    function httpRequest($url, $params = null)
    {
        $request = new HTTPClient($url);
        $request->setConfig(array(
            'connect_timeout' => 120,
            'timeout' => 120,
            'follow_redirects' => true,
            'ssl_verify_peer' => false,
            'ssl_verify_host' => false
        ));

        // Twitter was strict about accepting invalid "Expect" headers
        // between 2008ish and October 2012. Caused "417 Expectation failed"
        //$request->setHeader('Expect', '');

        if (isset($params)) {
            $request->setMethod(HTTP_Request2::METHOD_POST);
            $request->setBody($params);
        }

        try {
            $response = $request->send();
            $code = $response->getStatus();
            if ($code < 200 || $code >= 400) {
                throw new OAuthClientException($response->getBody(), $code);
            }
            return $response->getBody();
        } catch (Exception $e) {
            throw new OAuthClientException($e->getMessage(), $e->getCode());
        }
    }

}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Base class for making daemons that can do several tasks in parallel.
 *
 * 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  Daemon
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

declare(ticks = 1);

/**
 * Daemon able to spawn multiple child processes to do work in parallel
 *
 * @category Daemon
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */

class ParallelizingDaemon extends Daemon
{
    private $_children     = array();
    private $_interval     = 0; // seconds
    private $_max_children = 0; // maximum number of children
    private $_debug        = false;

    /**
     *  Constructor
     *
     * @param string  $id           the name/id of this daemon
     * @param int     $interval     sleep this long before doing everything again
     * @param int     $max_children maximum number of child processes at a time
     * @param boolean $debug        debug output flag
     *
     * @return void
     *
     **/

    function __construct($id = null, $interval = 60, $max_children = 2,
                         $debug = null)
    {
        parent::__construct(true); // daemonize

        $this->_interval     = $interval;
        $this->_max_children = $max_children;
        $this->_debug        = $debug;

        if (isset($id)) {
            $this->set_id($id);
        }
    }

    /**
     * Run the daemon
     *
     * @return void
     */

    function run()
    {
        if (isset($this->_debug)) {
            echo $this->name() . " - Debugging output enabled.\n";
        }

        do {

            $objects = $this->getObjects();

            foreach ($objects as $o) {

                // Fork a child for each object

                $pid = pcntl_fork();

                if ($pid == -1) {
                    die ($this->name() . ' - Couldn\'t fork!');
                }

                if ($pid) {

                    // Parent

                    if (isset($this->_debug)) {
                        echo $this->name() .
                          " - Forked new child - pid $pid.\n";

                    }

                    $this->_children[] = $pid;

                } else {

                    // Child

                    // Do something with each object

                    $this->childTask($o);

                    exit();
                }

                // Remove child from ps list as it finishes

                while (($c = pcntl_wait($status, WNOHANG OR WUNTRACED)) > 0) {

                    if (isset($this->_debug)) {
                        echo $this->name() . " - Child $c finished.\n";
                    }

                    $this->removePs($this->_children, $c);
                }

                // Wait! We have too many damn kids.

                if (sizeof($this->_children) >= $this->_max_children) {

                    if (isset($this->_debug)) {
                        echo $this->name() . " - Too many children. Waiting...\n";
                    }

                    if (($c = pcntl_wait($status, WUNTRACED)) > 0) {

                        if (isset($this->_debug)) {
                            echo $this->name() .
                              " - Finished waiting for child $c.\n";
                        }

                        $this->removePs($this->_children, $c);
                    }
                }
            }

            // Remove all children from the process list before restarting
            while (($c = pcntl_wait($status, WUNTRACED)) > 0) {

                if (isset($this->_debug)) {
                    echo $this->name() . " - Child $c finished.\n";
                }

                $this->removePs($this->_children, $c);
            }

            // Rest for a bit

            if (isset($this->_debug)) {
                echo $this->name() . ' - Waiting ' . $this->_interval .
                  " secs before running again.\n";
            }

            if ($this->_interval > 0) {
                sleep($this->_interval);
            }

        } while (true);
    }

    /**
     * Remove a child process from the list of children
     *
     * @param array &$plist array of processes
     * @param int   $ps     process id
     *
     * @return void
     */

    function removePs(&$plist, $ps)
    {
        for ($i = 0; $i < sizeof($plist); $i++) {
            if ($plist[$i] == $ps) {
                unset($plist[$i]);
                $plist = array_values($plist);
                break;
            }
        }
    }

    /**
     * Get a list of objects to work on in parallel
     *
     * @return array An array of objects to work on
     */

    function getObjects()
    {
        die('Implement ParallelizingDaemon::getObjects().');
    }

    /**
     * Do something with each object in parallel
     *
     * @param mixed $object data to work on
     *
     * @return void
     */

    function childTask($object)
    {
        die("Implement ParallelizingDaemon::childTask($object).");
    }

}