code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * utilities for sending email
 *
 * 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  Mail
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @author    Zach Copley <zach@status.net>
 * @author    Robin Millette <millette@status.net>
 * @author    Sarven Capadisli <csarven@status.net>
 * @copyright 2008 StatusNet, Inc.
 * @license   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); }

require_once 'Mail.php';

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

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

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

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

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

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

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

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

    if (!$notifyfrom) {

        $domain = mail_domain();

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

    return $notifyfrom;
}

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

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

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

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

/**
 * Send an email to confirm a user's control of an email address
 *
 * @param User   $user     User claiming the email address
 * @param string $code     Confirmation code
 * @param string $nickname Nickname of user
 * @param string $address  email address to confirm
 *
 * @see common_confirmation_code()
 *
 * @return success flag
 */
function mail_confirm_address($user, $code, $nickname, $address, $url=null)
{
    if (empty($url)) {
        $url = common_local_url('confirmaddress', array('code' => $code));
    }

    // TRANS: Subject for address confirmation email.
    $subject = _('Email address confirmation');

    // TRANS: Body for address confirmation email.
    // TRANS: %1$s is the addressed user's nickname, %2$s is the StatusNet sitename,
    // TRANS: %3$s is the URL to confirm at.
    $body = sprintf(_("Hey, %1\$s.\n\n".
                      "Someone just entered this email address on %2\$s.\n\n" .
                      "If it was you, and you want to confirm your entry, ".
                      "use the URL below:\n\n\t%3\$s\n\n" .
                      "If not, just ignore this message.\n\n".
                      "Thanks for your time, \n%2\$s\n"),
                    $nickname,
                    common_config('site', 'name'),
                    $url);

    $headers = array();

    return mail_to_user($user, $subject, $body, $headers, $address);
}

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

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

        $profile = $listenee->getProfile();

        $name = $profile->getBestName();

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

        $recipients = $listenee->email;

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

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

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

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

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

        $profile = $listenee->getProfile();

        $name = $profile->getBestName();

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

        $recipients = $listenee->email;

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

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

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

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

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

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

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

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

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

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

    $name = $profile->getBestName();

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

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

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

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

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

    $user = new User();

    $UT = common_config('db','type')=='pgsql'?'"user"':'user';
    $replies = $notice->getReplies();
    $user->query('SELECT nickname, smsemail, incomingemail ' .
                 "FROM $UT LEFT OUTER JOIN subscription " .
                 "ON $UT.id = subscription.subscriber " .
                 'AND subscription.subscribed = ' . $notice->profile_id . ' ' .
                 'AND subscription.subscribed != subscription.subscriber ' .
                 // Users (other than the sender) who `want SMS notices':
                 "WHERE $UT.id != " . $notice->profile_id . ' ' .
                 "AND $UT.smsemail IS NOT null " .
                 "AND $UT.smsnotify = 1 " .
                 // ... where either the user _is_ subscribed to the sender
                 // (any of the "subscription" fields IS NOT null)
                 // and wants to get SMS for all of this scribe's notices...
                 'AND (subscription.sms = 1 ' .
                 // ... or where the user was mentioned in
                 // or replied-to with the notice:
                 ($replies ? sprintf("OR $UT.id in (%s)",
                                     implode(',', $replies))
                           : '') .
                 ')');

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

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

    return true;
}

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

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

    $other = $notice->getProfile();

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

    $headers = array();

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

    $body = $notice->content;

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

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

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

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

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

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

    $from_profile = $from->getProfile();

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

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

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

/**
 * send a message to notify a user of a direct message (DM)
 *
 * This function checks to see if the recipient wants notification
 * of DMs and has a configured email address.
 *
 * @param Message $message message to notify about
 * @param User    $from    user sending message; default to sender
 * @param User    $to      user receiving message; default to recipient
 *
 * @return boolean success code
 */
function mail_notify_message($message, $from=null, $to=null)
{
    if (is_null($from)) {
        $from = User::getKV('id', $message->from_profile);
    }

    if (is_null($to)) {
        $to = User::getKV('id', $message->to_profile);
    }

    if (is_null($to->email) || !$to->emailnotifymsg) {
        return true;
    }

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

    $from_profile = $from->getProfile();

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

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

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

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

    $sender = $notice->getProfile();

    if ($sender->id == $user->id) {
        return;
    }

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

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

    $bestname = $sender->getBestName();

    common_switch_locale($user->language);

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

    // TRANS: E-mail subject for notice notification.
    // TRANS: %1$s is the sending user's long name, %2$s is the adding user's nickname.
    $subject = sprintf(_('%1$s (@%2$s) sent a notice to your attention'), $bestname, $sender->nickname);

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

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

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

    return $headers;
}

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

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

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

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

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

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

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

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


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

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

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

class MailHandler
{
    function __construct()
    {
    }

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

        $mediafiles = array();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        $attachments = array();

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

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

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

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

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

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

