code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * User administration panel
 *
 * 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  Settings
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @author    Zach Copley <zach@status.net>
 * @author    Sarven Capadisli <csarven@status.net>
 * @copyright 2008-2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

/**
 * Administer user settings
 *
 * @category Admin
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @author   Zach Copley <zach@status.net>
 * @author   Sarven Capadisli <csarven@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class UseradminpanelAction extends AdminPanelAction
{
    /**
     * Returns the page title
     *
     * @return string page title
     */
    function title()
    {
        // TRANS: User admin panel title.
        return _m('TITLE', 'User');
    }

    /**
     * Instructions for using this form.
     *
     * @return string instructions
     */
    function getInstructions()
    {
        // TRANS: Instruction for user admin panel.
        return _('User settings for this StatusNet site');
    }

    /**
     * Show the site admin panel form
     *
     * @return void
     */
    function showForm()
    {
        $form = new UserAdminPanelForm($this);
        $form->show();
        return;
    }

    /**
     * Save settings from the form
     *
     * @return void
     */
    function saveSettings()
    {
        static $settings = array(
                'profile' => array('biolimit'),
                'newuser' => array('welcome', 'default')
        );

        static $booleans = array(
            'invite' => array('enabled')
        );

        $values = array();

        foreach ($settings as $section => $parts) {
            foreach ($parts as $setting) {
                $values[$section][$setting] = $this->trimmed("$section-$setting");
            }
        }

        foreach ($booleans as $section => $parts) {
            foreach ($parts as $setting) {
                $values[$section][$setting] = ($this->boolean("$section-$setting")) ? 1 : 0;
            }
        }

        // This throws an exception on validation errors

        $this->validate($values);

        // assert(all values are valid);

        $config = new Config();

        $config->query('BEGIN');

        foreach ($settings as $section => $parts) {
            foreach ($parts as $setting) {
                Config::save($section, $setting, $values[$section][$setting]);
            }
        }

        foreach ($booleans as $section => $parts) {
            foreach ($parts as $setting) {
                Config::save($section, $setting, $values[$section][$setting]);
            }
        }

        $config->query('COMMIT');

        return;
    }

    function validate(&$values)
    {
        // Validate biolimit

        if (!Validate::number($values['profile']['biolimit'])) {
            // TRANS: Form validation error in user admin panel when a non-numeric character limit was set.
            $this->clientError(_('Invalid bio limit. Must be numeric.'));
        }

        // Validate welcome text

        if (mb_strlen($values['newuser']['welcome']) > 255) {
            // TRANS: Form validation error in user admin panel when welcome text is too long.
            $this->clientError(_('Invalid welcome text. Maximum length is 255 characters.'));
        }

        // Validate default subscription

        if (!empty($values['newuser']['default'])) {
            $defuser = User::getKV('nickname', trim($values['newuser']['default']));
            if (empty($defuser)) {
                $this->clientError(
                    sprintf(
                        // TRANS: Client error displayed when trying to set a non-existing user as default subscription for new
                        // TRANS: users in user admin panel. %1$s is the invalid nickname.
                        _('Invalid default subscripton: "%1$s" is not a user.'),
                        $values['newuser']['default']
                    )
                );
            }
        }
    }
}

// @todo FIXME: Class documentation missing.
class UserAdminPanelForm extends AdminForm
{
    /**
     * ID of the form
     *
     * @return int ID of the form
     */
    function id()
    {
        return 'useradminpanel';
    }

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

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

    /**
     * Data elements of the form
     *
     * @return void
     */
    function formData()
    {
        $this->out->elementStart('fieldset', array('id' => 'settings_user-profile'));
        // TRANS: Fieldset legend in user administration panel.
        $this->out->element('legend', null, _m('LEGEND','Profile'));
        $this->out->elementStart('ul', 'form_data');

        $this->li();
        // TRANS: Field label in user admin panel for setting the character limit for the bio field.
        $this->input('biolimit', _('Bio Limit'),
                     // TRANS: Tooltip in user admin panel for setting the character limit for the bio field.
                     _('Maximum length of a profile bio in characters.'),
                     'profile');
        $this->unli();

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

        $this->out->elementStart('fieldset', array('id' => 'settings_user-newusers'));
        // TRANS: Form legend in user admin panel.
        $this->out->element('legend', null, _('New users'));
        $this->out->elementStart('ul', 'form_data');

        $this->li();
        // TRANS: Field label in user admin panel for setting new user welcome text.
        $this->input('welcome', _('New user welcome'),
                     // TRANS: Tooltip in user admin panel for setting new user welcome text.
                     _('Welcome text for new users (maximum 255 characters).'),
                     'newuser');
        $this->unli();

        $this->li();
        // TRANS: Field label in user admin panel for setting default subscription for new users.
        $this->input('default', _('Default subscription'),
                     // TRANS: Tooltip in user admin panel for setting default subscription for new users.
                     _('Automatically subscribe new users to this user.'),
                     'newuser');
        $this->unli();

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

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

        $this->out->elementStart('fieldset', array('id' => 'settings_user-invitations'));
        // TRANS: Form legend in user admin panel.
        $this->out->element('legend', null, _('Invitations'));
        $this->out->elementStart('ul', 'form_data');

        $this->li();

        // TRANS: Field label for checkbox in user admin panel for allowing users to invite friend using site e-mail.
        $this->out->checkbox('invite-enabled', _('Invitations enabled'),
                              (bool) $this->value('enabled', 'invite'),
                              // TRANS: Tooltip for checkbox in user admin panel for allowing users to invite friend using site e-mail.
                              _('Whether to allow users to invite new users.'));
        $this->unli();

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

    /**
     * Utility to simplify some of the duplicated code around
     * params and settings.  Overrided from base class to be
     * more specific about input ids.
     *
     * @param string $setting      Name of the setting
     * @param string $title        Title to use for the input
     * @param string $instructions Instructions for this field
     * @param string $section      config section, default = 'site'
     *
     * @return void
     */
    function input($setting, $title, $instructions, $section='site')
    {
        $this->out->input("$section-$setting", $title, $this->value($setting, $section), $instructions);
    }

    /**
     * Action elements
     *
     * @return void
     */
    function formActions()
    {
        $this->out->submit('submit',
                           // TRANS: Button text to save user settings in user admin panel.
                           _m('BUTTON','Save'),
                           'submit',
                           null,
                           // TRANS: Button title to save user settings in user admin panel.
                           _('Save user settings.'));
    }
}


code:
<?php
/**
 * User by ID action class.
 *
 * PHP version 5
 *
 * @category Action
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @author   Robin Millette <millette@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/

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

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

/**
 * User by ID action class.
 *
 * @category Action
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @author   Robin Millette <millette@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 */
class UserbyidAction extends Action
{
     /**
     * Is read only?
     *
     * @return boolean true
     */
    function isReadOnly($args)
    {
        return true;
    }

