code:
<?php
/**
 * GNU social - a federating social network
 *
 * Abstraction for an image file
 *
 * 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  Image
 * @package   GNUsocial
 * @author    Evan Prodromou <evan@status.net>
 * @author    Zach Copley <zach@status.net>
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @author    Miguel Dantas <biodantasgs@gmail.com>
 * @copyright 2008, 2019 Free Software Foundation http://fsf.org
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      https://www.gnu.org/software/social/
 */

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

/**
 * A wrapper on uploaded images
 *
 * Makes it slightly easier to accept an image file from upload.
 *
 * @category Image
 * @package  GNUsocial
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @author   Evan Prodromou <evan@status.net>
 * @author   Zach Copley <zach@status.net>
 * @link      https://www.gnu.org/software/social/
 */
class ImageFile extends MediaFile
{
    public $type;
    public $height;
    public $width;
    public $rotate   = 0;    // degrees to rotate for properly oriented image (extrapolated from EXIF etc.)
    public $animated = null; // Animated image? (has more than 1 frame). null means untested
    public $mimetype = null; // The _ImageFile_ mimetype, _not_ the originating File object

    public function __construct($id, string $filepath)
    {
        // These do not have to be the same as fileRecord->filename for example,
        // since we may have generated an image source file from something else!
        $this->filepath = $filepath;
        $this->filename = basename($filepath);

        $info = @getimagesize($this->filepath);

        if (!(($info[2] == IMAGETYPE_GIF  && function_exists('imagecreatefromgif'))  ||
              ($info[2] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) ||
              ($info[2] == IMAGETYPE_BMP  && function_exists('imagecreatefrombmp')) ||
              ($info[2] == IMAGETYPE_WBMP && function_exists('imagecreatefromwbmp')) ||
              ($info[2] == IMAGETYPE_XBM  && function_exists('imagecreatefromxbm'))  ||
              ($info[2] == IMAGETYPE_PNG  && function_exists('imagecreatefrompng')))) {
            // TRANS: Exception thrown when trying to upload an unsupported image file format.
            throw new UnsupportedMediaException(_m('Unsupported image format.'), $this->filepath);
        }

        $this->width    = $info[0];
        $this->height   = $info[1];
        $this->type     = $info[2];
        $this->mimetype = $info['mime'];

        parent::__construct(
            $filepath,
            $this->mimetype,
            null /* filehash, MediaFile will calculate it */,
            $id
        );

        if ($this->type === IMAGETYPE_JPEG && function_exists('exif_read_data')) {
            // Orientation value to rotate thumbnails properly
            $exif = @exif_read_data($this->filepath);
            if (is_array($exif) && isset($exif['Orientation'])) {
                switch (intval($exif['Orientation'])) {
                case 1: // top is top
                    $this->rotate = 0;
                    break;
                case 3: // top is bottom
                    $this->rotate = 180;
                    break;
                case 6: // top is right
                    $this->rotate = -90;
                    break;
                case 8: // top is left
                    $this->rotate = 90;
                    break;
                }
                // If we ever write this back, Orientation should be set to '1'
            }
        } elseif ($this->type === IMAGETYPE_GIF) {
            $this->animated = $this->isAnimatedGif();
        }

        Event::handle('FillImageFileMetadata', array($this));
    }

    public static function fromFileObject(File $file)
    {
        $imgPath = null;
        $media = common_get_mime_media($file->mimetype);
        if (Event::handle('CreateFileImageThumbnailSource', array($file, &$imgPath, $media))) {
            if (empty($file->filename) && !file_exists($imgPath)) {
                throw new UnsupportedMediaException(_m('File without filename could not get a thumbnail source.'));
            }

            // First some mimetype specific exceptions
            switch ($file->mimetype) {
            case 'image/svg+xml':
                throw new UseFileAsThumbnailException($file);
            }

            // And we'll only consider it an image if it has such a media type
            switch ($media) {
            case 'image':
                $imgPath = $file->getPath();
                break;
            default:
                throw new UnsupportedMediaException(_m('Unsupported media format.'), $file->getPath());
            }
        }

        if (!file_exists($imgPath)) {
            throw new FileNotFoundException($imgPath);
        }

        try {
            $image = new ImageFile($file->getID(), $imgPath);
        } catch (Exception $e) {
            // Avoid deleting the original
            try {
                if (strlen($imgPath) > 0 && $imgPath !== $file->getPath()) {
                    common_debug(__METHOD__.': Deleting temporary file that was created as image file' .
                                 'thumbnail source: '._ve($imgPath));
                    @unlink($imgPath);
                }
            } catch (FileNotFoundException $e) {
                // File reported (via getPath) that the original file
                // doesn't exist anyway, so it's safe to delete $imgPath
                @unlink($imgPath);
            }
            common_debug(sprintf(
                'Exception %s caught when creating ImageFile for File id==%s ' .
                                 'and imgPath==%s: %s',
                get_class($e),
                _ve($file->id),
                _ve($imgPath),
                _ve($e->getMessage())
            ));
            throw $e;
        }
        return $image;
    }

    public function getPath()
    {
        if (!file_exists($this->filepath)) {
            throw new FileNotFoundException($this->filepath);
        }

        return $this->filepath;
    }

    /**
     * Process a file upload
     *
     * Uses MediaFile's `fromUpload` to do the majority of the work and reencodes the image,
     * to mitigate injection attacks.
     * @param string $param
     * @param Profile|null $scoped
     * @return ImageFile|MediaFile
     * @throws ClientException
     * @throws NoResultException
     * @throws NoUploadedMediaException
     * @throws ServerException
     * @throws UnsupportedMediaException
     * @throws UseFileAsThumbnailException
     */
    public static function fromUpload(string $param='upload', Profile $scoped = null)
    {
        return parent::fromUpload($param, $scoped);
    }

    /**
     * Several obscure file types should be normalized to PNG on resize.
     *
     * Keeps only PNG, JPEG and GIF
     *
     * @return int
     */
    public function preferredType()
    {
        // Keep only JPEG and GIF in their orignal format
        if ($this->type === IMAGETYPE_JPEG || $this->type === IMAGETYPE_GIF) {
            return $this->type;
        }
        // We don't want to save some formats as they are rare, inefficient and antiquated
        // thus we can't guarantee clients will support
        // So just save it as PNG
        return IMAGETYPE_PNG;
    }

    /**
     * Copy the image file to the given destination.
     *
     * This function may modify the resulting file. Please use the
     * returned ImageFile object to read metadata (width, height etc.)
     *
     * @param string $outpath
     * @return ImageFile the image stored at target path
     * @throws ClientException
     * @throws NoResultException
     * @throws ServerException
     * @throws UnsupportedMediaException
     * @throws UseFileAsThumbnailException
     */
    public function copyTo($outpath)
    {
        return new ImageFile(null, $this->resizeTo($outpath));
    }

    /**
     * Create and save a thumbnail image.
     *
     * @param string $outpath
     * @param array $box width, height, boundary box (x,y,w,h) defaults to full image
     * @return string full local filesystem filename
     * @throws UnsupportedMediaException
     * @throws UseFileAsThumbnailException
     */
    public function resizeTo($outpath, array $box=array())
    {
        $box['width']  = isset($box['width'])  ? intval($box['width'])  : $this->width;
        $box['height'] = isset($box['height']) ? intval($box['height']) : $this->height;
        $box['x']      = isset($box['x'])      ? intval($box['x'])      : 0;
        $box['y']      = isset($box['y'])      ? intval($box['y'])      : 0;
        $box['w']      = isset($box['w'])      ? intval($box['w'])      : $this->width;
        $box['h']      = isset($box['h'])      ? intval($box['h'])      : $this->height;

        if (!file_exists($this->filepath)) {
            // TRANS: Exception thrown during resize when image has been registered as present, but is no longer there.
            throw new Exception(_m('Lost our file.'));
        }

        // Don't rotate/crop/scale if it isn't necessary
        if ($box['width']     === $this->width
            && $box['height'] === $this->height
            && $box['x']      === 0
            && $box['y']      === 0
            && $box['w']      === $this->width
            && $box['h']      === $this->height
            && $this->type    === $this->preferredType()) {
            if (abs($this->rotate) == 90) {
                // Box is rotated 90 degrees in either direction,
                // so we have to redefine x to y and vice versa.
                $tmp = $box['width'];
                $box['width'] = $box['height'];
                $box['height'] = $tmp;
                $tmp = $box['x'];
                $box['x'] = $box['y'];
                $box['y'] = $tmp;
                $tmp = $box['w'];
                $box['w'] = $box['h'];
                $box['h'] = $tmp;
            }
        }

        if (Event::handle('StartResizeImageFile', array($this, $outpath, $box))) {
            $this->resizeToFile($outpath, $box);
        }

        if (!file_exists($outpath)) {
            if ($this->fileRecord instanceof File) {
                throw new UseFileAsThumbnailException($this->fileRecord);
            } else {
                throw new UnsupportedMediaException('No local File object exists for ImageFile.');
            }
        }

        return $outpath;
    }