        $output = '';

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

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

            $output .= ' ' . $line;
        }

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

repo_name: dansup/gnu

license: mit


code:
<?php
// @todo FIXME: add standard file header.

/**
 * Form for making a user an admin for a group
 *
 * @category Form
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @author   Sarven Capadisli <csarven@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class MakeAdminForm extends Form
{
    /**
     * Profile of user to block
     */
    var $profile = null;

    /**
     * Group to block the user from
     */
    var $group = null;

    /**
     * Return-to args
     */
    var $args = null;

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

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

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

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

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

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

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

    /**
     * Action elements
     *
     * @return void
     */
    function formActions()
    {
        $this->out->submit(
          'submit',
          // TRANS: Button text for the form that will make a user administrator.
          _m('BUTTON','Make Admin'),
          'submit',
          null,
          // TRANS: Submit button title.
          _m('TOOLTIP','Make this user an admin.'));
    }
}


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

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

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

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

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

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

        $this->showPage();
    }

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

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

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

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


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Abstraction for media files in general
 *
 * TODO: combine with ImageFile?
 *
 * 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  Media
 * @package   StatusNet
 * @author    Robin Millette <robin@millette.info>
 * @author    Zach Copley <zach@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('GNUSOCIAL')) { exit(1); }

class MediaFile
{
    protected $scoped   = null;

    var $filename      = null;
    var $fileRecord    = null;
    var $fileurl       = null;
    var $short_fileurl = null;
    var $mimetype      = null;

    function __construct(Profile $scoped, $filename = null, $mimetype = null, $filehash = null)
    {
        $this->scoped = $scoped;

        $this->filename   = $filename;
        $this->mimetype   = $mimetype;
        $this->filehash   = $filehash;
        $this->fileRecord = $this->storeFile();

        $this->fileurl = common_local_url('attachment',
                                    array('attachment' => $this->fileRecord->id));

        $this->maybeAddRedir($this->fileRecord->id, $this->fileurl);
        $this->short_fileurl = common_shorten_url($this->fileurl);
        $this->maybeAddRedir($this->fileRecord->id, $this->short_fileurl);
    }

    public function attachToNotice(Notice $notice)
    {
        File_to_post::processNew($this->fileRecord, $notice);
    }

    public function getPath()
    {
        return File::path($this->filename);
    }

    function shortUrl()
    {
        return $this->short_fileurl;
    }

    function getEnclosure()
    {
        return $this->getFile()->getEnclosure();
    }

    function delete()
    {
        $filepath = File::path($this->filename);
        @unlink($filepath);
    }

    public function getFile()
    {
        if (!$this->fileRecord instanceof File) {
            throw new ServerException('File record did not exist for MediaFile');
        }

        return $this->fileRecord;
    }

    protected function storeFile()
    {
        $filepath       = File::path($this->filename);
        if (!empty($this->filename) && $this->filehash === null) {
            // Calculate if we have an older upload method somewhere (Qvitter) that
            // doesn't do this before calling new MediaFile on its local files...
            $this->filehash = hash_file(File::FILEHASH_ALG, $filepath);
            if ($this->filehash === false) {
                throw new ServerException('Could not read file for hashing');
            }
        }

        try {
            $file = File::getByHash($this->filehash);
            // We're done here. Yes. Already. We assume sha256 won't collide on us anytime soon.
            return $file;
        } catch (NoResultException $e) {
            // Well, let's just continue below.
        }

        $fileurl = File::url($this->filename);

        $file = new File;

        $file->filename = $this->filename;
        $file->urlhash  = File::hashurl($fileurl);
        $file->url      = $fileurl;
        $file->filehash = $this->filehash;
        $file->size     = filesize($filepath);
        if ($file->size === false) {
            throw new ServerException('Could not read file to get its size');
        }
        $file->date     = time();
        $file->mimetype = $this->mimetype;

        $file_id = $file->insert();

        if ($file_id===false) {
            common_log_db_error($file, "INSERT", __FILE__);
            // TRANS: Client exception thrown when a database error was thrown during a file upload operation.
            throw new ClientException(_('There was a database error while saving your file. Please try again.'));
        }

        // Set file geometrical properties if available
        try {
            $image = ImageFile::fromFileObject($file);
            $orig = clone($file);
            $file->width = $image->width;
            $file->height = $image->height;
            $file->update($orig);

            // We have to cleanup after ImageFile, since it
            // may have generated a temporary file from a
            // video support plugin or something.
            // FIXME: Do this more automagically.
            if ($image->getPath() != $file->getPath()) {
                $image->unlink();
            }
        } catch (ServerException $e) {
            // We just couldn't make out an image from the file. This
            // does not have to be UnsupportedMediaException, as we can
            // also get ServerException from files not existing etc.
        }

        return $file;
    }

    function rememberFile($file, $short)
    {
        $this->maybeAddRedir($file->id, $short);
    }