     /**
     * Class handler.
     *
     * @param array $args array of arguments
     *
     * @return nothing
     */
    protected function handle()
    {
        parent::handle();
        $id = $this->trimmed('id');
        if (!$id) {
            // TRANS: Client error displayed trying to find a user by ID without providing an ID.
            $this->clientError(_('No ID.'));
        }
        $user = User::getKV($id);
        if (!$user) {
            // TRANS: Client error displayed trying to find a user by ID for a non-existing ID.
            $this->clientError(_('No such user.'));
        }

        // Support redirecting to FOAF rdf/xml if the agent prefers it...
        // Internet Explorer doesn't specify "text/html" and does list "*/*"
        // at least through version 8. We need to list text/html up front to
        // ensure that only user-agents who specifically ask for RDF get it.
        $page_prefs = 'text/html,application/xhtml+xml,application/rdf+xml,application/xml;q=0.3,text/xml;q=0.2';
        $httpaccept = isset($_SERVER['HTTP_ACCEPT'])
                      ? $_SERVER['HTTP_ACCEPT'] : null;
        $type       = common_negotiate_type(common_accept_to_prefs($httpaccept),
                      common_accept_to_prefs($page_prefs));
        $page       = $type == 'application/rdf+xml' ? 'foaf' : 'showstream';
        $url        = common_local_url($page, array('nickname' => $user->nickname));
        common_redirect($url, 303);
    }
}

repo_name: piratas/tortuga

license: mit


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

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

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

/**
 * User groups page
 *
 * Show the groups a user belongs to
 *
 * @category Personal
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class UsergroupsAction extends GalleryAction
{
    function title()
    {
        if ($this->page == 1) {
            // TRANS: Page title for first page of groups for a user.
            // TRANS: %s is a nickname.
            return sprintf(_('%s groups'), $this->user->nickname);
        } else {
            // TRANS: Page title for all but the first page of groups for a user.
            // TRANS: %1$s is a nickname, %2$d is a page number.
            return sprintf(_('%1$s groups, page %2$d'),
                           $this->user->nickname,
                           $this->page);
        }
    }

    function showContent()
    {
        $this->elementStart('p', array('id' => 'new_group'));
        $this->element('a', array('href' => common_local_url('newgroup'),
                                  'class' => 'more'),
                       // TRANS: Link text on group page to create a new group.
                       _('Create a new group'));
        $this->elementEnd('p');

        $this->elementStart('p', array('id' => 'group_search'));
        $this->element('a', array('href' => common_local_url('groupsearch'),
                                  'class' => 'more'),
                       // TRANS: Link text on group page to search for groups.
                       _('Search for more groups'));
        $this->elementEnd('p');

        if (Event::handle('StartShowUserGroupsContent', array($this))) {
            $offset = ($this->page-1) * GROUPS_PER_PAGE;
            $limit =  GROUPS_PER_PAGE + 1;

            $groups = $this->user->getGroups($offset, $limit);

            if ($groups instanceof User_group) {
                $gl = new GroupList($groups, $this->user, $this);
                $cnt = $gl->show();
                $this->pagination($this->page > 1, $cnt > GROUPS_PER_PAGE,
                              $this->page, 'usergroups',
                              array('nickname' => $this->user->nickname));
            } else {
                $this->showEmptyListMessage();
            }

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

    function showEmptyListMessage()
    {
        // TRANS: Text on group page for a user that is not a member of any group.
        // TRANS: %s is a user nickname.
        $message = sprintf(_('%s is not a member of any group.'), $this->user->nickname) . ' ';

        if (common_logged_in()) {
            $current_user = common_current_user();
            if ($this->user->id === $current_user->id) {
                // TRANS: Text on group page for a user that is not a member of any group. This message contains
                // TRANS: a Markdown link in the form [link text](link) and a variable that should not be changed.
                $message .= _('Try [searching for groups](%%action.groupsearch%%) and joining them.');
            }
        }
        $this->elementStart('div', 'guide');
        $this->raw(common_markup_to_html($message));
        $this->elementEnd('div');
    }

    function showProfileBlock()
    {
        $block = new AccountProfileBlock($this, $this->profile);
        $block->show();
    }
}


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

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

require_once(INSTALLDIR.'/lib/rssaction.php');

// Formatting of RSS handled by Rss10Action

class UserrssAction extends Rss10Action
{
    var $tag  = null;

    function prepare($args)
    {
        parent::prepare($args);
        $nickname   = $this->trimmed('nickname');
        $this->user = User::getKV('nickname', $nickname);
        $this->tag  = $this->trimmed('tag');

        if (!$this->user) {
            // TRANS: Client error displayed when user not found for an action.
            $this->clientError(_('No such user.'));
        }

        if (!empty($this->tag)) {
            $this->notices = $this->getTaggedNotices();
        } else {
            $this->notices = $this->getNotices();
        }

        return true;
    }

    function getTaggedNotices()
    {
        $notice = $this->user->getTaggedNotices(
            $this->tag,
            0,
            ($this->limit == 0) ? NOTICES_PER_PAGE : $this->limit,
            0,
            0
        );

        $notices = array();
        while ($notice->fetch()) {
            $notices[] = clone($notice);
        }

        return $notices;
    }

    function getNotices()
    {
        $notice = $this->user->getNotices(
            0,
            ($this->limit == 0) ? NOTICES_PER_PAGE : $this->limit
        );

        $notices = array();
        while ($notice->fetch()) {
            $notices[] = clone($notice);
        }

        return $notices;
    }

    function getChannel()
    {
        $user = $this->user;
        $profile = $user->getProfile();
        $c = array('url' => common_local_url('userrss',
                                             array('nickname' =>
                                                   $user->nickname)),
                   // TRANS: Message is used as link title. %s is a user nickname.
                   'title' => sprintf(_('%s timeline'), $user->nickname),
                   'link' => $profile->profileurl,
                   // TRANS: Message is used as link description. %1$s is a username, %2$s is a site name.
                   'description' => sprintf(_('Updates from %1$s on %2$s!'),
                                            $user->nickname, common_config('site', 'name')));
        return $c;
    }

    function getImage()
    {
        $profile = $this->user->getProfile();
        return $profile->avatarUrl(AVATAR_PROFILE_SIZE);
    }

    // override parent to add X-SUP-ID URL

    function initRss($limit=0)
    {
        $url = common_local_url('sup', null, null, $this->user->id);
        header('X-SUP-ID: '.$url);
        parent::initRss($limit);
    }

    function isReadOnly($args)
    {
        return true;
    }
}


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

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

/**
 * Version info page
 *
 * A page that shows version information for this site. Helpful for
 * debugging, for giving credit to authors, and for linking to more
 * complete documentation for admins.
 *
 * @category Info
 * @package  GNUsocial
 * @author   Evan Prodromou <evan@status.net>
 * @author   Craig Andrews <candrews@integralblue.com>
 * @copyright 2009-2011 Free Software Foundation, Inc http://www.fsf.org
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
 * @link     http://status.net/
 */
class VersionAction extends Action
{
    var $pluginVersions = array();

    /**
     * Return true since we're read-only.
     *
     * @param array $args other arguments
     *
     * @return boolean is read only action?
     */
    function isReadOnly($args)
    {
        return true;
    }

    /**
     * Returns the page title
     *
     * @return string page title
     */
    function title()
    {
        // TRANS: Title for version page. %1$s is the engine name, %2$s is the engine version.
        return sprintf(_('%1$s %2$s'), GNUSOCIAL_ENGINE, GNUSOCIAL_VERSION);
    }

    /**
     * Prepare to run
     *
     * Fire off an event to let plugins report their
     * versions.
     *
     * @param array $args array misc. arguments
     *
     * @return boolean true
     */
    protected function prepare(array $args=array())
    {
        parent::prepare($args);

        Event::handle('PluginVersion', array(&$this->pluginVersions));

        return true;
    }

    /**
     * Execute the action
     *
     * Shows a page with the version information in the
     * content area.
     *
     * @param array $args ignored.
     *
     * @return void
     */
    protected function handle()
    {
        parent::handle();
        $this->showPage();
    }

    /*
    * Override to add h-entry, and content-inner classes
    *
    * @return void
    */
    function showContentBlock()
     {
         $this->elementStart('div', array('id' => 'content', 'class' => 'h-entry'));
         $this->showPageTitle();
         $this->showPageNoticeBlock();
         $this->elementStart('div', array('id' => 'content_inner',
                                          'class' => 'e-content'));
         // show the actual content (forms, lists, whatever)
         $this->showContent();
         $this->elementEnd('div');
         $this->elementEnd('div');
     }

    /*
    * Overrride to add entry-title class
    *
    * @return void
    */
    function showPageTitle() {
        $this->element('h1', array('class' => 'entry-title'), $this->title());
    }