    /**
     * Resizes a file. If $box is omitted, the size is not changed, but this is still useful,
     * because it will reencode the image in the `self::prefferedType()` format. This only
     * applies henceforward, not retroactively
     *
     * Increases the 'memory_limit' to the one in the 'attachments' section in the config, to
     * enable the handling of bigger images, which can cause a peak of memory consumption, while
     * encoding
     * @param $outpath
     * @param array $box
     * @throws Exception
     */
    protected function resizeToFile($outpath, array $box)
    {
        $old_limit = ini_set('memory_limit', common_config('attachments', 'memory_limit'));
        $image_src = null;
        switch ($this->type) {
        case IMAGETYPE_GIF:
            $image_src = imagecreatefromgif($this->filepath);
            break;
        case IMAGETYPE_JPEG:
            $image_src = imagecreatefromjpeg($this->filepath);
            break;
        case IMAGETYPE_PNG:
            $image_src = imagecreatefrompng($this->filepath);
            break;
        case IMAGETYPE_BMP:
            $image_src = imagecreatefrombmp($this->filepath);
            break;
        case IMAGETYPE_WBMP:
            $image_src = imagecreatefromwbmp($this->filepath);
            break;
        case IMAGETYPE_XBM:
            $image_src = imagecreatefromxbm($this->filepath);
            break;
        default:
            // TRANS: Exception thrown when trying to resize an unknown file type.
            throw new Exception(_m('Unknown file type'));
        }

        if ($this->rotate != 0) {
            $image_src = imagerotate($image_src, $this->rotate, 0);
        }

        $image_dest = imagecreatetruecolor($box['width'], $box['height']);

        if ($this->type == IMAGETYPE_PNG || $this->type == IMAGETYPE_BMP) {
            $transparent_idx = imagecolortransparent($image_src);

            if ($transparent_idx >= 0 && $transparent_idx < 255) {
                $transparent_color = imagecolorsforindex($image_src, $transparent_idx);
                $transparent_idx = imagecolorallocate(
                    $image_dest,
                    $transparent_color['red'],
                    $transparent_color['green'],
                    $transparent_color['blue']
                );
                imagefill($image_dest, 0, 0, $transparent_idx);
                imagecolortransparent($image_dest, $transparent_idx);
            } elseif ($this->type == IMAGETYPE_PNG) {
                imagealphablending($image_dest, false);
                $transparent = imagecolorallocatealpha($image_dest, 0, 0, 0, 127);
                imagefill($image_dest, 0, 0, $transparent);
                imagesavealpha($image_dest, true);
            }
        }

        imagecopyresampled(
            $image_dest,
            $image_src,
            0,
            0,
            $box['x'],
            $box['y'],
            $box['width'],
            $box['height'],
            $box['w'],
            $box['h']
        );

        $type = $this->preferredType();
        $ext = image_type_to_extension($type, true);
        $outpath = preg_replace("/\.[^\.]+$/", $ext, $outpath);

        switch ($type) {
         case IMAGETYPE_GIF:
            imagegif($image_dest, $outpath);
            break;
         case IMAGETYPE_JPEG:
            imagejpeg($image_dest, $outpath, common_config('image', 'jpegquality'));
            break;
         case IMAGETYPE_PNG:
            imagepng($image_dest, $outpath);
            break;
         default:
            // TRANS: Exception thrown when trying resize an unknown file type.
            throw new Exception(_m('Unknown file type'));
        }

        imagedestroy($image_src);
        imagedestroy($image_dest);
        ini_set('memory_limit', $old_limit); // Restore the old memory limit
    }

    public function unlink()
    {
        @unlink($this->filepath);
    }

    public function scaleToFit($maxWidth=null, $maxHeight=null, $crop=null)
    {
        return self::getScalingValues(
            $this->width,
            $this->height,
            $maxWidth,
            $maxHeight,
            $crop,
            $this->rotate
        );
    }

    /**
     * Gets scaling values for images of various types. Cropping can be enabled.
     *
     * Values will scale _up_ to fit max values if cropping is enabled!
     * With cropping disabled, the max value of each axis will be respected.
     *
     * @param $width    int Original width
     * @param $height   int Original height
     * @param $maxW     int Resulting max width
     * @param $maxH     int Resulting max height
     * @param $crop     int Crop to the size (not preserving aspect ratio)
     * @param int $rotate
     * @return array
     * @throws ServerException
     */
    public static function getScalingValues(
        $width,
        $height,
        $maxW=null,
        $maxH=null,
        $crop=null,
        $rotate=0
    ) {
        $maxW = $maxW ?: common_config('thumbnail', 'width');
        $maxH = $maxH ?: common_config('thumbnail', 'height');

        if ($maxW < 1 || ($maxH !== null && $maxH < 1)) {
            throw new ServerException('Bad parameters for ImageFile::getScalingValues');
        } elseif ($maxH === null) {
            // if maxH is null, we set maxH to equal maxW and enable crop
            $maxH = $maxW;
            $crop = true;
        }

        // Because GD doesn't understand EXIF orientation etc.
        if (abs($rotate) == 90) {
            $tmp = $width;
            $width = $height;
            $height = $tmp;
        }

        // Cropping data (for original image size). Default values, 0 and null,
        // imply no cropping and with preserved aspect ratio (per axis).
        $cx = 0;    // crop x
        $cy = 0;    // crop y
        $cw = null; // crop area width
        $ch = null; // crop area height

        if ($crop) {
            $s_ar = $width / $height;
            $t_ar = $maxW / $maxH;

            $rw = $maxW;
            $rh = $maxH;

            // Source aspect ratio differs from target, recalculate crop points!
            if ($s_ar > $t_ar) {
                $cx = floor($width / 2 - $height * $t_ar / 2);
                $cw = ceil($height * $t_ar);
            } elseif ($s_ar < $t_ar) {
                $cy = floor($height / 2 - $width / $t_ar / 2);
                $ch = ceil($width / $t_ar);
            }
        } else {
            $rw = $maxW;
            $rh = ceil($height * $rw / $width);

            // Scaling caused too large height, decrease to max accepted value
            if ($rh > $maxH) {
                $rh = $maxH;
                $rw = ceil($width * $rh / $height);
            }
        }
        return array(intval($rw), intval($rh),
                     intval($cx), intval($cy),
                     is_null($cw) ? $width : intval($cw),
                     is_null($ch) ? $height : intval($ch));
    }

    /**
     * Animated GIF test, courtesy of frank at huddler dot com et al:
     * http://php.net/manual/en/function.imagecreatefromgif.php#104473
     * Modified so avoid landing inside of a header (and thus not matching our regexp).
     */
    protected function isAnimatedGif()
    {
        if (!($fh = @fopen($this->filepath, 'rb'))) {
            return false;
        }

        $count = 0;
        //an animated gif contains multiple "frames", with each frame having a
        //header made up of:
        // * a static 4-byte sequence (\x00\x21\xF9\x04)
        // * 4 variable bytes
        // * a static 2-byte sequence (\x00\x2C)
        // In total the header is maximum 10 bytes.

        // We read through the file til we reach the end of the file, or we've found
        // at least 2 frame headers
        while (!feof($fh) && $count < 2) {
            $chunk = fread($fh, 1024 * 100); //read 100kb at a time
            $count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00\x2C#s', $chunk, $matches);
            // rewind in case we ended up in the middle of the header, but avoid
            // infinite loop (i.e. don't rewind if we're already in the end).
            if (!feof($fh) && ftell($fh) >= 9) {
                fseek($fh, -9, SEEK_CUR);
            }
        }

        fclose($fh);
        return $count >= 1; // number of animated frames apart from the original image
    }

    public function getFileThumbnail($width, $height, $crop, $upscale=false)
    {
        if (!$this->fileRecord instanceof File) {
            throw new ServerException('No File object attached to this ImageFile object.');
        }

        if ($width === null) {
            $width  = common_config('thumbnail', 'width');
            $height = common_config('thumbnail', 'height');
            $crop   = common_config('thumbnail', 'crop');
        }

        if (!$upscale) {
            if ($width > $this->width) {
                $width = $this->width;
            }
            if (!is_null($height) && $height > $this->height) {
                $height = $this->height;
            }
        }

        if ($height === null) {
            $height = $width;
            $crop = true;
        }

        // Get proper aspect ratio width and height before lookup
        // We have to do it through an ImageFile object because of orientation etc.
        // Only other solution would've been to rotate + rewrite uploaded files
        // which we don't want to do because we like original, untouched data!
        list($width, $height, $x, $y, $w, $h) = $this->scaleToFit($width, $height, $crop);

        $thumb = File_thumbnail::pkeyGet(array(
                                            'file_id'=> $this->fileRecord->getID(),
                                            'width'  => $width,
                                            'height' => $height,
                                         ));
        if ($thumb instanceof File_thumbnail) {
            return $thumb;
        }

        $filename = $this->fileRecord->filehash ?: $this->filename;    // Remote files don't have $this->filehash
        $extension = File::guessMimeExtension($this->mimetype);
        $outname = "thumb-{$this->fileRecord->getID()}-{$width}x{$height}-{$filename}." . $extension;
        $outpath = File_thumbnail::path($outname);

        // The boundary box for our resizing
        $box = array('width'=>$width, 'height'=>$height,
                     'x'=>$x,         'y'=>$y,
                     'w'=>$w,         'h'=>$h);

        // Doublecheck that parameters are sane and integers.
        if ($box['width'] < 1 || $box['width'] > common_config('thumbnail', 'maxsize')
                || $box['height'] < 1 || $box['height'] > common_config('thumbnail', 'maxsize')
                || $box['w'] < 1 || $box['x'] >= $this->width
                || $box['h'] < 1 || $box['y'] >= $this->height) {
            // Fail on bad width parameter. If this occurs, it's due to algorithm in ImageFile->scaleToFit
            common_debug("Boundary box parameters for resize of {$this->filepath} : ".var_export($box, true));
            throw new ServerException('Bad thumbnail size parameters.');
        }

        common_debug(sprintf(
            'Generating a thumbnail of File id==%u of size %ux%u',
            $this->fileRecord->getID(),
            $width,
            $height
        ));

        // Perform resize and store into file
        $this->resizeTo($outpath, $box);

        try {
            // Avoid deleting the original
            if (!in_array($this->getPath(), [File::path($this->filename), File_thumbnail::path($this->filename)])) {
                $this->unlink();
            }
        } catch (FileNotFoundException $e) {
            // $this->getPath() says the file doesn't exist anyway, so no point in trying to delete it!
        }

        return File_thumbnail::saveThumbnail(
            $this->fileRecord->getID(),
            // no url since we generated it ourselves and can dynamically
            // generate the url
            null,
            $width,
            $height,
            $outname
        );
    }
}


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

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