    function maybeAddRedir($file_id, $url)
    {
        try {
            $file_redir = File_redirection::getByUrl($url);
        } catch (NoResultException $e) {
            $file_redir = new File_redirection;
            $file_redir->urlhash = File::hashurl($url);
            $file_redir->url = $url;
            $file_redir->file_id = $file_id;

            $result = $file_redir->insert();

            if ($result===false) {
                common_log_db_error($file_redir, "INSERT", __FILE__);
                // TRANS: Client exception thrown when a database error was thrown during a file upload operation.
                throw new ClientException(_('There was a database error while saving your file. Please try again.'));
            }
        }
    }

    static function fromUpload($param='media', Profile $scoped=null)
    {
        if (is_null($scoped)) {
            $scoped = Profile::current();
        }

        // The existence of the "error" element means PHP has processed it properly even if it was ok.
        if (!isset($_FILES[$param]) || !isset($_FILES[$param]['error'])) {
            throw new NoUploadedMediaException($param);
        }

        switch ($_FILES[$param]['error']) {
            case UPLOAD_ERR_OK: // success, jump out
                break;
            case UPLOAD_ERR_INI_SIZE:
                // TRANS: Client exception thrown when an uploaded file is larger than set in php.ini.
                throw new ClientException(_('The uploaded file exceeds the ' .
                            'upload_max_filesize directive in php.ini.'));
            case UPLOAD_ERR_FORM_SIZE:
                throw new ClientException(
                        // TRANS: Client exception.
                        _('The uploaded file exceeds the MAX_FILE_SIZE directive' .
                            ' that was specified in the HTML form.'));
            case UPLOAD_ERR_PARTIAL:
                @unlink($_FILES[$param]['tmp_name']);
                // TRANS: Client exception.
                throw new ClientException(_('The uploaded file was only' .
                            ' partially uploaded.'));
            case UPLOAD_ERR_NO_FILE:
                // No file; probably just a non-AJAX submission.
                throw new NoUploadedMediaException($param);
            case UPLOAD_ERR_NO_TMP_DIR:
                // TRANS: Client exception thrown when a temporary folder is not present to store a file upload.
                throw new ClientException(_('Missing a temporary folder.'));
            case UPLOAD_ERR_CANT_WRITE:
                // TRANS: Client exception thrown when writing to disk is not possible during a file upload operation.
                throw new ClientException(_('Failed to write file to disk.'));
            case UPLOAD_ERR_EXTENSION:
                // TRANS: Client exception thrown when a file upload operation has been stopped by an extension.
                throw new ClientException(_('File upload stopped by extension.'));
            default:
                common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " .
                        $_FILES[$param]['error']);
                // TRANS: Client exception thrown when a file upload operation has failed with an unknown reason.
                throw new ClientException(_('System error uploading file.'));
        }

        // TODO: Make documentation clearer that this won't work for files >2GiB because
        //       PHP is stupid in its 32bit head. But noone accepts 2GiB files with PHP
        //       anyway... I hope.
        $filehash = hash_file(File::FILEHASH_ALG, $_FILES[$param]['tmp_name']);

        try {
            $file = File::getByHash($filehash);
            // If no exception is thrown the file exists locally, so we'll use that and just add redirections.
            $filename = $file->filename;
            $mimetype = $file->mimetype;

        } catch (NoResultException $e) {
            // We have to save the upload as a new local file. This is the normal course of action.

            // Throws exception if additional size does not respect quota
            // This test is only needed, of course, if we're uploading something new.
            File::respectsQuota($scoped, $_FILES[$param]['size']);

            $mimetype = self::getUploadedMimeType($_FILES[$param]['tmp_name'], $_FILES[$param]['name']);

            switch (common_config('attachments', 'filename_base')) {
            case 'upload':
                $basename = basename($_FILES[$param]['name']);
                $filename = File::filename($scoped, $basename, $mimetype);
                break;
            case 'hash':
            default:
                $filename = strtolower($filehash) . '.' . File::guessMimeExtension($mimetype);
            }
            $filepath = File::path($filename);

            $result = move_uploaded_file($_FILES[$param]['tmp_name'], $filepath);

            if (!$result) {
                // TRANS: Client exception thrown when a file upload operation fails because the file could
                // TRANS: not be moved from the temporary folder to the permanent file location.
                throw new ClientException(_('File could not be moved to destination directory.'));
            }
        }