    /**
     * Show version information
     *
     * @return void
     */
    function showContent()
    {
        $this->elementStart('p');

        // TRANS: Content part of engine version page.
        // TRANS: %1$s is the engine name (GNU social) and %2$s is the GNU social version.
        $this->raw(sprintf(_('This site is powered by %1$s version %2$s, '.
                             'Copyright 2008-2013 StatusNet, Inc. '.
                             'and contributors.'),
                           XMLStringer::estring('a', array('href' => GNUSOCIAL_ENGINE_URL),
                                                // TRANS: Engine name.
                                                GNUSOCIAL_ENGINE),
                           GNUSOCIAL_VERSION));
        $this->elementEnd('p');

        // TRANS: Header for engine software contributors section on the version page.
        $this->element('h2', null, _('Contributors'));

        sort($this->contributors);
        $this->element('p', null, implode(', ', $this->contributors));

        // TRANS: Header for engine software license section on the version page.
        $this->element('h2', null, _('License'));

        $this->element('p', null,
                       // TRANS: Content part of engine software version page. %1s is engine name
                       sprintf(_('%1$s 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.'), GNUSOCIAL_ENGINE));

        $this->element('p', null,
                       // TRANS: Content part of engine software version page.
                       _('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.'));

        $this->elementStart('p');
        // TRANS: Content part of engine version page.
        // TRANS: %s is a link to the AGPL license with link description "http://www.gnu.org/licenses/agpl.html".
        $this->raw(sprintf(_('You should have received a copy of the GNU Affero General Public License '.
                             'along with this program.  If not, see %s.'),
                           XMLStringer::estring('a', array('href' => 'http://www.gnu.org/licenses/agpl.html'),
                                                'http://www.gnu.org/licenses/agpl.html')));
        $this->elementEnd('p');

        // XXX: Theme information?

        if (count($this->pluginVersions)) {
            // TRANS: Header for engine plugins section on the version page.
            $this->element('h2', null, _('Plugins'));

            $this->elementStart('table', array('id' => 'plugins_enabled'));

            $this->elementStart('thead');
            $this->elementStart('tr');
            // TRANS: Column header for plugins table on version page.
            $this->element('th', array('id' => 'plugin_name'), _m('HEADER','Name'));
            // TRANS: Column header for plugins table on version page.
            $this->element('th', array('id' => 'plugin_version'), _m('HEADER','Version'));
            // TRANS: Column header for plugins table on version page.
            $this->element('th', array('id' => 'plugin_authors'), _m('HEADER','Author(s)'));
            // TRANS: Column header for plugins table on version page.
            $this->element('th', array('id' => 'plugin_description'), _m('HEADER','Description'));
            $this->elementEnd('tr');
            $this->elementEnd('thead');

            $this->elementStart('tbody');
            foreach ($this->pluginVersions as $plugin) {
                $this->elementStart('tr');
                if (array_key_exists('homepage', $plugin)) {
                    $this->elementStart('th');
                    $this->element('a', array('href' => $plugin['homepage']),
                                   $plugin['name']);
                    $this->elementEnd('th');
                } else {
                    $this->element('th', null, $plugin['name']);
                }

                $this->element('td', null, $plugin['version']);

                if (array_key_exists('author', $plugin)) {
                    $this->element('td', null, $plugin['author']);
                }

                if (array_key_exists('rawdescription', $plugin)) {
                    $this->elementStart('td');
                    $this->raw($plugin['rawdescription']);
                    $this->elementEnd('td');
                } else if (array_key_exists('description', $plugin)) {
                    $this->element('td', null, $plugin['description']);
                }
                $this->elementEnd('tr');
            }
            $this->elementEnd('tbody');
            $this->elementEnd('table');
        }

    }

    var $contributors = array('Evan Prodromou (StatusNet)',
                              'Zach Copley (StatusNet)',
                              'Earle Martin (StatusNet)',
                              'Marie-Claude Doyon (StatusNet)',
                              'Sarven Capadisli (StatusNet)',
                              'Robin Millette (StatusNet)',
                              'Ciaran Gultnieks',
                              'Michael Landers',
                              'Ori Avtalion',
                              'Garret Buell',
                              'Mike Cochrane',
                              'Matthew Gregg',
                              'Florian Biree',
                              'Erik Stambaugh',
                              'drry',
                              'Gina Haeussge',
                              'Tryggvi Björgvinsson',
                              'Adrian Lang',
                              'Meitar Moscovitz',
                              'Sean Murphy',
                              'Leslie Michael Orchard',
                              'Eric Helgeson',
                              'Ken Sedgwick',
                              'Brian Hendrickson',
                              'Tobias Diekershoff',
                              'Dan Moore',
                              'Fil',
                              'Jeff Mitchell',
                              'Brenda Wallace',
                              'Jeffery To',
                              'Federico Marani',
                              'Craig Andrews',
                              'mEDI',
                              'Brett Taylor',
                              'Brigitte Schuster',
                              'Brion Vibber (StatusNet)',
                              'Siebrand Mazeland',
                              'Samantha Doherty (StatusNet)',
                              'Mikael Nordfeldth (FSF)');
}


code:
<?php
/*
 * GNU social - a federating social network
 * Copyright (C) 2014, Free Software Foundation, 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/>.
 */

class Attention extends Managed_DataObject
{
    public $__table = 'attention';  // table name
    public $notice_id;              // int(4) primary_key not_null
    public $profile_id;             // int(4) primary_key not_null
    public $reason;                 // varchar(191)   not 255 because utf8mb4 takes more space
    public $created;                // datetime()   not_null
    public $modified;               // timestamp   not_null default_CURRENT_TIMESTAMP

    public static function schemaDef()
    {
        return array(
            'description' => 'Notice attentions to profiles (that are not a mention and not result of a subscription)',
            'fields' => array(
                'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'notice_id to give attention'),
                'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'profile_id for feed receiver'),
                'reason' => array('type' => 'varchar', 'length' => 191, 'description' => 'Optional reason why this was brought to the attention of profile_id'),
                'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
                'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
            ),
            'primary key' => array('notice_id', 'profile_id'),
            'foreign keys' => array(
                'attention_notice_id_fkey' => array('notice', array('notice_id' => 'id')),
                'attention_profile_id_fkey' => array('profile', array('profile_id' => 'id')),
            ),
            'indexes' => array(
                'attention_notice_id_idx' => array('notice_id'),
                'attention_profile_id_idx' => array('profile_id'),
            ),
        );
    }

    public static function saveNew(Notice $notice, Profile $profile, $reason=null)
    {
        $att = new Attention();

        $att->notice_id = $notice->getID();
        $att->profile_id = $profile->getID();
        $att->reason = $reason;
        $att->created = common_sql_now();
        $result = $att->insert();

        if ($result === false) {
            throw new Exception('Could not saveNew in Attention');
        }
        return $att;
    }
}


code:
<?php
/**
 * Table Definition for avatar
 */
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';

class Avatar extends Managed_DataObject
{
    ###START_AUTOCODE
    /* the code below is auto generated do not remove the above tag */

    public $__table = 'avatar';                          // table name
    public $profile_id;                      // int(4)  primary_key not_null
    public $original;                        // tinyint(1)
    public $width;                           // int(4)  primary_key not_null
    public $height;                          // int(4)  primary_key not_null
    public $mediatype;                       // varchar(32)   not_null
    public $filename;                        // varchar(191)   not 255 because utf8mb4 takes more space
    public $url;                             // varchar(191)  unique_key   not 255 because utf8mb4 takes more space
    public $created;                         // datetime()   not_null
    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP

    /* the code above is auto generated do not remove the tag below */
    ###END_AUTOCODE