class IMChannel extends Channel
{

    var $imPlugin;

    function source()
    {
        return $imPlugin->transport;
    }

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

    function on($user)
    {
        return $this->setNotify($user, 1);
    }

    function off($user)
    {
        return $this->setNotify($user, 0);
    }

    function output($user, $text)
    {
        $text = '['.common_config('site', 'name') . '] ' . $text;
        $this->imPlugin->sendMessage($this->imPlugin->getScreenname($user), $text);
    }

    function error($user, $text)
    {
        $text = '['.common_config('site', 'name') . '] ' . $text;

        $screenname = $this->imPlugin->getScreenname($user);
        if($screenname){
            $this->imPlugin->sendMessage($screenname, $text);
            return true;
        }else{
            common_log(LOG_ERR,
                'Could not send error message to user ' . common_log_objstring($user) .
                ' on transport ' . $this->imPlugin->transport .' : user preference does not exist');
            return false;
        }
    }

    function setNotify($user, $notify)
    {
        global $_PEAR;

        $user_im_prefs = new User_im_prefs();
        $user_im_prefs->transport = $this->imPlugin->transport;
        $user_im_prefs->user_id = $user->id;
        if($user_im_prefs->find() && $user_im_prefs->fetch()){
            if($user_im_prefs->notify == $notify){
                //notify is already set the way they want
                return true;
            }else{
                $original = clone($user_im_prefs);
                $user_im_prefs->notify = $notify;
                $result = $user_im_prefs->update($original);

                if (!$result) {
                    $last_error = &$_PEAR->getStaticProperty('DB_DataObject','lastError');
                    common_log(LOG_ERR,
                               'Could not set notify flag to ' . $notify .
                               ' for user ' . common_log_objstring($user) .
                               ' on transport ' . $this->imPlugin->transport .' : ' . $last_error->message);
                    return false;
                } else {
                    common_log(LOG_INFO,
                               'User ' . $user->nickname . ' set notify flag to ' . $notify);
                    return true;
                }
            }
        }else{
                common_log(LOG_ERR,
                           'Could not set notify flag to ' . $notify .
                           ' for user ' . common_log_objstring($user) .
                           ' on transport ' . $this->imPlugin->transport .' : user preference does not exist');
                return false;
        }
    }
}

repo_name: andremichelle/gnu

license: mit


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

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

/**
 * IKM background connection manager for IM-using queue handlers,
 * allowing them to send outgoing messages on the right connection.
 *
 * In a multi-site queuedaemon.php run, one connection will be instantiated
 * for each site being handled by the current process that has IM enabled.
 *
 * Implementations that extend this class will likely want to:
 * 1) override start() with their connection process.
 * 2) override handleInput() with what to do when data is waiting on
 *    one of the sockets
 * 3) override idle($timeout) to do keepalives (if necessary)
 * 4) implement send_raw_message() to send raw data that ImPlugin::enqueueOutgoingRaw
 *      enqueued
 */

abstract class ImManager extends IoManager
{
    abstract function send_raw_message($data);

    function __construct($imPlugin)
    {
        $this->plugin = $imPlugin;
        $this->plugin->imManager = $this;
    }