        return new MediaFile($scoped, $filename, $mimetype, $filehash);
    }

    static function fromFilehandle($fh, Profile $scoped) {
        $stream = stream_get_meta_data($fh);
        // So far we're only handling filehandles originating from tmpfile(),
        // so we can always do hash_file on $stream['uri'] as far as I can tell!
        $filehash = hash_file(File::FILEHASH_ALG, $stream['uri']);

        try {
            $file = File::getByHash($filehash);
            // Already have it, so let's reuse the locally stored File
            $filename = $file->filename;
            $mimetype = $file->mimetype;
        } catch (NoResultException $e) {
            File::respectsQuota($scoped, filesize($stream['uri']));

            $mimetype = self::getUploadedMimeType($stream['uri']);

            switch (common_config('attachments', 'filename_base')) {
            case 'upload':
                $filename = File::filename($scoped, "email", $mimetype);
                break;
            case 'hash':
            default:
                $filename = strtolower($filehash) . '.' . File::guessMimeExtension($mimetype);
            }
            $filepath = File::path($filename);

            $result = copy($stream['uri'], $filepath) && chmod($filepath, 0664);

            if (!$result) {
                // TRANS: Client exception thrown when a file upload operation fails because the file could
                // TRANS: not be moved from the temporary folder to the permanent file location.
                throw new ClientException(_('File could not be moved to destination directory.' .
                    $stream['uri'] . ' ' . $filepath));
            }
        }

        return new MediaFile($scoped, $filename, $mimetype, $filehash);
    }

    /**
     * Attempt to identify the content type of a given file.
     *
     * @param string $filepath filesystem path as string (file must exist)
     * @param string $originalFilename (optional) for extension-based detection
     * @return string
     *
     * @fixme this seems to tie a front-end error message in, kinda confusing
     *
     * @throws ClientException if type is known, but not supported for local uploads
     */
    static function getUploadedMimeType($filepath, $originalFilename=false) {
        // We only accept filenames to existing files
        $mimelookup = new finfo(FILEINFO_MIME_TYPE);
        $mimetype = $mimelookup->file($filepath);

        // Unclear types are such that we can't really tell by the auto
        // detect what they are (.bin, .exe etc. are just "octet-stream")
        $unclearTypes = array('application/octet-stream',
                              'application/vnd.ms-office',
                              'application/zip',
                              'text/html',  // Ironically, Wikimedia Commons' SVG_logo.svg is identified as text/html
                              // TODO: for XML we could do better content-based sniffing too
                              'text/xml');

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

        // If we didn't match, or it is an unclear match
        if ($originalFilename && (!$mimetype || in_array($mimetype, $unclearTypes))) {
            try {
                $type = common_supported_ext_to_mime($originalFilename);
                return $type;
            } catch (Exception $e) {
                // Extension not found, so $mimetype is our best guess
            }
        }

        // If $config['attachments']['supported'] equals boolean true, accept any mimetype
        if ($supported === true || array_key_exists($mimetype, $supported)) {
            // FIXME: Don't know if it always has a mimetype here because
            // finfo->file CAN return false on error: http://php.net/finfo_file
            // so if $supported === true, this may return something unexpected.
            return $mimetype;
        }

        // We can conclude that we have failed to get the MIME type
        $media = common_get_mime_media($mimetype);
        if ('application' !== $media) {
            // TRANS: Client exception thrown trying to upload a forbidden MIME type.
            // TRANS: %1$s is the file type that was denied, %2$s is the application part of
            // TRANS: the MIME type that was denied.
            $hint = sprintf(_('"%1$s" is not a supported file type on this server. ' .
            'Try using another %2$s format.'), $mimetype, $media);
        } else {
            // TRANS: Client exception thrown trying to upload a forbidden MIME type.
            // TRANS: %s is the file type that was denied.
            $hint = sprintf(_('"%s" is not a supported file type on this server.'), $mimetype);
        }
        throw new ClientException($hint);
    }
}


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

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

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

class Menu extends Widget
{
    var $action     = null;
    var $actionName = null;
    var $actionArgs = null;

    /**
     * Construction
     *
     * @param Action $action current action, used for output
     */
    function __construct(Action $action=null)
    {
        parent::__construct($action);

        $this->action     = $action;
        $this->actionName = $action->trimmed('action');

        $rtargs = $action->returnToArgs();

        $this->actionArgs = $rtargs[1];
    }

    function getItems()
    {
        return array();
    }

    function tag()
    {
        return null;
    }

    function show()
    {
        $items = $this->getItems();
        $tag = $this->tag();

        $attrs = array('class' => 'nav');

        if (!is_null($tag)) {
            $attrs['id']  = 'nav_' . $tag;
        }

        if (Event::handle('StartNav', array($this, &$tag, &$items))) {

            $this->out->elementStart('ul', $attrs);

            foreach ($items as $item) {
                list($actionName, $args, $label, $description, $id) = $item;
                $this->item($actionName, $args, $label, $description, $id);
            }

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

            Event::handle('EndNav', array($this, $tag, $items));
        }
    }