    public static function schemaDef()
    {
        return array(
            'fields' => array(
                'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'),
                'original' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'uploaded by user or generated?'),
                'width' => array('type' => 'int', 'not null' => true, 'description' => 'image width'),
                'height' => array('type' => 'int', 'not null' => true, 'description' => 'image height'),
                'mediatype' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'file type'),
                'filename' => array('type' => 'varchar', 'length' => 191, 'description' => 'local filename, if local'),
                'url' => array('type' => 'text', 'description' => 'avatar location, not indexed - do not use in WHERE statement'),
                'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
                'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
            ),
            'primary key' => array('profile_id', 'width', 'height'),
            'unique keys' => array(
//                'avatar_filename_key' => array('filename'),
            ),
            'foreign keys' => array(
                'avatar_profile_id_fkey' => array('profile', array('profile_id' => 'id')),
            ),
            'indexes' => array(
                'avatar_profile_id_idx' => array('profile_id'),
            ),
        );
    }

    // We clean up the file, too
    function delete($useWhere=false)
    {
        $filename = $this->filename;
        if (file_exists(Avatar::path($filename))) {
            @unlink(Avatar::path($filename));
        }

        return parent::delete($useWhere);
    }

    /*
     * Deletes all avatars (but may spare the original) from a profile.
     *
     * @param   Profile $target     The profile we're deleting avatars of.
     * @param   boolean $original   Whether original should be removed or not.
     */
    public static function deleteFromProfile(Profile $target, $original=true) {
        try {
            $avatars = self::getProfileAvatars($target);
            foreach ($avatars as $avatar) {
                if ($avatar->original && !$original) {
                    continue;
                }
                $avatar->delete();
            }
        } catch (NoAvatarException $e) {
            // There are no avatars to delete, a sort of success.
        }

        return true;
    }

    static protected $_avatars = array();

    /*
     * Get an avatar by profile. Currently can't call newSize with $height
     */
    public static function byProfile(Profile $target, $width=null, $height=null)
    {
        $width  = intval($width);
        $height = !is_null($height) ? intval($height) : null;
        if (is_null($height)) {
            $height = $width;
        }

        $size = "{$width}x{$height}";
        if (!isset(self::$_avatars[$target->id])) {
            self::$_avatars[$target->id] = array();
        } elseif (isset(self::$_avatars[$target->id][$size])){
            return self::$_avatars[$target->id][$size];
        }

        $avatar = null;
        if (Event::handle('StartProfileGetAvatar', array($target, $width, &$avatar))) {
            $avatar = self::pkeyGet(
                array(
                    'profile_id' => $target->id,
                    'width'      => $width,
                    'height'     => $height,
                )
            );
            Event::handle('EndProfileGetAvatar', array($target, $width, &$avatar));
        }

        if (is_null($avatar)) {
            // Obviously we can't find an avatar, so let's resize the original!
            $avatar = Avatar::newSize($target, $width);
        } elseif (!($avatar instanceof Avatar)) {
            throw new NoAvatarException($target, $avatar);
        }

        self::$_avatars[$target->id]["{$avatar->width}x{$avatar->height}"] = $avatar;
        return $avatar;
    }

    public static function getUploaded(Profile $target)
    {
        $avatar = new Avatar();
        $avatar->profile_id = $target->id;
        $avatar->original = true;
        if (!$avatar->find(true)) {
            throw new NoAvatarException($target, $avatar);
        }
        if (!file_exists(Avatar::path($avatar->filename))) {
            // The delete call may be odd for, say, unmounted filesystems
            // that cause a file to currently not exist, but actually it does...
            $avatar->delete();
            throw new NoAvatarException($target, $avatar);
        }
        return $avatar;
    }

    public static function getProfileAvatars(Profile $target) {
        $avatar = new Avatar();
        $avatar->profile_id = $target->id;
        if (!$avatar->find()) {
            throw new NoAvatarException($target, $avatar);
        }
        return $avatar->fetchAll();
    }

    /**
     * Where should the avatar go for this user?
     */
    static function filename($id, $extension, $size=null, $extra=null)
    {
        if ($size) {
            return $id . '-' . $size . (($extra) ? ('-' . $extra) : '') . $extension;
        } else {
            return $id . '-original' . (($extra) ? ('-' . $extra) : '') . $extension;
        }
    }

    static function path($filename)
    {
        $dir = common_config('avatar', 'dir');

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

        return $dir . $filename;
    }

    static function url($filename)
    {
        $path = common_config('avatar', 'path');

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

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

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

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

        $ssl = common_config('avatar', 'ssl');

        if (is_null($ssl)) { // null -> guess
            if (common_config('site', 'ssl') == 'always' &&
                !common_config('avatar', 'server')) {
                $ssl = true;
            } else {
                $ssl = false;
            }
        }

        $protocol = ($ssl) ? 'https' : 'http';

        return $protocol.'://'.$server.$path.$filename;
    }

    function displayUrl()
    {
        $server = common_config('avatar', 'server');
        if ($server && !empty($this->filename)) {
            return Avatar::url($this->filename);
        } else {
            return $this->url;
        }
    }

    static function urlByProfile(Profile $target, $width=null, $height=null) {
        try {
            return self::byProfile($target,  $width, $height)->displayUrl();
        } catch (Exception $e) {
            return self::defaultImage($width);
        }
    }

    static function defaultImage($size)
    {
        static $sizenames = array(AVATAR_PROFILE_SIZE => 'profile',
                                  AVATAR_STREAM_SIZE => 'stream',
                                  AVATAR_MINI_SIZE => 'mini');
        return Theme::path('default-avatar-'.$sizenames[$size].'.png');
    }

    static function newSize(Profile $target, $width) {
        $width = intval($width);
        if ($width < 1 || $width > common_config('avatar', 'maxsize')) {
            // TRANS: An error message when avatar size is unreasonable
            throw new Exception(_m('Avatar size too large'));
        }
        // So far we only have square avatars and I don't have time to
        // rewrite support for non-square ones right now ;)
        $height = $width;

        $original = Avatar::getUploaded($target);

        $imagefile = new ImageFile(null, Avatar::path($original->filename));
        $filename = Avatar::filename($target->getID(), image_type_to_extension($imagefile->preferredType()),
                                     $width, common_timestamp());
        $imagefile->resizeTo(Avatar::path($filename), array('width'=>$width, 'height'=>$height));

        $scaled = clone($original);
        $scaled->original = false;
        $scaled->width = $width;
        $scaled->height = $height;
        $scaled->url = Avatar::url($filename);
        $scaled->filename = $filename;
        $scaled->created = common_sql_now();

        if (!$scaled->insert()) {
            // TRANS: An error message when unable to insert avatar data into the db
            throw new Exception(_m('Could not insert new avatar data to database'));
        }

        // Return the new avatar object
        return $scaled;
    }
}


code:
<?php
/*
 * Laconica - a distributed open-source microblogging tool
 * Copyright (C) 2008, 2009, Control Yourself, 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')) {
    exit(1);
}

/**
 * Table Definition for config
 */

require_once INSTALLDIR.'/classes/Memcached_DataObject.php';

class Config extends Managed_DataObject
{
    ###START_AUTOCODE
    /* the code below is auto generated do not remove the above tag */

    public $__table = 'config';                          // table name
    public $section;                         // varchar(32)  primary_key not_null
    public $setting;                         // varchar(32)  primary_key not_null
    public $value;                           // varchar(191)   not 255 because utf8mb4 takes more space

    /* the code above is auto generated do not remove the tag below */
    ###END_AUTOCODE

    public static function schemaDef()
    {
        return array(
            'fields' => array(
                'section' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'default' => '', 'description' => 'configuration section'),
                'setting' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'default' => '', 'description' => 'configuration setting'),
                'value' => array('type' => 'varchar', 'length' => 191, 'description' => 'configuration value'),
            ),
            'primary key' => array('section', 'setting'),
        );
    }

    const settingsKey = 'config:settings';

    static function loadSettings()
    {
        try {
            $settings = self::_getSettings();
            if (!empty($settings)) {
                self::_applySettings($settings);
            }
        } catch (Exception $e) {
            return;
        }
    }

    static function _getSettings()
    {
        $c = self::memcache();

        if (!empty($c)) {
            $settings = $c->get(Cache::key(self::settingsKey));
            if ($settings !== false) {
                return $settings;
            }
        }

        $settings = array();

        $config = new Config();

        $config->find();

        while ($config->fetch()) {
            $settings[] = array($config->section, $config->setting, $config->value);
        }

        $config->free();

        if (!empty($c)) {
            $c->set(Cache::key(self::settingsKey), $settings);
        }

        return $settings;
    }

    static function _applySettings($settings)
    {
        global $config;

        foreach ($settings as $s) {
            list($section, $setting, $value) = $s;
            $config[$section][$setting] = $value;
        }
    }

    function insert()
    {
        $result = parent::insert();
        if ($result) {
            Config::_blowSettingsCache();
        }
        return $result;
    }

    function delete($useWhere=false)
    {
        $result = parent::delete($useWhere);
        if ($result !== false) {
            Config::_blowSettingsCache();
        }
        return $result;
    }

    function update($dataObject=false)
    {
        $result = parent::update($dataObject);
        if ($result !== false) {
            Config::_blowSettingsCache();
        }
        return $result;
    }

    static function save($section, $setting, $value)
    {
        $result = null;

        $config = Config::pkeyGet(array('section' => $section,
                                        'setting' => $setting));

        if (!empty($config)) {
            $orig = clone($config);
            $config->value = $value;
            $result = $config->update($orig);
        } else {
            $config = new Config();

            $config->section = $section;
            $config->setting = $setting;
            $config->value   = $value;

            $result = $config->insert();
        }

        return $result;
    }

    function _blowSettingsCache()
    {
        $c = self::memcache();

        if (!empty($c)) {
            $c->delete(Cache::key(self::settingsKey));
        }
    }
}