    /**
     * Fetch the singleton manager for the current site.
     * @return mixed ImManager, or false if unneeded
     */
    public static function get()
    {
        throw new Exception('ImManager should be created using it\'s constructor, not the static get method');
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Superclass for plugins that do instant messaging
 *
 * 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  Plugin
 * @package   StatusNet
 * @author    Craig Andrews <candrews@integralblue.com>
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

/**
 * Superclass for plugins that do authentication
 *
 * Implementations will likely want to override onStartIoManagerClasses() so that their
 *   IO manager is used
 *
 * @category Plugin
 * @package  StatusNet
 * @author   Craig Andrews <candrews@integralblue.com>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
abstract class ImPlugin extends Plugin
{
    //name of this IM transport
    public $transport = null;
    //list of screennames that should get all public notices
    public $public = array();

    protected $requires_cli = true;

    /**
     * normalize a screenname for comparison
     *
     * @param string $screenname screenname to normalize
     *
     * @return string an equivalent screenname in normalized form
     */
    abstract function normalize($screenname);

    /**
     * validate (ensure the validity of) a screenname
     *
     * @param string $screenname screenname to validate
     *
     * @return boolean
     */
    abstract function validate($screenname);

    /**
     * get the internationalized/translated display name of this IM service
     *
     * @return string
     */
    abstract function getDisplayName();

    /**
     * send a single notice to a given screenname
     * The implementation should put raw data, ready to send, into the outgoing
     *   queue using enqueueOutgoingRaw()
     *
     * @param string $screenname screenname to send to
     * @param Notice $notice notice to send
     *
     * @return boolean success value
     */
    function sendNotice($screenname, Notice $notice)
    {
        return $this->sendMessage($screenname, $this->formatNotice($notice));
    }

    /**
     * send a message (text) to a given screenname
     * The implementation should put raw data, ready to send, into the outgoing
     *   queue using enqueueOutgoingRaw()
     *
     * @param string $screenname screenname to send to
     * @param Notice $body text to send
     *
     * @return boolean success value
     */
    abstract function sendMessage($screenname, $body);

    /**
     * receive a raw message
     * Raw IM data is taken from the incoming queue, and passed to this function.
     * It should parse the raw message and call handleIncoming()
     *
     * Returning false may CAUSE REPROCESSING OF THE QUEUE ITEM, and should
     * be used for temporary failures only. For permanent failures such as
     * unrecognized addresses, return true to indicate your processing has
     * completed.
     *
     * @param object $data raw IM data
     *
     * @return boolean true if processing completed, false for temporary failures
     */
    abstract function receiveRawMessage($data);

    /**
     * get the screenname of the daemon that sends and receives message for this service
     *
     * @return string screenname of this plugin
     */
    abstract function daemonScreenname();

    //========================UTILITY FUNCTIONS USEFUL TO IMPLEMENTATIONS - MISC ========================\

    /**
     * Put raw message data (ready to send) into the outgoing queue
     *
     * @param object $data
     */
    function enqueueOutgoingRaw($data)
    {
        $qm = QueueManager::get();
        $qm->enqueue($data, $this->transport . '-out');
    }

    /**
     * Put raw message data (received, ready to be processed) into the incoming queue
     *
     * @param object $data
     */
    function enqueueIncomingRaw($data)
    {
        $qm = QueueManager::get();
        $qm->enqueue($data, $this->transport . '-in');
    }

    /**
     * given a screenname, get the corresponding user
     *
     * @param string $screenname
     *
     * @return User user
     */
    function getUser($screenname)
    {
        $user_im_prefs = $this->getUserImPrefsFromScreenname($screenname);
        if($user_im_prefs){
            $user = User::getKV('id', $user_im_prefs->user_id);
            $user_im_prefs->free();
            return $user;
        }else{
            return false;
        }
    }

    /**
     * given a screenname, get the User_im_prefs object for this transport
     *
     * @param string $screenname
     *
     * @return User_im_prefs user_im_prefs
     */
    function getUserImPrefsFromScreenname($screenname)
    {
        $user_im_prefs = User_im_prefs::pkeyGet(
            array('transport' => $this->transport,
                  'screenname' => $this->normalize($screenname)));
        if ($user_im_prefs) {
            return $user_im_prefs;
        } else {
            return false;
        }
    }

    /**
     * given a User, get their screenname
     *
     * @param User $user
     *
     * @return string screenname of that user
     */
    function getScreenname($user)
    {
        $user_im_prefs = $this->getUserImPrefsFromUser($user);
        if ($user_im_prefs) {
            return $user_im_prefs->screenname;
        } else {
            return false;
        }
    }

    /**
     * given a User, get their User_im_prefs
     *
     * @param User $user
     *
     * @return User_im_prefs user_im_prefs of that user
     */
    function getUserImPrefsFromUser($user)
    {
        $user_im_prefs = User_im_prefs::pkeyGet(
            array('transport' => $this->transport,
                  'user_id' => $user->id));
        if ($user_im_prefs){
            return $user_im_prefs;
        } else {
            return false;
        }
    }
    //========================UTILITY FUNCTIONS USEFUL TO IMPLEMENTATIONS - SENDING ========================\
    /**
     * Send a message to a given screenname from the site
     *
     * @param string $screenname screenname to send the message to
     * @param string $msg message contents to send
     *
     * @param boolean success
     */
    protected function sendFromSite($screenname, $msg)
    {
        $text = '['.common_config('site', 'name') . '] ' . $msg;
        $this->sendMessage($screenname, $text);
    }

    /**
     * Send a confirmation code to a user
     *
     * @param string $screenname screenname sending to
     * @param string $code the confirmation code
     * @param Profile $target For whom the code is valid for
     *
     * @return boolean success value
     */
    function sendConfirmationCode($screenname, $code, Profile $target)
    {
        // TRANS: Body text for confirmation code e-mail.
        // TRANS: %1$s is a user nickname, %2$s is the StatusNet sitename,
        // TRANS: %3$s is the display name of an IM plugin.
        $body = sprintf(_('User "%1$s" on %2$s has said that your %3$s screenname belongs to them. ' .
          'If that is true, you can confirm by clicking on this URL: ' .
          '%4$s' .
          ' . (If you cannot click it, copy-and-paste it into the ' .
          'address bar of your browser). If that user is not you, ' .
          'or if you did not request this confirmation, just ignore this message.'),
          $target->getNickname(), common_config('site', 'name'), $this->getDisplayName(), common_local_url('confirmaddress', null, array('code' => $code)));

        return $this->sendMessage($screenname, $body);
    }

    /**
     * send a notice to all public listeners
     *
     * For notices that are generated on the local system (by users), we can optionally
     * forward them to remote listeners by XMPP.
     *
     * @param Notice $notice notice to broadcast
     *
     * @return boolean success flag
     */

    function publicNotice($notice)
    {
        // Now, users who want everything

        // FIXME PRIV don't send out private messages here
        // XXX: should we send out non-local messages if public,localonly
        // = false? I think not

        foreach ($this->public as $screenname) {
            common_log(LOG_INFO,
                       'Sending notice ' . $notice->id .
                       ' to public listener ' . $screenname,
                       __FILE__);
            $this->sendNotice($screenname, $notice);
        }

        return true;
    }

    /**
     * broadcast a notice to all subscribers and reply recipients
     *
     * This function will send a notice to all subscribers on the local server
     * who have IM addresses, and have IM notification enabled, and
     * have this subscription enabled for IM. It also sends the notice to
     * all recipients of @-replies who have IM addresses and IM notification
     * enabled. This is really the heart of IM distribution in StatusNet.
     *
     * @param Notice $notice The notice to broadcast
     *
     * @return boolean success flag
     */

    function broadcastNotice($notice)
    {
        $ni = $notice->whoGets();

        foreach ($ni as $user_id => $reason) {
            $user = User::getKV($user_id);
            if (empty($user)) {
                // either not a local user, or just not found
                continue;
            }
            $user_im_prefs = $this->getUserImPrefsFromUser($user);
            if(!$user_im_prefs || !$user_im_prefs->notify){
                continue;
            }

            switch ($reason) {
            case NOTICE_INBOX_SOURCE_REPLY:
                if (!$user_im_prefs->replies) {
                    continue 2;
                }
                break;
            case NOTICE_INBOX_SOURCE_SUB:
                $sub = Subscription::pkeyGet(array('subscriber' => $user->id,
                                                   'subscribed' => $notice->profile_id));
                if (empty($sub) || !$sub->jabber) {
                    continue 2;
                }
                break;
            case NOTICE_INBOX_SOURCE_GROUP:
                break;
            default:
                // TRANS: Exception thrown when trying to deliver a notice to an unknown inbox.
                // TRANS: %d is the unknown inbox ID (number).
                throw new Exception(sprintf(_('Unknown inbox source %d.'), $reason));
            }

            common_log(LOG_INFO,
                       'Sending notice ' . $notice->id . ' to ' . $user_im_prefs->screenname,
                       __FILE__);
            $this->sendNotice($user_im_prefs->screenname, $notice);
            $user_im_prefs->free();
        }

        return true;
    }

    /**
     * makes a plain-text formatted version of a notice, suitable for IM distribution
     *
     * @param Notice  $notice  notice being sent
     *
     * @return string plain-text version of the notice, with user nickname prefixed
     */

    protected function formatNotice(Notice $notice)
    {
        $profile = $notice->getProfile();
        $nicknames = $profile->getNickname();

        try {
            $parent = $notice->getParent();
            $orig_profile = $parent->getProfile();
            $nicknames = sprintf('%1$s => %2$s', $profile->getNickname(), $orig_profile->getNickname());
        } catch (NoParentNoticeException $e) {
            // Not a reply, no parent notice stored
        } catch (NoResultException $e) {
            // Parent notice was probably deleted
        }

        return sprintf('%1$s: %2$s [%3$u]', $nicknames, $notice->content, $notice->id);
    }
    //========================UTILITY FUNCTIONS USEFUL TO IMPLEMENTATIONS - RECEIVING ========================\

    /**
     * Attempt to handle a message as a command
     * @param User $user user the message is from
     * @param string $body message text
     * @return boolean true if the message was a command and was executed, false if it was not a command
     */
    protected function handleCommand($user, $body)
    {
        $inter = new CommandInterpreter();
        $cmd = $inter->handle_command($user, $body);
        if ($cmd) {
            $chan = new IMChannel($this);
            $cmd->execute($chan);
            return true;
        }
        return false;
    }

    /**
     * Is some text an autoreply message?
     * @param string $txt message text
     * @return boolean true if autoreply
     */
    protected function isAutoreply($txt)
    {
        if (preg_match('/[\[\(]?[Aa]uto[-\s]?[Rr]e(ply|sponse)[\]\)]/', $txt)) {
            return true;
        } else if (preg_match('/^System: Message wasn\'t delivered. Offline storage size was exceeded.$/', $txt)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Is some text an OTR message?
     * @param string $txt message text
     * @return boolean true if OTR
     */
    protected function isOtr($txt)
    {
        if (preg_match('/^\?OTR/', $txt)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Helper for handling incoming messages
     * Your incoming message handler will probably want to call this function
     *
     * @param string $from screenname the message was sent from
     * @param string $message message contents
     *
     * @param boolean success
     */
    protected function handleIncoming($from, $notice_text)
    {
        $user = $this->getUser($from);
        // For common_current_user to work
        global $_cur;
        $_cur = $user;

        if (!$user) {
            $this->sendFromSite($from, 'Unknown user; go to ' .
                             common_local_url('imsettings') .
                             ' to add your address to your account');
            common_log(LOG_WARNING, 'Message from unknown user ' . $from);
            return;
        }
        if ($this->handleCommand($user, $notice_text)) {
            common_log(LOG_INFO, "Command message by $from handled.");
            return;
        } else if ($this->isAutoreply($notice_text)) {
            common_log(LOG_INFO, 'Ignoring auto reply from ' . $from);
            return;
        } else if ($this->isOtr($notice_text)) {
            common_log(LOG_INFO, 'Ignoring OTR from ' . $from);
            return;
        } else {

            common_log(LOG_INFO, 'Posting a notice from ' . $user->nickname);

            $this->addNotice($from, $user, $notice_text);
        }

        $user->free();
        unset($user);
        unset($_cur);
        unset($message);
    }

    /**
     * Helper for handling incoming messages
     * Your incoming message handler will probably want to call this function
     *
     * @param string $from screenname the message was sent from
     * @param string $message message contents
     *
     * @param boolean success
     */
    protected function addNotice($screenname, $user, $body)
    {
        $body = trim(strip_tags($body));
        $content_shortened = common_shorten_links($body);
        if (Notice::contentTooLong($content_shortened)) {
          $this->sendFromSite($screenname,
                              // TRANS: Message given when a status is too long. %1$s is the maximum number of characters,
                              // TRANS: %2$s is the number of characters sent (used for plural).
                              sprintf(_m('Message too long - maximum is %1$d character, you sent %2$d.',
                                         'Message too long - maximum is %1$d characters, you sent %2$d.',
                                         Notice::maxContent()),
                                      Notice::maxContent(),
                                      mb_strlen($content_shortened)));
          return;
        }

        try {
            $notice = Notice::saveNew($user->id, $content_shortened, $this->transport);
        } catch (Exception $e) {
            common_log(LOG_ERR, $e->getMessage());
            $this->sendFromSite($from, $e->getMessage());
            return;
        }

        common_log(LOG_INFO,
                   'Added notice ' . $notice->id . ' from user ' . $user->nickname);
        $notice->free();
        unset($notice);
    }

    //========================EVENT HANDLERS========================\

    /**
     * Register notice queue handler
     *
     * @param QueueManager $manager
     *
     * @return boolean hook return
     */
    function onEndInitializeQueueManager($manager)
    {
        // If we don't require CLI mode, or if we do and GNUSOCIAL_CLI _is_ set, then connect the transports
        // This check is made mostly because some IM plugins can't deliver to transports unless they
        // have continuously running daemons (such as XMPP) and we can't have that over HTTP requests.
        if (!$this->requires_cli || defined('GNUSOCIAL_CLI')) {
            $manager->connect($this->transport . '-in', new ImReceiverQueueHandler($this), 'im');
            $manager->connect($this->transport, new ImQueueHandler($this));
            $manager->connect($this->transport . '-out', new ImSenderQueueHandler($this), 'im');
        }
        return true;
    }

    function onStartImDaemonIoManagers(&$classes)
    {
        //$classes[] = new ImManager($this); // handles sending/receiving/pings/reconnects
        return true;
    }

    function onStartEnqueueNotice($notice, &$transports)
    {
        $profile = Profile::getKV($notice->profile_id);

        if (!$profile) {
            common_log(LOG_WARNING, 'Refusing to broadcast notice with ' .
                       'unknown profile ' . common_log_objstring($notice),
                       __FILE__);
        }else{
            $transports[] = $this->transport;
        }

        return true;
    }

    function onEndShowHeadElements(Action $action)
    {
        if ($action instanceof ShownoticeAction) {

            $user_im_prefs = new User_im_prefs();
            $user_im_prefs->user_id = $action->notice->getProfile()->getID();
            $user_im_prefs->transport = $this->transport;

        } elseif ($action instanceof ShowstreamAction) {

            $user_im_prefs = new User_im_prefs();
            $user_im_prefs->user_id = $action->getTarget()->getID();
            $user_im_prefs->transport = $this->transport;

        }
    }

    function onNormalizeImScreenname($transport, &$screenname)
    {
        if($transport == $this->transport)
        {
            $screenname = $this->normalize($screenname);
            return false;
        }
    }

    function onValidateImScreenname($transport, $screenname, &$valid)
    {
        if($transport == $this->transport)
        {
            $valid = $this->validate($screenname);
            return false;
        }
    }

    function onGetImTransports(&$transports)
    {
        $transports[$this->transport] = array(
            'display' => $this->getDisplayName(),
            'daemonScreenname' => $this->daemonScreenname());
    }

    function onSendImConfirmationCode($transport, $screenname, $code, Profile $target)
    {
        if($transport == $this->transport)
        {
            $this->sendConfirmationCode($screenname, $code, $target);
            return false;
        }
    }

    function onUserDeleteRelated($user, &$tables)
    {
        $tables[] = 'User_im_prefs';
        return true;
    }

    function onHaveImPlugin(&$haveImPlugin) {
        $haveImPlugin = true; // set flag true (we're loaded, after all!)
        return false; // stop looking
    }

    function initialize()
    {
        if( ! common_config('queue', 'enabled'))
        {
            // TRANS: Server exception thrown trying to initialise an IM plugin without meeting all prerequisites.
            throw new ServerException(_('Queueing must be enabled to use IM plugins.'));
        }

        if(is_null($this->transport)){
            // TRANS: Server exception thrown trying to initialise an IM plugin without a transport method.
            throw new ServerException(_('Transport cannot be null.'));
        }
    }
}


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

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

/**
 * Common superclass for all IM sending queue handlers.
 */

class ImQueueHandler extends QueueHandler
{
    function __construct($plugin)
    {
        $this->plugin = $plugin;
    }

    /**
     * Handle a notice
     * @param Notice $notice
     * @return boolean success
     */
    function handle($notice)
    {
        $this->plugin->broadcastNotice($notice);
        if ($notice->isLocal()) {
            $this->plugin->publicNotice($notice);
        }
        return true;
    }

}


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

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

/**
 * Common superclass for all IM receiving queue handlers.
 */

class ImReceiverQueueHandler extends QueueHandler
{
    function __construct($plugin)
    {
        $this->plugin = $plugin;
    }

    /**
     * Handle incoming IM data sent by a user to the IM bot
     * @param object $data
     * @return boolean success
     */
    function handle($data)
    {
        return $this->plugin->receiveRawMessage($data);
    }
}


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

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

/**
 * Common superclass for all IM sending queue handlers.
 */

class ImSenderQueueHandler extends QueueHandler
{
    function __construct($plugin)
    {
        $this->plugin = $plugin;
    }

    /**
     * Handle outgoing IM data to be sent from the bot to a user
     * @param object $data
     * @return boolean success
     */
    function handle($data)
    {
        return $this->plugin->imManager->send_raw_message($data);
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Stream of notices for a profile's "all" feed
 *
 * 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  NoticeStream
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @author    Alexei Sorokin <sor.alexei@meowr.ru>
 * @copyright 2011 StatusNet, Inc.
 * @copyright 2014 Free Software Foundation, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

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

/**
 * Stream of notices for a profile's "all" feed
 *
 * @category  General
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @author    Alexei Sorokin <sor.alexei@meowr.ru>
 * @author    chimo <chimo@chromic.org>
 * @copyright 2011 StatusNet, Inc.
 * @copyright 2014 Free Software Foundation, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */
class InboxNoticeStream extends ScopingNoticeStream
{
    /**
     * Constructor
     *
     * @param Profile $target Profile to get a stream for
     * @param Profile $scoped Currently scoped profile (if null, it is fetched)
     */
    public function __construct(Profile $target, Profile $scoped = null)
    {
        parent::__construct(new CachingNoticeStream(new RawInboxNoticeStream($target), 'profileall:'.$target->getID()), $scoped);
    }
}

/**
 * Raw stream of notices for the target's inbox
 *
 * @category  General
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @author    Alexei Sorokin <sor.alexei@meowr.ru>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */
class RawInboxNoticeStream extends FullNoticeStream
{
    protected $target = null;
    protected $inbox = null;

    /**
     * Constructor
     *
     * @param Profile $target Profile to get a stream for
     */
    public function __construct(Profile $target)
    {
        parent::__construct();
        $this->target = $target;
    }

    /**
     * Get IDs in a range
     *
     * @param int $offset Offset from start
     * @param int $limit Limit of number to get
     * @param int $since_id Since this notice
     * @param int $max_id Before this notice
     *
     * @return array IDs found
     */
    public function getNoticeIds($offset, $limit, $since_id = null, $max_id = null)
    {
        $notice = new Notice();
        $notice->selectAdd();
        $notice->selectAdd('id');
        $notice->whereAdd(sprintf('notice.created > "%s"', $notice->escape($this->target->created)));
        // Reply:: is a table of mentions
        // Subscription:: is a table of subscriptions (every user is subscribed to themselves)
        // Sort in descending order as id will give us even really old posts,
        // which were recently imported. For example, if a remote instance had
        // problems and just managed to post here.
        $notice->whereAdd(
            sprintf('id IN (SELECT DISTINCT id FROM (' .
                '(SELECT id FROM notice WHERE profile_id IN (SELECT subscribed FROM subscription WHERE subscriber = %1$d)) UNION ' .
                '(SELECT notice_id AS id FROM reply WHERE profile_id = %1$d) UNION ' .
                '(SELECT notice_id AS id FROM attention WHERE profile_id = %1$d) UNION ' .
                '(SELECT notice_id AS id FROM group_inbox WHERE group_id IN (SELECT group_id FROM group_member WHERE profile_id = %1$d)) ' .
                'ORDER BY id DESC) AS T)',
                $this->target->getID())
        );

        if (!empty($since_id)) {
            $notice->whereAdd(sprintf('notice.id > %d', $since_id));
        }
        if (!empty($max_id)) {
            $notice->whereAdd(sprintf('notice.id <= %d', $max_id));
        }

        self::filterVerbs($notice, $this->selectVerbs);

        $notice->limit($offset, $limit);

        if (!$notice->find()) {
            return [];
        }

        return $notice->fetchAll('id');
    }
}


code:
<?php

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

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

/**
 * Base class for displaying dialog box like messages to the user
 *
 * @category Action
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 *
 * @see ErrorAction
 */

class InfoAction extends ManagedAction
{
    var $message = null;

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

        $this->message = $message;
        $this->title   = $title;

        // XXX: hack alert: usually we aren't going to
        // call this page directly, but because it's
        // an action it needs an args array anyway
        $this->prepare($_REQUEST);
    }

    /**
     * Page title.
     *
     * @return string page title
     */

    function title()
    {
        return empty($this->title) ? '' : $this->title;
    }

    function isReadOnly($args)
    {
        return true;
    }

    // Overload a bunch of stuff so the page isn't too bloated

    function showBody()
    {
        $this->elementStart('body', ['id' => 'error']);
        $this->elementStart('div', ['id' => 'wrap']);
        $this->showHeader();
        $this->showCore();
        $this->showFooter();
        $this->elementEnd('div');
        $this->elementEnd('body');
    }

    function showCore()
    {
        $this->elementStart('div', ['id' => 'core']);
        $this->elementStart('div', ['id' => 'aside_primary_wrapper']);
        $this->elementStart('div', ['id' => 'content_wrapper']);
        $this->elementStart('div', ['id' => 'site_nav_local_views_wrapper']);
        $this->showContentBlock();
        $this->elementEnd('div');
        $this->elementEnd('div');
        $this->elementEnd('div');
        $this->elementEnd('div');
    }

    function showHeader()
    {
        $this->elementStart('div', ['id' => 'header']);
        $this->showLogo();
        $this->showPrimaryNav();
        $this->elementEnd('div');
    }

    /**
     * Display content.
     *
     * @return void
     */
    function showContent()
    {
        $this->element('div', ['class' => 'info'], $this->message);
    }

}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * widget for displaying notice attachments thumbnails
 *
 * 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  UI
 * @package   StatusNet
 * @author    Brion Vibber <brion@status.net>
 * @copyright 2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

class InlineAttachmentList extends AttachmentList
{
    function showListStart()
    {
        $this->out->element('h4', 'attachments-title', _('Attachments'));
        parent::showListStart();
    }

    /**
     * returns a new list item for the current attachment
     *
     * @param File $notice the current attachment
     *
     * @return ListItem a list item for displaying the attachment
     */
    function newListItem(File $attachment)
    {
        return new InlineAttachmentListItem($attachment, $this->out);
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * widget for displaying notice attachments thumbnails
 *
 * 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  UI
 * @package   StatusNet
 * @author    Brion Vibber <brion@status.net>
 * @copyright 2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

class InlineAttachmentListItem extends AttachmentListItem
{
    /**
     * start a single notice.
     *
     * @return void
     */
    function showStart()
    {
        // XXX: RDFa
        // TODO: add notice_type class e.g., notice_video, notice_image
        $this->out->elementStart('li',
                    array('class' => 'inline-attachment',
                          'id' => 'attachment-' . $this->attachment->getID(),
                ));
    }

    /**
     * finish the notice
     *
     * Close the last elements in the notice list item
     *
     * @return void
     */
    function showEnd()
    {
        $this->out->elementEnd('li');
    }
}


code:
<?php

/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2009-2010, StatusNet, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category Installation
 * @package  Installation
 *
 * @author   Adrian Lang <mail@adrianlang.de>
 * @author   Brenda Wallace <shiny@cpan.org>
 * @author   Brett Taylor <brett@webfroot.co.nz>
 * @author   Brion Vibber <brion@pobox.com>
 * @author   CiaranG <ciaran@ciarang.com>
 * @author   Craig Andrews <candrews@integralblue.com>
 * @author   Eric Helgeson <helfire@Erics-MBP.local>
 * @author   Evan Prodromou <evan@status.net>
 * @author   Mikael Nordfeldth <mmn@hethane.se>
 * @author   Robin Millette <millette@controlyourself.ca>
 * @author   Sarven Capadisli <csarven@status.net>
 * @author   Tom Adams <tom@holizz.com>
 * @author   Zach Copley <zach@status.net>
 * @copyright 2009-2010 StatusNet, Inc http://status.net
 * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
 * @license  GNU Affero General Public License http://www.gnu.org/licenses/
 * @version  1.0.x
 * @link     http://status.net
 */

abstract class Installer
{
    /** Web site info */
    public $sitename, $server, $path, $fancy, $siteProfile, $ssl;
    /** DB info */
    public $host, $database, $dbtype, $username, $password, $db;
    /** Storage info */
    public $avatarDir, $fileDir;
    /** Administrator info */
    public $adminNick, $adminPass, $adminEmail;
    /** Should we skip writing the configuration file? */
    public $skipConfig = false;

    public static $dbModules = array(
        'mysql' => array(
            'name' => 'MariaDB (or MySQL 5.5+)',
            'check_module' => 'mysqli',
            'scheme' => 'mysqli', // DSN prefix for PEAR::DB
        ),
/*        'pgsql' => array(
            'name' => 'PostgreSQL',
            'check_module' => 'pgsql',
            'scheme' => 'pgsql', // DSN prefix for PEAR::DB
        ),*/
    );

    /**
     * Attempt to include a PHP file and report if it worked, while
     * suppressing the annoying warning messages on failure.
     */
    private function haveIncludeFile($filename) {
        $old = error_reporting(error_reporting() & ~E_WARNING);
        $ok = include_once($filename);
        error_reporting($old);
        return $ok;
    }

    /**
     * Check if all is ready for installation
     *
     * @return void
     */
    function checkPrereqs()
    {
        $pass = true;

        $config = INSTALLDIR.'/config.php';
        if (!$this->skipConfig && file_exists($config)) {
            if (!is_writable($config) || filesize($config) > 0) {
                if (filesize($config) == 0) {
                    $this->warning('Config file "config.php" already exists and is empty, but is not writable.');
                } else {
                    $this->warning('Config file "config.php" already exists.');
                }
                $pass = false;
            }
        }

        if (version_compare(PHP_VERSION, '7.0.0', '<')) {
            $this->warning('Require PHP version 7.0.0 or greater.');
            $pass = false;
        }

        $reqs = array('gd', 'curl', 'intl', 'json',
                      'xmlwriter', 'mbstring', 'xml', 'dom', 'simplexml');

        foreach ($reqs as $req) {
            if (!$this->checkExtension($req)) {
                $this->warning(sprintf('Cannot load required extension: %s', $req));
                $pass = false;
            }
        }

        // Make sure we have at least one database module available
        $missingExtensions = array();
        foreach (self::$dbModules as $type => $info) {
            if (!$this->checkExtension($info['check_module'])) {
                $missingExtensions[] = $info['check_module'];
            }
        }

        if (count($missingExtensions) == count(self::$dbModules)) {
            $req = implode(', ', $missingExtensions);
            $this->warning(sprintf('Cannot find a database extension. You need at least one of %s.', $req));
            $pass = false;
        }

        // @fixme this check seems to be insufficient with Windows ACLs
        if (!$this->skipConfig && !is_writable(INSTALLDIR)) {
            $this->warning(sprintf('Cannot write config file to: %s
', INSTALLDIR),
                           sprintf('On your server, try this command: chmod a+w %s', INSTALLDIR));
            $pass = false;
        }

        // Check the subdirs used for file uploads
        // TODO get another flag for this --skipFileSubdirCreation
        if (!$this->skipConfig) {
            define('GNUSOCIAL', true);
            define('STATUSNET', true);
            require_once INSTALLDIR . '/lib/language.php';
            $_server=$this->server; $_path=$this->path; // We won't be using those so it's safe to do this small hack
            require_once INSTALLDIR.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR.'default.php';
            $fileSubdirs = [empty($this->avatarDir) ? $default['avatar']['dir'] : $this->avatarDir,
                            empty($this->fileDir)   ? $default['attachments']['dir'] : $this->fileDir];
            unset($default);
            foreach ($fileSubdirs as $fileFullPath) {
                if (!file_exists($fileFullPath)) {
                    $pass = $pass && mkdir($fileFullPath);
                } elseif (!is_dir($fileFullPath)) {
                    $this->warning(sprintf('GNU social expected a directory but found something else on this path: %s', $fileFullPath),
                                   'Either make sure it goes to a directory or remove it and a directory will be created.');
                    $pass = false;
                } elseif (!is_writable($fileFullPath)) {
                    $this->warning(sprintf('Cannot write to %s directory: %s', $fileSubdir, $fileFullPath),
                                   sprintf('On your server, try this command: chmod a+w %s', $fileFullPath));
                    $pass = false;
                }
            }
        }
        return $pass;
    }

    /**
     * Checks if a php extension is both installed and loaded
     *
     * @param string $name of extension to check
     *
     * @return boolean whether extension is installed and loaded
     */
    function checkExtension($name)
    {
        if (extension_loaded($name)) {
            return true;
        } elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) {
            // dl will throw a fatal error if it's disabled or we're in safe mode.
            // More fun, it may not even exist under some SAPIs in 5.3.0 or later...
            $soname = $name . '.' . PHP_SHLIB_SUFFIX;
            if (PHP_SHLIB_SUFFIX == 'dll') {
                $soname = "php_" . $soname;
            }
            return @dl($soname);
        } else {
            return false;
        }
    }

    /**
     * Basic validation on the database paramters
     * Side effects: error output if not valid
     *
     * @return boolean success
     */
    function validateDb()
    {
        $fail = false;

        if (empty($this->host)) {
            $this->updateStatus("No hostname specified.", true);
            $fail = true;
        }

        if (empty($this->database)) {
            $this->updateStatus("No database specified.", true);
            $fail = true;
        }

        if (empty($this->username)) {
            $this->updateStatus("No username specified.", true);
            $fail = true;
        }

        if (empty($this->sitename)) {
            $this->updateStatus("No sitename specified.", true);
            $fail = true;
        }

        return !$fail;
    }

    /**
     * Basic validation on the administrator user paramters
     * Side effects: error output if not valid
     *
     * @return boolean success
     */
    function validateAdmin()
    {
        $fail = false;

        if (empty($this->adminNick)) {
            $this->updateStatus("No initial user nickname specified.", true);
            $fail = true;
        }
        if ($this->adminNick && !preg_match('/^[0-9a-z]{1,64}$/', $this->adminNick)) {
            $this->updateStatus('The user nickname "' . htmlspecialchars($this->adminNick) .
                         '" is invalid; should be plain letters and numbers no longer than 64 characters.', true);
            $fail = true;
        }
        // @fixme hardcoded list; should use Nickname::isValid()
        // if/when it's safe to have loaded the infrastructure here
        $blacklist = array('main', 'panel', 'twitter', 'settings', 'rsd.xml', 'favorited', 'featured', 'favoritedrss', 'featuredrss', 'rss', 'getfile', 'api', 'groups', 'group', 'peopletag', 'tag', 'user', 'message', 'conversation', 'notice', 'attachment', 'search', 'index.php', 'doc', 'opensearch', 'robots.txt', 'xd_receiver.html', 'facebook', 'activity');
        if (in_array($this->adminNick, $blacklist)) {
            $this->updateStatus('The user nickname "' . htmlspecialchars($this->adminNick) .
                         '" is reserved.', true);
            $fail = true;
        }

        if (empty($this->adminPass)) {
            $this->updateStatus("No initial user password specified.", true);
            $fail = true;
        }

        return !$fail;
    }

    /**
     * Make sure a site profile was selected
     *
     * @return type boolean success
     */
    function validateSiteProfile()
    {
        if (empty($this->siteProfile))  {
            $this->updateStatus("No site profile selected.", true);
            return false;
        }

        return true;
    }

    /**
     * Set up the database with the appropriate function for the selected type...
     * Saves database info into $this->db.
     *
     * @fixme escape things in the connection string in case we have a funny pass etc
     * @return mixed array of database connection params on success, false on failure
     */
    function setupDatabase()
    {
        if ($this->db) {
            throw new Exception("Bad order of operations: DB already set up.");
        }
        $this->updateStatus("Starting installation...");

        if (empty($this->password)) {
            $auth = '';
        } else {
            $auth = ":$this->password";
        }
        $scheme = self::$dbModules[$this->dbtype]['scheme'];
        $dsn = "{$scheme}://{$this->username}{$auth}@{$this->host}/{$this->database}";

        $this->updateStatus("Checking database...");
        $conn = $this->connectDatabase($dsn);

        if (!$conn instanceof DB_common) {
            // Is not the right instance
            throw new Exception('Cannot connect to database: ' . $conn->getMessage());
        }

        // ensure database encoding is UTF8
        if ($this->dbtype == 'mysql') {
            // @fixme utf8m4 support for mysql 5.5?
            // Force the comms charset to utf8 for sanity
            // This doesn't currently work. :P
            //$conn->executes('set names utf8');
        } else if ($this->dbtype == 'pgsql') {
            $record = $conn->getRow('SHOW server_encoding');
            if ($record->server_encoding != 'UTF8') {
                $this->updateStatus("GNU social requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
                return false;
            }
        }

        $res = $this->updateStatus("Creating database tables...");
        if (!$this->createCoreTables($conn)) {
            $this->updateStatus("Error creating tables.", true);
            return false;
        }

        foreach (array('sms_carrier' => 'SMS carrier',
                    'notice_source' => 'notice source',
                    'foreign_services' => 'foreign service')
              as $scr => $name) {
            $this->updateStatus(sprintf("Adding %s data to database...", $name));
            $res = $this->runDbScript($scr.'.sql', $conn);
            if ($res === false) {
                $this->updateStatus(sprintf("Can't run %s script.", $name), true);
                return false;
            }
        }

        $db = array('type' => $this->dbtype, 'database' => $dsn);
        return $db;
    }

    /**
     * Open a connection to the database.
     *
     * @param <type> $dsn
     * @return <type>
     */
    function connectDatabase($dsn)
    {
        global $_DB;
        return $_DB->connect($dsn);
    }

    /**
     * Create core tables on the given database connection.
     *
     * @param DB_common $conn
     */
    function createCoreTables(DB_common $conn)
    {
        $schema = Schema::get($conn);
        $tableDefs = $this->getCoreSchema();
        foreach ($tableDefs as $name => $def) {
            if (defined('DEBUG_INSTALLER')) {
                echo " $name ";
            }
            $schema->ensureTable($name, $def);
        }
        return true;
    }

    /**
     * Fetch the core table schema definitions.
     *
     * @return array of table names => table def arrays
     */
    function getCoreSchema()
    {
        $schema = array();
        include INSTALLDIR . '/db/core.php';
        return $schema;
    }

    /**
     * Return a parseable PHP literal for the given value.
     * This will include quotes for strings, etc.
     *
     * @param mixed $val
     * @return string
     */
    function phpVal($val)
    {
        return var_export($val, true);
    }

    /**
     * Return an array of parseable PHP literal for the given values.
     * These will include quotes for strings, etc.
     *
     * @param mixed $val
     * @return array
     */
    function phpVals($map)
    {
        return array_map(array($this, 'phpVal'), $map);
    }

    /**
     * Write a stock configuration file.
     *
     * @return boolean success
     *
     * @fixme escape variables in output in case we have funny chars, apostrophes etc
     */
    function writeConf()
    {
        $vals = $this->phpVals(array(
            'sitename' => $this->sitename,
            'server' => $this->server,
            'path' => $this->path,
            'ssl' => in_array($this->ssl, array('never', 'always'))
                     ? $this->ssl
                     : 'never',
            'db_database' => $this->db['database'],
            'db_type' => $this->db['type']
        ));

        // assemble configuration file in a string
        $cfg =  "<?php\n".
                "if (!defined('GNUSOCIAL')) { exit(1); }\n\n".

                // site name
                "\$config['site']['name'] = {$vals['sitename']};\n\n".

                // site location
                "\$config['site']['server'] = {$vals['server']};\n".
                "\$config['site']['path'] = {$vals['path']}; \n\n".
                "\$config['site']['ssl'] = {$vals['ssl']}; \n\n".

                // checks if fancy URLs are enabled
                ($this->fancy ? "\$config['site']['fancy'] = true;\n\n":'').

                // database
                "\$config['db']['database'] = {$vals['db_database']};\n\n".
                ($this->db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
                "\$config['db']['type'] = {$vals['db_type']};\n\n".

                "// Uncomment below for better performance. Just remember you must run\n".
                "// php scripts/checkschema.php whenever your enabled plugins change!\n".
                "//\$config['db']['schemacheck'] = 'script';\n\n";

        // Normalize line endings for Windows servers
        $cfg = str_replace("\n", PHP_EOL, $cfg);

        // write configuration file out to install directory
        $res = file_put_contents(INSTALLDIR.'/config.php', $cfg);

        return $res;
    }

    /**
     * Write the site profile. We do this after creating the initial user
     * in case the site profile is set to single user. This gets around the
     * 'chicken-and-egg' problem of the system requiring a valid user for
     * single user mode, before the intial user is actually created. Yeah,
     * we should probably do this in smarter way.
     *
     * @return int res number of bytes written
     */
    function writeSiteProfile()
    {
        $vals = $this->phpVals(array(
            'site_profile' => $this->siteProfile,
            'nickname' => $this->adminNick
        ));

        $cfg =
        // site profile
        "\$config['site']['profile'] = {$vals['site_profile']};\n";

        if ($this->siteProfile == "singleuser") {
            $cfg .= "\$config['singleuser']['nickname'] = {$vals['nickname']};\n\n";
        } else {
            $cfg .= "\n";
        }

        // Normalize line endings for Windows servers
        $cfg = str_replace("\n", PHP_EOL, $cfg);

        // write configuration file out to install directory
        $res = file_put_contents(INSTALLDIR.'/config.php', $cfg, FILE_APPEND);

        return $res;
    }

    /**
     * Install schema into the database
     *
     * @param string    $filename location of database schema file
     * @param DB_common $conn     connection to database
     *
     * @return boolean - indicating success or failure
     */
    function runDbScript($filename, DB_common $conn)
    {
        $sql = trim(file_get_contents(INSTALLDIR . '/db/' . $filename));
        $stmts = explode(';', $sql);
        foreach ($stmts as $stmt) {
            $stmt = trim($stmt);
            if (!mb_strlen($stmt)) {
                continue;
            }
            try {
                $res = $conn->simpleQuery($stmt);
            } catch (Exception $e) {
                $error = $e->getMessage();
                $this->updateStatus("ERROR ($error) for SQL '$stmt'");
                return false;
            }
        }
        return true;
    }

    /**
     * Create the initial admin user account.
     * Side effect: may load portions of GNU social framework.
     * Side effect: outputs program info
     */
    function registerInitialUser()
    {
        // initalize hostname from install arguments, so it can be used to find
        // the /etc config file from the commandline installer
        $server = $this->server;
        require_once INSTALLDIR . '/lib/common.php';

        $data = array('nickname' => $this->adminNick,
                      'password' => $this->adminPass,
                      'fullname' => $this->adminNick);
        if ($this->adminEmail) {
            $data['email'] = $this->adminEmail;
        }
        try {
            $user = User::register($data, true);    // true to skip email sending verification
        } catch (Exception $e) {
            return false;
        }

        // give initial user carte blanche

        $user->grantRole('owner');
        $user->grantRole('moderator');
        $user->grantRole('administrator');

        return true;
    }

    /**
     * The beef of the installer!
     * Create database, config file, and admin user.
     *
     * Prerequisites: validation of input data.
     *
     * @return boolean success
     */
    function doInstall()
    {
        global $config;

        $this->updateStatus("Initializing...");
        ini_set('display_errors', 1);
        error_reporting(E_ALL & ~E_STRICT & ~E_NOTICE);
        if (!defined('GNUSOCIAL')) {
            define('GNUSOCIAL', true);
        }
        if (!defined('STATUSNET')) {
            define('STATUSNET', true);
        }

        require_once INSTALLDIR . '/lib/framework.php';
        GNUsocial::initDefaults($this->server, $this->path);

        if ($this->siteProfile == "singleuser") {
            // Until we use ['site']['profile']==='singleuser' everywhere
            $config['singleuser']['enabled'] = true;
        }

        try {
            $this->db = $this->setupDatabase();
            if (!$this->db) {
                // database connection failed, do not move on to create config file.
                return false;
            }
        } catch (Exception $e) {
            // Lower-level DB error!
            $this->updateStatus("Database error: " . $e->getMessage(), true);
            return false;
        }

        if (!$this->skipConfig) {
        // Make sure we can write to the file twice
        $oldUmask = umask(000);

            $this->updateStatus("Writing config file...");
            $res = $this->writeConf();

            if (!$res) {
                $this->updateStatus("Can't write config file.", true);
                return false;
            }
        }

        if (!empty($this->adminNick)) {
            // Okay, cross fingers and try to register an initial user
            if ($this->registerInitialUser()) {
                $this->updateStatus(
                    "An initial user with the administrator role has been created."
                );
            } else {
                $this->updateStatus(
                    "Could not create initial user account.",
                    true
                );
                return false;
            }
        }

        if (!$this->skipConfig) {
            $this->updateStatus("Setting site profile...");
            $res = $this->writeSiteProfile();

            if (!$res) {
                $this->updateStatus("Can't write to config file.", true);
                return false;
            }

        // Restore original umask
        umask($oldUmask);
        // Set permissions back to something decent
        chmod(INSTALLDIR.'/config.php', 0644);
        }

        $scheme = $this->ssl === 'always' ? 'https' : 'http';
        $link = "{$scheme}://{$this->server}/{$this->path}";

        $this->updateStatus("GNU social has been installed at $link");
        $this->updateStatus(
            'DONE! You can visit your new GNU social site (log in as "'.htmlspecialchars($this->adminNick).'"). If this is your first GNU social install, make your experience the best possible by visiting our resource site to join the mailing list or IRC. FAQ is found here.'
        );

        return true;
    }

    /**
     * Output a pre-install-time warning message
     * @param string $message HTML ok, but should be plaintext-able
     * @param string $submessage HTML ok, but should be plaintext-able
     */
    abstract function warning($message, $submessage='');

    /**
     * Output an install-time progress message
     * @param string $message HTML ok, but should be plaintext-able
     * @param boolean $error true if this should be marked as an error condition
     */
    abstract function updateStatus($status, $error=false);

}


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

/**
 * GNU social's implementation of SessionHandler
 *
 * @package   GNUsocial
 * @author    Evan Prodromou
 * @author    Brion Vibber
 * @author    Mikael Nordfeldth
 * @author    Sorokin Alexei
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 * @copyright 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();

/**
 * Superclass representing the associated interfaces of session handling.
 *
 * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */
class InternalSessionHandler implements SessionHandlerInterface
{
    /**
     * A helper function to print a session-related message to the debug log if
     * the site session debug configuration option is enabled.
     * @param $msg
     * @return void
     */
    public static function logdeb($msg)
    {
        if (common_config('sessions', 'debug')) {
            common_debug("Session: " . $msg);
        }
    }

    /**
     * Dummy option for saving to file needed for full PHP adherence.
     *
     * @param $save_path
     * @param $session_name
     * @return bool true
     */
    public function open($save_path, $session_name)
    {
        return true;
    }

    /**
     * Dummy option for saving to file needed for full PHP adherence.
     *
     * @return bool true
     */
    public function close()
    {
        return true;
    }

    /**
     * Fetch the session data for the session with the given $id.
     *
     * @param $id
     * @return string Returns an encoded string of the read data. If nothing was read, it must return an empty string. Note this value is returned internally to PHP for processing.
     */
    public function read($id)
    {
        self::logdeb("Fetching session '$id'.");

        $session = Session::getKV('id', $id);

        if (empty($session)) {
            self::logdeb("Couldn't find '$id'.");
            return '';
        } else {
            self::logdeb("Found '$id', returning " .
                         strlen($session->session_data) .
                         " chars of data.");
            return (string)$session->session_data;
        }
    }

    /**
     * Write the session data for session with given $id as $session_data.
     *
     * @param $id
     * @param $session_data
     * @return bool Returns TRUE on success or FALSE on failure.
     */
    public function write($id, $session_data)
    {
        self::logdeb("Writing session '$id'.");

        $session = Session::getKV('id', $id);

        if (empty($session)) {
            self::logdeb("'$id' doesn't yet exist; inserting.");
            $session = new Session();

            $session->id = $id;
            $session->session_data = $session_data;
            $session->created = common_sql_now();

            $result = $session->insert();

            if (!$result) {
                common_log_db_error($session, 'INSERT', __FILE__);
                self::logdeb("Failed to insert '$id'.");
            } else {
                self::logdeb("Successfully inserted '$id' (result = $result).");
            }
            return (bool) $result;
        } else {
            self::logdeb("'$id' already exists; updating.");
            if (strcmp($session->session_data, $session_data) == 0) {
                self::logdeb("Not writing session '$id' - unchanged.");
                return true;
            } else {
                self::logdeb("Session '$id' data changed - updating.");

                // Only update the required field
                $orig = clone($session);
                $session->session_data = $session_data;
                $result = $session->update($orig);

                if (!$result) {
                    common_log_db_error($session, 'UPDATE', __FILE__);
                    self::logdeb("Failed to update '$id'.");
                } else {
                    self::logdeb("Successfully updated '$id' (result = $result).");
                }

                return (bool) $result;
            }
        }
    }

    /**
     * Find sessions that have persisted beyond $maxlifetime and delete them.
     * This will be limited by config['sessions']['gc_limit'] - it won't delete
     * more than the number of sessions specified there at a single pass.
     *
     * @param $maxlifetime
     * @return bool Returns TRUE on success or FALSE on failure.
     */
    public function gc($maxlifetime)
    {
        self::logdeb("Garbage Collector has now started with maxlifetime = '$maxlifetime'.");

        $epoch = common_sql_date(time() - $maxlifetime);

        $ids = [];

        $session = new Session();
        $session->whereAdd('modified < "' . $epoch . '"');
        $session->selectAdd();
        $session->selectAdd('id');

        $limit = common_config('sessions', 'gc_limit');
        if ($limit > 0) {
            // On large sites, too many sessions to expire
            // at once will just result in failure.
            $session->limit($limit);
        }

        $session->find();

        while ($session->fetch()) {
            $ids[] = $session->id;
        }

        $session->free();

        self::logdeb("Garbage Collector found " . count($ids) . " ids to delete.");

        foreach ($ids as $id) {
            self::logdeb("Garbage Collector will now delete session '$id'.");
            self::destroy($id);
        }

        return true;
    }

    /**
     * Deletes session with given id $id.
     *
     * @param $id
     * @return bool Returns TRUE on success or FALSE on failure.
     */
    public function destroy($id)
    {
        self::logdeb("Destroying session '$id'.");

        $session = Session::getKV('id', $id);

        if (empty($session)) {
            self::logdeb("Can't find '$id' to destroy.");
            return false;
        } else {
            $result = $session->delete();
            if (!$result) {
                common_log_db_error($session, 'DELETE', __FILE__);
                self::logdeb("Failed to destroy '$id'.");
            } else {
                self::logdeb("Successfully destroyed '$id' (result = $result).");
            }
            return (bool) $result;
        }
    }
}


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

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

/**
 * Class for an exception when a filename is invalid
 *
 * @category Exception
 * @package  GNUsocial
 * @author   Mikael Nordfeldth <mmn@hethane.se>
 * @license  https://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
 * @link     https://gnu.io/social
 */

class InvalidFilenameException extends ServerException
{
    public $filename = null;

    public function __construct($filename)
    {
        $this->filename = $filename;
        // We could log an entry here with the search parameters
        parent::__construct(_('Invalid filename.'));
    }
}


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

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

/**
 * Class for an exception when a URL is invalid
 *
 * @category Exception
 * @package  GNUsocial
 * @author   Mikael Nordfeldth <mmn@hethane.se>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
 * @link     http://status.net/
 */

class InvalidUrlException extends ServerException
{
    public $url = null;

    public function __construct($url)
    {
        $this->url = $url;
        // We could log an entry here with the search parameters
        parent::__construct(_('Invalid URL.'));
    }
}