    function item($actionName, array $args, $label, $description, $id=null, $cls=null)
    {
        if (empty($id)) {
            $id = $this->menuItemID($actionName, $args);
        }

        $url = common_local_url($actionName, $args);

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

    function isCurrent($actionName, array $args)
    {
        if ($actionName != $this->actionName) {
            return false;
        }

        foreach ($this->actionArgs as $k => $v) {
            if (!array_key_exists($k, $args) || $args[$k] != $v) {
                return false;
            }
        }

        return true;
    }

    function menuItemID($actionName, $args = null)
    {
        $id = sprintf('nav_%s', $actionName);

        if (!is_null($args)) {
            foreach ($args as $key => $value) {
                $id .= '_' . $key . '_' . $value;
            }
        }

        return $id;
    }

    function submenu($label, $menu)
    {
        if (Event::handle('StartSubMenu', array($this->action, $menu, $label))) {
            $this->action->elementStart('li');
            $this->action->element('h3', null, $label);
            $menu->show();
            $this->action->elementEnd('li');
            Event::handle('EndSubMenu', array($this->action, $menu, $label));
        }
    }
}


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

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

/**
 * Class for an exception when a local user is not found by certain criteria
 *
 * @category Exception
 * @package  StatusNet
 * @author   Mikael Nordfeldth <mmn@hethane.se>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
 * @link     http://status.net/
 */

class MethodNotImplementedException extends ServerException
{
    public function __construct($method)
    {
        parent::__construct(sprintf(_('Method %s not implemented'), $method));
    }
}


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

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

/**
 * Superclass for microapp plugins
 *
 * This class lets you define micro-applications with different kinds of activities.
 *
 * The applications work more-or-less like other
 *
 * @category  Microapp
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */
abstract class MicroAppPlugin extends ActivityHandlerPlugin
{
    /**
     * Returns a localized string which represents this micro-app,
     * to be shown to users selecting what type of post to make.
     * This is paired with the key string in $this->tag().
     *
     * All micro-app classes must override this method.
     *
     * @return string
     */
    abstract function appTitle();

    /**
     * When building the primary notice form, we'll fetch also some
     * alternate forms for specialized types -- that's you!
     *
     * Return a custom Widget or Form object for the given output
     * object, and it'll be included in the HTML output. Beware that
     * your form may be initially hidden.
     *
     * All micro-app classes must override this method.
     *
     * @param HTMLOutputter $out
     * @return Widget
     */
    abstract function entryForm($out);

    /**
     *
     */
    public function newFormAction() {
        // such as 'newbookmark' or 'newevent' route
        return 'new'.$this->tag();
    }

    /**
     * Output the HTML for this kind of object in a list
     *
     * @param NoticeListItem $nli The list item being shown.
     *
     * @return boolean hook value
     */
    function onStartShowNoticeItem(NoticeListItem $nli)
    {
        if (!$this->isMyNotice($nli->notice)) {
            return true;
        }

        // Legacy use was creating a "NoticeListItemAdapter", but
        // nowadays we solve that using event handling for microapps.
        // This section will remain until all plugins are fixed.
        $adapter = $this->adaptNoticeListItem($nli) ?: $nli;

        $adapter->showNotice();
        $adapter->showNoticeAttachments();
        $adapter->showNoticeFooter();

        return false;
    }

    /**
     * Given a notice list item, returns an adapter specific
     * to this plugin.
     *
     * @param NoticeListItem $nli item to adapt
     *
     * @return NoticeListItemAdapter adapter or null
     */
    function adaptNoticeListItem($nli)
    {
      return null;
    }

    function onStartShowEntryForms(&$tabs)
    {
        $tabs[$this->tag()] = array('title' => $this->appTitle(),
                                    'href'  => common_local_url($this->newFormAction()),
                                   );
        return true;
    }

    function onStartMakeEntryForm($tag, $out, &$form)
    {
        if ($tag == $this->tag()) {
            $form = $this->entryForm($out);
            return false;
        }

        return true;
    }
}


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

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

/**
 * A menu with a More... element to show more items
 *
 * @category  General
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */
class MoreMenu extends Menu
{
    const SOFT_MAX = 5;
    const HARD_MAX = 15;