code:
<?php
/**
 * Table Definition for confirm_address
 */
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';

class Confirm_address extends Managed_DataObject
{
    ###START_AUTOCODE
    /* the code below is auto generated do not remove the above tag */

    public $__table = 'confirm_address';                 // table name
    public $code;                            // varchar(32)  primary_key not_null
    public $user_id;                         // int(4)   not_null
    public $address;                         // varchar(191)   not_null   not 255 because utf8mb4 takes more space
    public $address_extra;                   // varchar(191)   not_null   not 255 because utf8mb4 takes more space
    public $address_type;                    // varchar(8)   not_null
    public $claimed;                         // datetime()
    public $sent;                            // datetime()
    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP

    /* the code above is auto generated do not remove the tag below */
    ###END_AUTOCODE

    public static function schemaDef()
    {
        return array(
            'fields' => array(
                'code' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'good random code'),
                'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'user who requested confirmation'),
                'address' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'address (email, xmpp, SMS, etc.)'),
                'address_extra' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'carrier ID, for SMS'),
                'address_type' => array('type' => 'varchar', 'length' => 8, 'not null' => true, 'description' => 'address type ("email", "xmpp", "sms")'),
                'claimed' => array('type' => 'datetime', 'description' => 'date this was claimed for queueing'),
                'sent' => array('type' => 'datetime', 'description' => 'date this was sent for queueing'),
                'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
            ),
            'primary key' => array('code'),
            'foreign keys' => array(
                'confirm_address_user_id_fkey' => array('user', array('user_id' => 'id')),
            ),
        );
    }

    static function getAddress($address, $addressType)
    {
        $ca = new Confirm_address();

        $ca->address      = $address;
        $ca->address_type = $addressType;

        if ($ca->find(true)) {
            return $ca;
        }

        return null;
    }

    static function saveNew($user, $address, $addressType, $extra=null)
    {
        $ca = new Confirm_address();

        if (!empty($user)) {
            $ca->user_id = $user->id;
        }

        $ca->address       = $address;
        $ca->address_type  = $addressType;
        $ca->address_extra = $extra;
        $ca->code          = common_confirmation_code(64);

        $ca->insert();

        return $ca;
    }
}


code:
<?php
/**
 * Table Definition for consumer
 */
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';

class Consumer extends Managed_DataObject
{
    ###START_AUTOCODE
    /* the code below is auto generated do not remove the above tag */

    public $__table = 'consumer';                        // table name
    public $consumer_key;                    // varchar(191)  primary_key not_null   not 255 because utf8mb4 takes more space
    public $consumer_secret;                 // varchar(191)   not_null   not 255 because utf8mb4 takes more space
    public $seed;                            // char(32)   not_null
    public $created;                         // datetime   not_null
    public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP

    /* the code above is auto generated do not remove the tag below */
    ###END_AUTOCODE

    public static function schemaDef()
    {
        return array(
            'description' => 'OAuth consumer record',
            'fields' => array(
                'consumer_key' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'unique identifier, root URL'),
                'consumer_secret' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'secret value'),
                'seed' => array('type' => 'char', 'length' => 32, 'not null' => true, 'description' => 'seed for new tokens by this consumer'),

                'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
                'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
            ),
            'primary key' => array('consumer_key'),
        );
    }

    static function generateNew()
    {
        $cons = new Consumer();
        $rand = common_random_hexstr(16);

        $cons->seed            = $rand;
        $cons->consumer_key    = md5(time() + $rand);
        $cons->consumer_secret = md5(md5(time() + time() + $rand));
        $cons->created         = common_sql_now();

        return $cons;
    }

    /**
     * Delete a Consumer and related tokens and nonces
     *
     * XXX: Should this happen in an OAuthDataStore instead?
     *
     */
    function delete($useWhere=false)
    {
        // XXX: Is there any reason NOT to do this kind of cleanup?

        $this->_deleteTokens();
        $this->_deleteNonces();

        return parent::delete($useWhere);
    }

    function _deleteTokens()
    {
        $token = new Token();
        $token->consumer_key = $this->consumer_key;
        $token->delete();
    }

    function _deleteNonces()
    {
        $nonce = new Nonce();
        $nonce->consumer_key = $this->consumer_key;
        $nonce->delete();
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Data class for Conversations
 *
 * 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  Data
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @copyright 2010 StatusNet Inc.
 * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

class Conversation extends Managed_DataObject
{
    public $__table = 'conversation';        // table name
    public $id;                              // int(4)  primary_key not_null
    public $uri;                             // varchar(191)  unique_key   not 255 because utf8mb4 takes more space
    public $created;                         // datetime   not_null
    public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP

    public static function schemaDef()
    {
        return array(
            'fields' => array(
                'id' => array('type' => 'int', 'not null' => true, 'description' => 'should be set from root notice id (since 2014-03-01 commit)'),
                'uri' => array('type' => 'varchar', 'not null'=>true, 'length' => 191, 'description' => 'URI of the conversation'),
                'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
                'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
            ),
            'primary key' => array('id'),
            'unique keys' => array(
                'conversation_uri_key' => array('uri'),
            ),
        );
    }

    /**
     * Factory method for creating a new conversation.
     *
     * Use this for locally initiated conversations. Remote notices should
     * preferrably supply their own conversation URIs in the OStatus feed.
     *
     * @return Conversation the new conversation DO
     */
    static function create(Notice $notice, $uri=null)
    {
        if (empty($notice->id)) {
            throw new ServerException(_('Tried to create conversation for not yet inserted notice'));
        }
        $conv = new Conversation();
        $conv->created = common_sql_now();
        $conv->id = $notice->id;
        $conv->uri = $uri ?: sprintf('%s%s=%d:%s=%s:%s=%x',
                             TagURI::mint(),
                             'noticeId', $notice->id,
                             'objectType', 'thread',
                             'crc32', crc32($notice->content));
        $result = $conv->insert();

        if ($result === false) {
            common_log_db_error($conv, 'INSERT', __FILE__);
            throw new ServerException(_('Failed to create conversation for notice'));
        }

        return $conv;
    }

    static function noticeCount($id)
    {
        $keypart = sprintf('conversation:notice_count:%d', $id);

        $cnt = self::cacheGet($keypart);

        if ($cnt !== false) {
            return $cnt;
        }

        $notice               = new Notice();
        $notice->conversation = $id;
        $notice->whereAddIn('verb', array(ActivityVerb::POST, ActivityUtils::resolveUri(ActivityVerb::POST, true)), $notice->columnType('verb'));
        $cnt                  = $notice->count();

        self::cacheSet($keypart, $cnt);

        return $cnt;
    }

    static public function getUrlFromNotice(Notice $notice, $anchor=true)
    {
        $conv = new Conversation();
        $conv->id = $notice->conversation;
        $conv->find(true);
        if (!$conv instanceof Conversation) {
            common_debug('Conversation does not exist for notice ID: '.$notice->id);
            throw new NoResultException($conv);
        }
        return $conv->getUrl($anchor ? $notice->id : null);
    }

    public function getUri()
    {
        return $this->uri;
    }

    public function getUrl($noticeId=null)
    {
        // FIXME: the URL router should take notice-id as an argument...
        return common_local_url('conversation', array('id' => $this->id)) .
                ($noticeId===null ? '' : "#notice-{$noticeId}");
    }

    // FIXME: ...will 500 ever be too low? Taken from ConversationAction::MAX_NOTICES
    public function getNotices($offset=0, $limit=500, Profile $scoped=null)
    {
        if ($scoped === null) {
            $scoped = Profile::current();
        }
        $stream = new ConversationNoticeStream($this->id, $scoped);
        $notices = $stream->getNotices($offset, $limit);
        return $notices;
    }
}


code:
<?php
/*
 * Laconica - a distributed open-source microblogging tool
 * Copyright (C) 2008, 2009, Control Yourself, 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')) {
    exit(1);
}

/**
 * Table Definition for notice
 */
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';

class Deleted_notice extends Managed_DataObject
{
    ###START_AUTOCODE
    /* the code below is auto generated do not remove the above tag */

    public $__table = 'deleted_notice';                  // table name
    public $id;                              // int(4)  primary_key not_null
    public $profile_id;                      // int(4)   not_null
    public $uri;                             // varchar(191)  unique_key   not 255 because utf8mb4 takes more space
    public $created;                         // datetime()   not_null
    public $deleted;                         // datetime()   not_null

    /* the code above is auto generated do not remove the tag below */
    ###END_AUTOCODE

    public static function schemaDef()
    {
        return array(
            'fields' => array(
                'id' => array('type' => 'int', 'not null' => true, 'description' => 'identity of notice'),
                'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'author of the notice'),
                'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universally unique identifier, usually a tag URI'),
                'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was created'),
                'deleted' => array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was created'),
            ),
            'primary key' => array('id'),
            'unique keys' => array(
                'deleted_notice_uri_key' => array('uri'),
            ),
            'indexes' => array(
                'deleted_notice_profile_id_idx' => array('profile_id'),
            ),
        );
    }
}


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('GNUSOCIAL')) { exit(1); }

/**
 * Table Definition for file
 */
class File extends Managed_DataObject
{
    public $__table = 'file';                            // table name
    public $id;                              // int(4)  primary_key not_null
    public $urlhash;                         // varchar(64)  unique_key
    public $url;                             // text
    public $filehash;                        // varchar(64)     indexed
    public $mimetype;                        // varchar(50)
    public $size;                            // int(4)
    public $title;                           // varchar(191)   not 255 because utf8mb4 takes more space
    public $date;                            // int(4)
    public $protected;                       // int(4)
    public $filename;                        // varchar(191)   not 255 because utf8mb4 takes more space
    public $width;                           // int(4)
    public $height;                          // int(4)
    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP

    const URLHASH_ALG = 'sha256';
    const FILEHASH_ALG = 'sha256';

    public static function schemaDef()
    {
        return array(
            'fields' => array(
                'id' => array('type' => 'serial', 'not null' => true),
                'urlhash' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'sha256 of destination URL (url field)'),
                'url' => array('type' => 'text', 'description' => 'destination URL after following possible redirections'),
                'filehash' => array('type' => 'varchar', 'length' => 64, 'not null' => false, 'description' => 'sha256 of the file contents, only for locally stored files of course'),
                'mimetype' => array('type' => 'varchar', 'length' => 50, 'description' => 'mime type of resource'),
                'size' => array('type' => 'int', 'description' => 'size of resource when available'),
                'title' => array('type' => 'varchar', 'length' => 191, 'description' => 'title of resource when available'),
                'date' => array('type' => 'int', 'description' => 'date of resource according to http query'),
                'protected' => array('type' => 'int', 'description' => 'true when URL is private (needs login)'),
                'filename' => array('type' => 'varchar', 'length' => 191, 'description' => 'if a local file, name of the file'),
                'width' => array('type' => 'int', 'description' => 'width in pixels, if it can be described as such and data is available'),
                'height' => array('type' => 'int', 'description' => 'height in pixels, if it can be described as such and data is available'),

                'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
            ),
            'primary key' => array('id'),
            'unique keys' => array(
                'file_urlhash_key' => array('urlhash'),
            ),
            'indexes' => array(
                'file_filehash_idx' => array('filehash'),
            ),
        );
    }

    function isProtected($url) {
        return 'http://www.facebook.com/login.php' === $url;
    }

    /**
     * Save a new file record.
     *
     * @param array $redir_data lookup data eg from File_redirection::where()
     * @param string $given_url
     * @return File
     */
    public static function saveNew(array $redir_data, $given_url) {

        // I don't know why we have to keep doing this but I'm adding this last check to avoid
        // uniqueness bugs.

        $file = File::getKV('urlhash', self::hashurl($given_url));

        if (!$file instanceof File) {
            $file = new File;
            $file->urlhash = self::hashurl($given_url);
            $file->url = $given_url;
            if (!empty($redir_data['protected'])) $file->protected = $redir_data['protected'];
            if (!empty($redir_data['title'])) $file->title = $redir_data['title'];
            if (!empty($redir_data['type'])) $file->mimetype = $redir_data['type'];
            if (!empty($redir_data['size'])) $file->size = intval($redir_data['size']);
            if (isset($redir_data['time']) && $redir_data['time'] > 0) $file->date = intval($redir_data['time']);
            $file_id = $file->insert();
        }

        Event::handle('EndFileSaveNew', array($file, $redir_data, $given_url));
        assert ($file instanceof File);
        return $file;
    }

    /**
     * Go look at a URL and possibly save data about it if it's new:
     * - follow redirect chains and store them in file_redirection
     * - if a thumbnail is available, save it in file_thumbnail
     * - save file record with basic info
     * - optionally save a file_to_post record
     * - return the File object with the full reference
     *
     * @fixme refactor this mess, it's gotten pretty scary.
     * @param string $given_url the URL we're looking at
     * @param int $notice_id (optional)
     * @param bool $followRedirects defaults to true
     *
     * @return mixed File on success, -1 on some errors
     *
     * @throws ServerException on failure
     */
    public static function processNew($given_url, $notice_id=null, $followRedirects=true) {
        if (empty($given_url)) {
            throw new ServerException('No given URL to process');
        }

        $given_url = File_redirection::_canonUrl($given_url);
        if (empty($given_url)) {
            throw new ServerException('No canonical URL from given URL to process');
        }

        $file = null;

        try {
            $file = File::getByUrl($given_url);
        } catch (NoResultException $e) {
            // First check if we have a lookup trace for this URL already
            try {
                $file_redir = File_redirection::getByUrl($given_url);
                $file = File::getKV('id', $file_redir->file_id);
                if (!$file instanceof File) {
                    // File did not exist, let's clean up the File_redirection entry
                    $file_redir->delete();
                }
            } catch (NoResultException $e) {
                // We just wanted to doublecheck whether a File_thumbnail we might've had
                // actually referenced an existing File object.
            }
        }

        // If we still don't have a File object, let's create one now!
        if (!$file instanceof File) {
            // @fixme for new URLs this also looks up non-redirect data
            // such as target content type, size, etc, which we need
            // for File::saveNew(); so we call it even if not following
            // new redirects.
            $redir_data = File_redirection::where($given_url);
            if (is_array($redir_data)) {
                $redir_url = $redir_data['url'];
            } elseif (is_string($redir_data)) {
                $redir_url = $redir_data;
                $redir_data = array();
            } else {
                // TRANS: Server exception thrown when a URL cannot be processed.
                throw new ServerException(sprintf(_("Cannot process URL '%s'"), $given_url));
            }

            if ($redir_url === $given_url || !$followRedirects) {
                // Save the File object based on our lookup trace
                $file = File::saveNew($redir_data, $given_url);
            } else {
                // This seems kind of messed up... for now skipping this part
                // if we're already under a redirect, so we don't go into
                // horrible infinite loops if we've been given an unstable
                // redirect (where the final destination of the first request
                // doesn't match what we get when we ask for it again).
                //
                // Seen in the wild with clojure.org, which redirects through
                // wikispaces for auth and appends session data in the URL params.
                $file = self::processNew($redir_url, $notice_id, /*followRedirects*/false);
                File_redirection::saveNew($redir_data, $file->id, $given_url);
            }

            if (!$file instanceof File) {
                // This should only happen if File::saveNew somehow did not return a File object,
                // though we have an assert for that in case the event there might've gone wrong.
                // If anything else goes wrong, there should've been an exception thrown.
                throw new ServerException('URL processing failed without new File object');
            }
        }

        if (!empty($notice_id)) {
            File_to_post::processNew($file->id, $notice_id);
        }
        return $file;
    }

    public static function respectsQuota(Profile $scoped, $fileSize) {
        if ($fileSize > common_config('attachments', 'file_quota')) {
            // TRANS: Message used to be inserted as %2$s in  the text "No file may
            // TRANS: be larger than %1$d byte and the file you sent was %2$s.".
            // TRANS: %1$d is the number of bytes of an uploaded file.
            $fileSizeText = sprintf(_m('%1$d byte','%1$d bytes',$fileSize),$fileSize);

            $fileQuota = common_config('attachments', 'file_quota');
            // TRANS: Message given if an upload is larger than the configured maximum.
            // TRANS: %1$d (used for plural) is the byte limit for uploads,
            // TRANS: %2$s is the proper form of "n bytes". This is the only ways to have
            // TRANS: gettext support multiple plurals in the same message, unfortunately...
            throw new ClientException(
                    sprintf(_m('No file may be larger than %1$d byte and the file you sent was %2$s. Try to upload a smaller version.',
                              'No file may be larger than %1$d bytes and the file you sent was %2$s. Try to upload a smaller version.',
                              $fileQuota),
                    $fileQuota, $fileSizeText));
        }

        $file = new File;

        $query = "select sum(size) as total from file join file_to_post on file_to_post.file_id = file.id join notice on file_to_post.post_id = notice.id where profile_id = {$scoped->id} and file.url like '%/notice/%/file'";
        $file->query($query);
        $file->fetch();
        $total = $file->total + $fileSize;
        if ($total > common_config('attachments', 'user_quota')) {
            // TRANS: Message given if an upload would exceed user quota.
            // TRANS: %d (number) is the user quota in bytes and is used for plural.
            throw new ClientException(
                    sprintf(_m('A file this large would exceed your user quota of %d byte.',
                              'A file this large would exceed your user quota of %d bytes.',
                              common_config('attachments', 'user_quota')),
                    common_config('attachments', 'user_quota')));
        }
        $query .= ' AND EXTRACT(month FROM file.modified) = EXTRACT(month FROM now()) and EXTRACT(year FROM file.modified) = EXTRACT(year FROM now())';
        $file->query($query);
        $file->fetch();
        $total = $file->total + $fileSize;
        if ($total > common_config('attachments', 'monthly_quota')) {
            // TRANS: Message given id an upload would exceed a user's monthly quota.
            // TRANS: $d (number) is the monthly user quota in bytes and is used for plural.
            throw new ClientException(
                    sprintf(_m('A file this large would exceed your monthly quota of %d byte.',
                              'A file this large would exceed your monthly quota of %d bytes.',
                              common_config('attachments', 'monthly_quota')),
                    common_config('attachments', 'monthly_quota')));
        }
        return true;
    }

    public function getFilename()
    {
        if (!self::validFilename($this->filename)) {
            // TRANS: Client exception thrown if a file upload does not have a valid name.
            throw new ClientException(_("Invalid filename."));
        }
        return $this->filename;
    }

    // where should the file go?

    static function filename(Profile $profile, $origname, $mimetype)
    {
        $ext = self::guessMimeExtension($mimetype);

        // Normalize and make the original filename more URL friendly.
        $origname = basename($origname, ".$ext");
        if (class_exists('Normalizer')) {
            // http://php.net/manual/en/class.normalizer.php
            // http://www.unicode.org/reports/tr15/
            $origname = Normalizer::normalize($origname, Normalizer::FORM_KC);
        }
        $origname = preg_replace('/[^A-Za-z0-9\.\_]/', '_', $origname);

        $nickname = $profile->getNickname();
        $datestamp = strftime('%Y%m%d', time());
        do {
            // generate new random strings until we don't run into a filename collision.
            $random = strtolower(common_confirmation_code(16));
            $filename = "$nickname-$datestamp-$origname-$random.$ext";
        } while (file_exists(self::path($filename)));
        return $filename;
    }

    static function guessMimeExtension($mimetype)
    {
        try {
            $ext = common_supported_mime_to_ext($mimetype);
        } catch (Exception $e) {
            // We don't support this mimetype, but let's guess the extension
            $ext = substr(strrchr($mimetype, '/'), 1);
        }
        return strtolower($ext);
    }

    /**
     * Validation for as-saved base filenames
     */
    static function validFilename($filename)
    {
        return preg_match('/^[A-Za-z0-9._-]+$/', $filename);
    }

    /**
     * @throws ClientException on invalid filename
     */
    static function path($filename)
    {
        if (!self::validFilename($filename)) {
            // TRANS: Client exception thrown if a file upload does not have a valid name.
            throw new ClientException(_("Invalid filename."));
        }
        $dir = common_config('attachments', 'dir');

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

        return $dir . $filename;
    }

    static function url($filename)
    {
        if (!self::validFilename($filename)) {
            // TRANS: Client exception thrown if a file upload does not have a valid name.
            throw new ClientException(_("Invalid filename."));
        }

        if (common_config('site','private')) {

            return common_local_url('getfile',
                                array('filename' => $filename));

        }

        if (GNUsocial::useHTTPS()) {

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

            if (empty($sslserver)) {
                // XXX: this assumes that background dir == site dir + /file/
                // not true if there's another server
                if (is_string(common_config('site', 'sslserver')) &&
                    mb_strlen(common_config('site', 'sslserver')) > 0) {
                    $server = common_config('site', 'sslserver');
                } else if (common_config('site', 'server')) {
                    $server = common_config('site', 'server');
                }
                $path = common_config('site', 'path') . '/file/';
            } else {
                $server = $sslserver;
                $path   = common_config('attachments', 'sslpath');
                if (empty($path)) {
                    $path = common_config('attachments', 'path');
                }
            }

            $protocol = 'https';
        } else {
            $path = common_config('attachments', 'path');
            $server = common_config('attachments', 'server');

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

            $ssl = common_config('attachments', 'ssl');

            $protocol = ($ssl) ? 'https' : 'http';
        }

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

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

        return $protocol.'://'.$server.$path.$filename;
    }

    function getEnclosure(){
        $enclosure = (object) array();
        foreach (array('title', 'url', 'date', 'modified', 'size', 'mimetype') as $key) {
            $enclosure->$key = $this->$key;
        }

        $needMoreMetadataMimetypes = array(null, 'application/xhtml+xml');

        if (!isset($this->filename) && in_array(common_bare_mime($enclosure->mimetype), $needMoreMetadataMimetypes)) {
            // This fetches enclosure metadata for non-local links with unset/HTML mimetypes,
            // which may be enriched through oEmbed or similar (implemented as plugins)
            Event::handle('FileEnclosureMetadata', array($this, &$enclosure));
        }
        if (empty($enclosure->mimetype) || in_array(common_bare_mime($enclosure->mimetype), $needMoreMetadataMimetypes)) {
            // This means we either don't know what it is, so it can't
            // be shown as an enclosure, or it is an HTML link which
            // does not link to a resource with further metadata.
            throw new ServerException('Unknown enclosure mimetype, not enough metadata');
        }
        return $enclosure;
    }

    /**
     * Get the attachment's thumbnail record, if any.
     * Make sure you supply proper 'int' typed variables (or null).
     *
     * @param $width  int   Max width of thumbnail in pixels. (if null, use common_config values)
     * @param $height int   Max height of thumbnail in pixels. (if null, square-crop to $width)
     * @param $crop   bool  Crop to the max-values' aspect ratio
     *
     * @return File_thumbnail
     *
     * @throws UseFileAsThumbnailException  if the file is considered an image itself and should be itself as thumbnail
     * @throws UnsupportedMediaException    if, despite trying, we can't understand how to make a thumbnail for this format
     * @throws ServerException              on various other errors
     */
    public function getThumbnail($width=null, $height=null, $crop=false, $force_still=true)
    {
        // Get some more information about this file through our ImageFile class
        $image = ImageFile::fromFileObject($this);
        if ($image->animated && !common_config('thumbnail', 'animated')) {
            // null  means "always use file as thumbnail"
            // false means you get choice between frozen frame or original when calling getThumbnail
            if (is_null(common_config('thumbnail', 'animated')) || !$force_still) {
                throw new UseFileAsThumbnailException($this->id);
            }
        }

        return $image->getFileThumbnail($width, $height, $crop);
    }

    public function getPath()
    {
        $filepath = self::path($this->filename);
        if (!file_exists($filepath)) {
            throw new FileNotFoundException($filepath);
        }
        return $filepath;
    }

    public function getUrl()
    {
        if (!empty($this->filename)) {
            // A locally stored file, so let's generate a URL for our instance.
            $url = self::url($this->filename);
            if (self::hashurl($url) !== $this->urlhash) {
                // For indexing purposes, in case we do a lookup on the 'url' field.
                // also we're fixing possible changes from http to https, or paths
                $this->updateUrl($url);
            }
            return $url;
        }

        // No local filename available, return the URL we have stored
        return $this->url;
    }

    static public function getByUrl($url)
    {
        $file = new File();
        $file->urlhash = self::hashurl($url);
        if (!$file->find(true)) {
            throw new NoResultException($file);
        }
        return $file;
    }

    /**
     * @param   string  $hashstr    String of (preferrably lower case) hexadecimal characters, same as result of 'hash_file(...)'
     */
    static public function getByHash($hashstr, $alg=File::FILEHASH_ALG)
    {
        $file = new File();
        $file->filehash = strtolower($hashstr);
        if (!$file->find(true)) {
            throw new NoResultException($file);
        }
        return $file;
    }

    public function updateUrl($url)
    {
        $file = File::getKV('urlhash', self::hashurl($url));
        if ($file instanceof File) {
            throw new ServerException('URL already exists in DB');
        }
        $sql = 'UPDATE %1$s SET urlhash=%2$s, url=%3$s WHERE urlhash=%4$s;';
        $result = $this->query(sprintf($sql, $this->__table,
                                             $this->_quote((string)self::hashurl($url)),
                                             $this->_quote((string)$url),
                                             $this->_quote((string)$this->urlhash)));
        if ($result === false) {
            common_log_db_error($this, 'UPDATE', __FILE__);
            throw new ServerException("Could not UPDATE {$this->__table}.url");
        }

        return $result;
    }

    /**
     * Blow the cache of notices that link to this URL
     *
     * @param boolean $last Whether to blow the "last" cache too
     *
     * @return void
     */

    function blowCache($last=false)
    {
        self::blow('file:notice-ids:%s', $this->urlhash);
        if ($last) {
            self::blow('file:notice-ids:%s;last', $this->urlhash);
        }
        self::blow('file:notice-count:%d', $this->id);
    }

    /**
     * Stream of notices linking to this URL
     *
     * @param integer $offset   Offset to show; default is 0
     * @param integer $limit    Limit of notices to show
     * @param integer $since_id Since this notice
     * @param integer $max_id   Before this notice
     *
     * @return array ids of notices that link to this file
     */

    function stream($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
    {
        $stream = new FileNoticeStream($this);
        return $stream->getNotices($offset, $limit, $since_id, $max_id);
    }

    function noticeCount()
    {
        $cacheKey = sprintf('file:notice-count:%d', $this->id);

        $count = self::cacheGet($cacheKey);

        if ($count === false) {

            $f2p = new File_to_post();

            $f2p->file_id = $this->id;

            $count = $f2p->count();

            self::cacheSet($cacheKey, $count);
        }

        return $count;
    }

    public function isLocal()
    {
        return !empty($this->filename);
    }

    public function delete($useWhere=false)
    {
        // Delete the file, if it exists locally
        if (!empty($this->filename) && file_exists(self::path($this->filename))) {
            $deleted = @unlink(self::path($this->filename));
            if (!$deleted) {
                common_log(LOG_ERR, sprintf('Could not unlink existing file: "%s"', self::path($this->filename)));
            }
        }

        // Clear out related things in the database and filesystem, such as thumbnails
        if (Event::handle('FileDeleteRelated', array($this))) {
            $thumbs = new File_thumbnail();
            $thumbs->file_id = $this->id;
            if ($thumbs->find()) {
                while ($thumbs->fetch()) {
                    $thumbs->delete();
                }
            }

            $f2p = new File_to_post();
            $f2p->file_id = $this->id;
            if ($f2p->find()) {
                while ($f2p->fetch()) {
                    $f2p->delete();
                }
            }
        }

        // And finally remove the entry from the database
        return parent::delete($useWhere);
    }

    public function getTitle()
    {
        $title = $this->title ?: $this->filename;

        return $title ?: null;
    }

    static public function hashurl($url)
    {
        if (empty($url)) {
            throw new Exception('No URL provided to hash algorithm.');
        }
        return hash(self::URLHASH_ALG, $url);
    }

    static public function beforeSchemaUpdate()
    {
        $table = strtolower(get_called_class());
        $schema = Schema::get();
        $schemadef = $schema->getTableDef($table);

        // 2015-02-19 We have to upgrade our table definitions to have the urlhash field populated
        if (isset($schemadef['fields']['urlhash']) && isset($schemadef['unique keys']['file_urlhash_key'])) {
            // We already have the urlhash field, so no need to migrate it.
            return;
        }
        echo "\nFound old $table table, upgrading it to contain 'urlhash' field...";

        $file = new File();
        $file->query(sprintf('SELECT id, LEFT(url, 191) AS shortenedurl, COUNT(*) AS c FROM %1$s WHERE LENGTH(url)>191 GROUP BY shortenedurl HAVING c > 1', $schema->quoteIdentifier($table)));
        print "\nFound {$file->N} URLs with too long entries in file table\n";
        while ($file->fetch()) {
            // We've got a URL that is too long for our future file table
            // so we'll cut it. We could save the original URL, but there is
            // no guarantee it is complete anyway since the previous max was 255 chars.
            $dupfile = new File();
            // First we find file entries that would be duplicates of this when shortened
            // ... and we'll just throw the dupes out the window for now! It's already so borken.
            $dupfile->query(sprintf('SELECT * FROM file WHERE LEFT(url, 191) = "%1$s"', $file->shortenedurl));
            // Leave one of the URLs in the database by using ->find(true) (fetches first entry)
            if ($dupfile->find(true)) {
                print "\nShortening url entry for $table id: {$file->id} [";
                $orig = clone($dupfile);
                $dupfile->url = $file->shortenedurl;    // make sure it's only 191 chars from now on
                $dupfile->update($orig);
                print "\nDeleting duplicate entries of too long URL on $table id: {$file->id} [";
                // only start deleting with this fetch.
                while($dupfile->fetch()) {
                    print ".";
                    $dupfile->delete();
                }
                print "]\n";
            } else {
                print "\nWarning! URL suddenly disappeared from database: {$file->url}\n";
            }
        }
        echo "...and now all the non-duplicates which are longer than 191 characters...\n";
        $file->query('UPDATE file SET url=LEFT(url, 191) WHERE LENGTH(url)>191');

        echo "\n...now running hacky pre-schemaupdate change for $table:";
        // We have to create a urlhash that is _not_ the primary key,
        // transfer data and THEN run checkSchema
        $schemadef['fields']['urlhash'] = array (
                                              'type' => 'varchar',
                                              'length' => 64,
                                              'not null' => false,  // this is because when adding column, all entries will _be_ NULL!
                                              'description' => 'sha256 of destination URL (url field)',
                                            );
        $schemadef['fields']['url'] = array (
                                              'type' => 'text',
                                              'description' => 'destination URL after following possible redirections',
                                            );
        unset($schemadef['unique keys']);
        $schema->ensureTable($table, $schemadef);
        echo "DONE.\n";

        $classname = ucfirst($table);
        $tablefix = new $classname;
        // urlhash is hash('sha256', $url) in the File table
        echo "Updating urlhash fields in $table table...";
        // Maybe very MySQL specific :(
        $tablefix->query(sprintf('UPDATE %1$s SET %2$s=%3$s;',
                            $schema->quoteIdentifier($table),
                            'urlhash',
                            // The line below is "result of sha256 on column `url`"
                            'SHA2(url, 256)'));
        echo "DONE.\n";
        echo "Resuming core schema upgrade...";
    }
}