    /**
     * Show a menu with a limited number of elements
     *
     * @param
     *
     * @return
     */
    function show()
    {
        $items = $this->getItems();
        $tag = $this->tag();
        $menuID  = null;

        $attrs = array('class' => 'nav');

        if (!is_null($tag)) {
            $menuID = 'nav_' . $tag;
            $attrs['id'] = $menuID;
        }

        if (Event::handle('StartNav', array($this, &$tag, &$items))) {
            $this->out->elementStart('ul', $attrs);

            $total = count($items);

            if ($total <= self::SOFT_MAX + 1) {
                $toShow = $items;
            } else {
                $toShow = array_slice($items, 0, self::SOFT_MAX);
            }

            foreach ($toShow as $item) {
            	if (count($item) == 5) {
                	list($actionName, $args, $label, $description, $id) = $item;
            	} else {
                	list($actionName, $args, $label, $description) = $item;
                	$id = null;
            	}
                $this->item($actionName, $args, $label, $description, $id);
            }

            if ($total > self::SOFT_MAX + 1) {
                $this->out->elementStart('li', array('class' => 'more_link'));
                $this->out->element('a', array('href' => '#',
                                               'onclick' => 'SN.U.showMoreMenuItems("'.$menuID.'"); return false;'),
                                    // TRANS: Link description to show more items in a list.
                                    _('More ▼'));
                $this->out->elementEnd('li');

                $extended = array_slice($items, self::SOFT_MAX, self::HARD_MAX - self::SOFT_MAX);

                foreach ($extended as $item) {
            		if (count($item) == 5) {
                		list($actionName, $args, $label, $description, $id) = $item;
            		} else {
                		list($actionName, $args, $label, $description) = $item;
                		$id = null;
            		}
                    $this->item($actionName, $args, $label, $description, $id, 'extended_menu');
                }

                if ($total > self::HARD_MAX) {
                    $seeAll = $this->seeAllItem();

                    if (!empty($seeAll)) {
            			if (count($seeAll) == 5) {
                			list($actionName, $args, $label, $description, $id) = $seeAll;
            			} else {
                			list($actionName, $args, $label, $description) = $seeAll;
                			$id = null;
            			}
                        $this->item($actionName, $args, $label, $description, $id, 'extended_menu see_all');
                    }
                }
            }

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

            Event::handle('EndNav', array($this, $tag, $items));
        }
    }

    function seeAllItem()
    {
        return null;
    }
}


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

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

/**
 * Class representing the database schema
 *
 * A class representing the database schema. Can be used to
 * manipulate the schema -- especially for plugins and upgrade
 * utilities.
 *
 * @category Database
 * @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 MysqlSchema extends Schema
{
    static $_single = null;
    protected $conn = null;

    /**
     * Main public entry point. Use this to get
     * the singleton object.
     *
     * @return Schema the (single) Schema object
     */

    static function get($conn = null)
    {
        if (empty(self::$_single)) {
            self::$_single = new Schema($conn);
        }
        return self::$_single;
    }

    /**
     * Returns a TableDef object for the table
     * in the schema with the given name.
     *
     * Throws an exception if the table is not found.
     *
     * @param string $table Name of the table to get
     *
     * @return TableDef tabledef for that table.
     * @throws SchemaTableMissingException
     */

    public function getTableDef($table)
    {
        $def = array();
        $hasKeys = false;

        // Pull column data from INFORMATION_SCHEMA
        $columns = $this->fetchMetaInfo($table, 'COLUMNS', 'ORDINAL_POSITION');
        if (count($columns) == 0) {
            throw new SchemaTableMissingException("No such table: $table");
        }

        foreach ($columns as $row) {

            $name = $row['COLUMN_NAME'];
            $field = array();

            // warning -- 'unsigned' attr on numbers isn't given in DATA_TYPE and friends.
            // It is stuck in on COLUMN_TYPE though (eg 'bigint(20) unsigned')
            $field['type'] = $type = $row['DATA_TYPE'];

            if ($type == 'char' || $type == 'varchar') {
                if ($row['CHARACTER_MAXIMUM_LENGTH'] !== null) {
                    $field['length'] = intval($row['CHARACTER_MAXIMUM_LENGTH']);
                }
            }
            if ($type == 'decimal') {
                // Other int types may report these values, but they're irrelevant.
                // Just ignore them!
                if ($row['NUMERIC_PRECISION'] !== null) {
                    $field['precision'] = intval($row['NUMERIC_PRECISION']);
                }
                if ($row['NUMERIC_SCALE'] !== null) {
                    $field['scale'] = intval($row['NUMERIC_SCALE']);
                }
            }
            if ($row['IS_NULLABLE'] == 'NO') {
                $field['not null'] = true;
            }
            if ($row['COLUMN_DEFAULT'] !== null) {
                // Hack for timestamp cols
                if ($type == 'timestamp' && $row['COLUMN_DEFAULT'] == 'CURRENT_TIMESTAMP') {
                    // skip
                } else {
                    $field['default'] = $row['COLUMN_DEFAULT'];
                    if ($this->isNumericType($type)) {
                        $field['default'] = intval($field['default']);
                    }
                }
            }
            if ($row['COLUMN_KEY'] !== null) {
                // We'll need to look up key info...
                $hasKeys = true;
            }
            if ($row['COLUMN_COMMENT'] !== null && $row['COLUMN_COMMENT'] != '') {
                $field['description'] = $row['COLUMN_COMMENT'];
            }

            $extra = $row['EXTRA'];
            if ($extra) {
                if (preg_match('/(^|\s)auto_increment(\s|$)/i', $extra)) {
                    $field['auto_increment'] = true;
                }
                // $row['EXTRA'] may contain 'on update CURRENT_TIMESTAMP'
                // ^ ...... how to specify?
            }

            if ($row['CHARACTER_SET_NAME'] !== null) {
                // @fixme check against defaults?
                //$def['charset'] = $row['CHARACTER_SET_NAME'];
                //$def['collate']  = $row['COLLATION_NAME'];
            }

            $def['fields'][$name] = $field;
        }

        if ($hasKeys) {
            // INFORMATION_SCHEMA's CONSTRAINTS and KEY_COLUMN_USAGE tables give
            // good info on primary and unique keys but don't list ANY info on
            // multi-value keys, which is lame-o. Sigh.
            //
            // Let's go old school and use SHOW INDEX :D
            //
            $keyInfo = $this->fetchIndexInfo($table);
            $keys = array();
            foreach ($keyInfo as $row) {
                $name = $row['Key_name'];
                $column = $row['Column_name'];

                if (!isset($keys[$name])) {
                    $keys[$name] = array();
                }
                $keys[$name][] = $column;

                if ($name == 'PRIMARY') {
                    $type = 'primary key';
                } else if ($row['Non_unique'] == 0) {
                    $type = 'unique keys';
                } else if ($row['Index_type'] == 'FULLTEXT') {
                    $type = 'fulltext indexes';
                } else {
                    $type = 'indexes';
                }
                $keyTypes[$name] = $type;
            }

            foreach ($keyTypes as $name => $type) {
                if ($type == 'primary key') {
                    // there can be only one
                    $def[$type] = $keys[$name];
                } else {
                    $def[$type][$name] = $keys[$name];
                }
            }
        }
        return $def;
    }

    /**
     * Pull the given table properties from INFORMATION_SCHEMA.
     * Most of the good stuff is MySQL extensions.
     *
     * @return array
     * @throws Exception if table info can't be looked up
     */

    function getTableProperties($table, $props)
    {
        $data = $this->fetchMetaInfo($table, 'TABLES');
        if ($data) {
            return $data[0];
        } else {
            throw new SchemaTableMissingException("No such table: $table");
        }
    }

    /**
     * Pull some INFORMATION.SCHEMA data for the given table.
     *
     * @param string $table
     * @return array of arrays
     */
    function fetchMetaInfo($table, $infoTable, $orderBy=null)
    {
        $query = "SELECT * FROM INFORMATION_SCHEMA.%s " .
                 "WHERE TABLE_SCHEMA='%s' AND TABLE_NAME='%s'";
        $schema = $this->conn->dsn['database'];
        $sql = sprintf($query, $infoTable, $schema, $table);
        if ($orderBy) {
            $sql .= ' ORDER BY ' . $orderBy;
        }
        return $this->fetchQueryData($sql);
    }

    /**
     * Pull 'SHOW INDEX' data for the given table.
     *
     * @param string $table
     * @return array of arrays
     */
    function fetchIndexInfo($table)
    {
        $query = "SHOW INDEX FROM `%s`";
        $sql = sprintf($query, $table);
        return $this->fetchQueryData($sql);
    }

    /**
     * Append an SQL statement with an index definition for a full-text search
     * index over one or more columns on a table.
     *
     * @param array $statements
     * @param string $table
     * @param string $name
     * @param array $def
     */
    function appendCreateFulltextIndex(array &$statements, $table, $name, array $def)
    {
        $statements[] = "CREATE FULLTEXT INDEX $name ON $table " . $this->buildIndexList($def);
    }

    /**
     * Close out a 'create table' SQL statement.
     *
     * @param string $name
     * @param array $def
     * @return string;
     *
     * @fixme ENGINE may need to be set differently in some cases,
     * such as to support fulltext index.
     */
    function endCreateTable($name, array $def)
    {
        $engine = $this->preferredEngine($def);
        return ") ENGINE=$engine CHARACTER SET utf8mb4 COLLATE utf8mb4_bin";
    }

    function preferredEngine($def)
    {
        if (!empty($def['fulltext indexes'])) {
            return 'MyISAM';
        }
        return 'InnoDB';
    }

    /**
     * Get the unique index key name for a given column on this table
     */
    function _uniqueKey($tableName, $columnName)
    {
        return $this->_key($tableName, $columnName);
    }

    /**
     * Get the index key name for a given column on this table
     */
    function _key($tableName, $columnName)
    {
        return "{$tableName}_{$columnName}_idx";
    }

    /**
     * MySQL doesn't take 'DROP CONSTRAINT', need to treat primary keys as
     * if they were indexes here, but can use 'PRIMARY KEY' special name.
     *
     * @param array $phrase
     */
    function appendAlterDropPrimary(array &$phrase)
    {
        $phrase[] = 'DROP PRIMARY KEY';
    }

    /**
     * MySQL doesn't take 'DROP CONSTRAINT', need to treat unique keys as
     * if they were indexes here.
     *
     * @param array $phrase
     * @param <type> $keyName MySQL
     */
    function appendAlterDropUnique(array &$phrase, $keyName)
    {
        $phrase[] = 'DROP INDEX ' . $keyName;
    }

    /**
     * Throw some table metadata onto the ALTER TABLE if we have a mismatch
     * in expected type, collation.
     */
    function appendAlterExtras(array &$phrase, $tableName, array $def)
    {
        // Check for table properties: make sure we're using a sane
        // engine type and charset/collation.
        // @fixme make the default engine configurable?
        $oldProps = $this->getTableProperties($tableName, array('ENGINE', 'TABLE_COLLATION'));
        $engine = $this->preferredEngine($def);
        if (strtolower($oldProps['ENGINE']) != strtolower($engine)) {
            $phrase[] = "ENGINE=$engine";
        }
        if (strtolower($oldProps['TABLE_COLLATION']) != 'utf8mb4_bin') {
            $phrase[] = 'CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_bin';
            $phrase[] = 'DEFAULT CHARACTER SET = utf8mb4';
            $phrase[] = 'DEFAULT COLLATE = utf8mb4_bin';
        }
    }

    /**
     * Is this column a string type?
     */
    private function _isString(array $cd)
    {
        $strings = array('char', 'varchar', 'text');
        return in_array(strtolower($cd['type']), $strings);
    }

    /**
     * Return the proper SQL for creating or
     * altering a column.
     *
     * Appropriate for use in CREATE TABLE or
     * ALTER TABLE statements.
     *
     * @param ColumnDef $cd column to create
     *
     * @return string correct SQL for that column
     */

    function columnSql(array $cd)
    {
        $line = array();
        $line[] = parent::columnSql($cd);

        // This'll have been added from our transform of 'serial' type
        if (!empty($cd['auto_increment'])) {
            $line[] = 'auto_increment';
        }

        if (!empty($cd['description'])) {
            $line[] = 'comment';
            $line[] = $this->quoteValue($cd['description']);
        }

        return implode(' ', $line);
    }

    function mapType($column)
    {
        $map = array('serial' => 'int',
                     'integer' => 'int',
                     'numeric' => 'decimal');

        $type = $column['type'];
        if (isset($map[$type])) {
            $type = $map[$type];
        }

        if (!empty($column['size'])) {
            $size = $column['size'];
            if ($type == 'int' &&
                       in_array($size, array('tiny', 'small', 'medium', 'big'))) {
                $type = $size . $type;
            } else if (in_array($type, array('blob', 'text')) &&
                       in_array($size, array('tiny', 'medium', 'long'))) {
                $type = $size . $type;
            }
        }

        return $type;
    }

    function typeAndSize($column)
    {
        if ($column['type'] == 'enum') {
            $vals = array_map(array($this, 'quote'), $column['enum']);
            return 'enum(' . implode(',', $vals) . ')';
        } else if ($this->_isString($column)) {
            $col = parent::typeAndSize($column);
            if (!empty($column['charset'])) {
                $col .= ' CHARSET ' . $column['charset'];
            }
            if (!empty($column['collate'])) {
                $col .= ' COLLATE ' . $column['collate'];
            }
            return $col;
        } else {
            return parent::typeAndSize($column);
        }
    }

    /**
     * Filter the given table definition array to match features available
     * in this database.
     *
     * This lets us strip out unsupported things like comments, foreign keys,
     * or type variants that we wouldn't get back from getTableDef().
     *
     * @param array $tableDef
     */
    function filterDef(array $tableDef)
    {
        foreach ($tableDef['fields'] as $name => &$col) {
            if ($col['type'] == 'serial') {
                $col['type'] = 'int';
                $col['auto_increment'] = true;
            }
            if ($col['type'] == 'datetime' && isset($col['default']) && $col['default'] == 'CURRENT_TIMESTAMP') {
                $col['type'] = 'timestamp';
            }
            $col['type'] = $this->mapType($col);
            unset($col['size']);
        }
        if (!common_config('db', 'mysql_foreign_keys')) {
            unset($tableDef['foreign keys']);
        }
        return $tableDef;
    }
}


code:
<?php

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

class NetworkPublicNoticeStream extends ScopingNoticeStream
{
    function __construct(Profile $scoped=null)
    {
        parent::__construct(new CachingNoticeStream(new RawNetworkPublicNoticeStream(),
                                                    'networkpublic'),
                            $scoped);
    }
}

/**
 * Raw public stream
 *
 * @category  Stream
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

class RawNetworkPublicNoticeStream extends FullNoticeStream
{
    function getNoticeIds($offset, $limit, $since_id, $max_id)
    {
        $notice = new Notice();

        $notice->selectAdd(); // clears it
        $notice->selectAdd('id');

        $notice->orderBy('created DESC, id DESC');

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

        $notice->whereAdd('is_local ='. Notice::REMOTE);
        // -1 == blacklisted, -2 == gateway (i.e. Twitter)
        $notice->whereAdd('is_local !='. Notice::LOCAL_NONPUBLIC);
        $notice->whereAdd('is_local !='. Notice::GATEWAY);

        Notice::addWhereSinceId($notice, $since_id);
        Notice::addWhereMaxId($notice, $max_id);

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

        $ids = array();

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

        $notice->free();
        $notice = NULL;

        return $ids;
    }
}