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);
}

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

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

        $profile = $listenee->getProfile();

        $name = $profile->getBestName();

        $long_name = $other->getFancyName();

        $recipients = $listenee->email;

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

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

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

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

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

        $profile = $listenee->getProfile();

        $name = $profile->getBestName();

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

        $recipients = $listenee->email;

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

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

        // TRANS: Main body of pending new-subscriber notification e-mail.
        // TRANS: %1$s is the subscriber's long name, %2$s is the StatusNet sitename.
        $body = sprintf(_('%1$s would like to listen to your notices on %2$s. ' .
                          'You may approve or reject their subscription at %3$s'),
                        $long_name,
                        common_config('site', 'name'),
                        common_local_url('subqueue', 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 Profile $rcpt  The Profile who recevied the notice, should be a local user
 * @param Notice $notice The notice that was sent
 *
 * @return void
 */
function mail_notify_attn(Profile $rcpt, Notice $notice)
{
    if (!$rcpt->isLocal()) {
        return;
    }

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

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

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

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

    common_switch_locale($user->language);

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

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

        // TRANS: Body of @-reply notification e-mail.
        // TRANS: %1$s is the sending user's name, $2$s is the 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->getID())),//%3
                    $notice->getContent(),//%4
                    $conversationEmailText,//%5
                    common_local_url('newnotice',
                                     array('replyto' => $sender->getNickname(), 'inreplyto' => $notice->getID())),//%6
                    common_local_url('replies',
                                     array('nickname' => $rcpt->getNickname()))) . //%7
                mail_footer_block();
    $headers = _mail_prepare_headers('mention', $rcpt->getNickname(), $sender->getNickname());

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

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

    return $headers;
}

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

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

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

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

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

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

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

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 = [])
    {
        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
/**
 * GNU social - a federating social network
 *
 * Abstraction for media files
 *
 * 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   GNUsocial
 * @author    Robin Millette <robin@millette.info>
 * @author    Miguel Dantas <biodantas@gmail.com>
 * @author    Zach Copley <zach@status.net>
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @copyright 2008-2009, 2019 Free Software Foundation http://fsf.org
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      https://www.gnu.org/software/social/
 */

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

/**
 * Class responsible for abstracting media files
 */
class MediaFile
{
    public $id            = null;
    public $filepath      = null;
    public $filename      = null;
    public $fileRecord    = null;
    public $fileurl       = null;
    public $short_fileurl = null;
    public $mimetype      = null;

    /**
     * @param string $filepath The path of the file this media refers to. Required
     * @param string $mimetype The mimetype of the file. Required
     * @param $filehash        The hash of the file, if known. Optional
     * @param int|null $id     The DB id of the file. Int if known, null if not.
     *                         If null, it searches for it. If -1, it skips all DB
     *                         interactions (useful for temporary objects)
     * @throws ClientException
     * @throws NoResultException
     * @throws ServerException
     */
    public function __construct(string $filepath, string $mimetype, $filehash = null, $id = null)
    {
        $this->filepath = $filepath;
        $this->filename = basename($this->filepath);
        $this->mimetype = $mimetype;
        $this->filehash = self::getHashOfFile($this->filepath, $filehash);
        $this->id       = $id;

        // If id is -1, it means we're dealing with a temporary object and don't want to store it in the DB,
        // or add redirects
        if ($this->id !== -1) {
            if (!empty($this->id)) {
                // If we have an id, load it
                $this->fileRecord = new File();
                $this->fileRecord->id = $this->id;
                if (!$this->fileRecord->find(true)) {
                    // If we have set an ID, we need that ID to exist!
                    throw new NoResultException($this->fileRecord);
                }
            } else {
                // Otherwise, store it
                $this->fileRecord = $this->storeFile();
            }

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

            $this->short_fileurl = common_shorten_url($this->fileurl);
        }
    }

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

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

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

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

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

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

        return $this->fileRecord;
    }

    /**
     * Calculate the hash of a file.
     *
     * This won't work for files >2GiB because PHP uses only 32bit.
     * @param string $filepath
     * @param string|null $filehash
     * @return string
     * @throws ServerException
     */
    public static function getHashOfFile(string $filepath, $filehash = null)
    {
        assert(!empty($filepath), __METHOD__ . ": filepath cannot be null");
        if ($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...
            $filehash = hash_file(File::FILEHASH_ALG, $filepath);
            if ($filehash === false) {
                throw new ServerException('Could not read file for hashing');
            }
        }
        return $filehash;
    }

    /**
     * Retrieve or insert as a file in the DB
     *
     * @return object File
     * @throws ClientException
     * @throws ServerException
     */
    protected function storeFile()
    {
        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 = common_local_url('attachment_view', array('filehash' => $this->filehash));

        $file = new File;

        $file->filename = $this->filename;
        $file->urlhash  = File::hashurl($fileurl);
        $file->url      = $fileurl;
        $file->filehash = $this->filehash;
        $file->size     = filesize($this->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;
    }

    /**
     * The maximum allowed file size, as a string
     */
    public static function maxFileSize()
    {
        $value = self::maxFileSizeInt();
        if ($value > 1024 * 1024) {
            $value = $value/(1024*1024);
            // TRANS: Number of megabytes. %d is the number.
            return sprintf(_m('%dMB', '%dMB', $value), $value);
        } elseif ($value > 1024) {
            $value = $value/1024;
            // TRANS: Number of kilobytes. %d is the number.
            return sprintf(_m('%dkB', '%dkB', $value), $value);
        } else {
            // TRANS: Number of bytes. %d is the number.
            return sprintf(_m('%dB', '%dB', $value), $value);
        }
    }

    /**
     * The maximum allowed file size, as an int
     */
    public static function maxFileSizeInt() : int
    {
        return common_config('attachments', 'file_quota');
    }

    /**
     * Encodes a file name and a file hash in the new file format, which is used to avoid
     * having an extension in the file, removing trust in extensions, while keeping the original name
     * @throws ClientException
     */
    public static function encodeFilename($original_name, string $filehash, $ext = null) : string
    {
        if (empty($original_name)) {
            $original_name = _('Untitled attachment');
        }

        // If we're given an extension explicitly, use it, otherwise...
        $ext = $ext ?:
             // get a replacement extension if configured, returns false if it's blocked,
             // null if no extension
             File::getSafeExtension($original_name);
        if ($ext === false) {
            throw new ClientException(_('Blacklisted file extension.'));
        }

        if (!empty($ext)) {
            // Remove dots if we have them (make sure they're not repeated)
            $ext = preg_replace('/^\.+/', '', $ext);
            $original_name = preg_replace('/\.+.+$/i', ".{$ext}", $original_name);
        }

        $enc_name = bin2hex($original_name);
        return "{$enc_name}-{$filehash}";
    }

    /**
     * Decode the new filename format
     * @return false | null | string on failure, no match (old format) or original file name, respectively
     */
    public static function decodeFilename(string $encoded_filename)
    {
        // The x is because it is using in thumbnails
        $ret = preg_match('/^([^-x]+?)-[^-]+$/', $encoded_filename, $matches);
        if ($ret === false) {
            return false;
        } elseif ($ret === 0) {
            return null; // No match
        } else {
            $filename = hex2bin($matches[1]);

            // Matches extension
            if (preg_match('/^(.+?)\.(.+)$/', $filename, $sub_matches) === 1) {
                $ext = $sub_matches[2];
                // Previously, there was a blacklisted extension array, which could have an alternative
                // extension, such as phps, to replace php. We want to turn it back (this is deprecated,
                // as it no longer makes sense, since we don't trust trust files based on extension,
                // but keep the feature)
                $blacklist = common_config('attachments', 'extblacklist');
                if (is_array($blacklist)) {
                    foreach ($blacklist as $upload_ext => $safe_ext) {
                        if ($ext === $safe_ext) {
                            $ext = $upload_ext;
                            break;
                        }
                    }
                }
                return "{$sub_matches[1]}.{$ext}";
            } else {
                // No extension, don't bother trying to replace it
                return $filename;
            }
        }
    }

    /**
     * Create a new MediaFile or ImageFile object from an upload
     *
     * Tries to set the mimetype correctly, using the most secure method available and rejects the file otherwise.
     * In case the upload is an image, this function returns an new ImageFile (which extends MediaFile)
     * The filename has a new format:
     *   bin2hex("{$original_name}.{$ext}")."-{$filehash}"
     * This format should be respected. Notice the dash, which is important to distinguish it from the previous
     * format ("{$hash}.{$ext}")
     *
     * @param string $param
     * @param Profile|null $scoped
     * @return ImageFile|MediaFile
     * @throws ClientException
     * @throws NoResultException
     * @throws NoUploadedMediaException
     * @throws ServerException
     * @throws UnsupportedMediaException
     * @throws UseFileAsThumbnailException
     */
    public static function fromUpload(string $param='media', Profile $scoped=null)
    {
        // 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:
            case UPLOAD_ERR_FORM_SIZE:
                // TRANS: Exception thrown when too large a file is uploaded.
                // TRANS: %s is the maximum file size, for example "500b", "10kB" or "2MB".
                throw new ClientException(sprintf(
                    _('That file is too big. The maximum file size is %s.'),
                    self::maxFileSize()
                ));
            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.'));
        }

        $filehash = strtolower(self::getHashOfFile($_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.
            // but if the _actual_ locally stored file doesn't exist, getPath will throw FileNotFoundException
            $filepath = $file->getPath();
            $mimetype = $file->mimetype;
        } catch (FileNotFoundException | NoResultException $e) {
            // We have to save the upload as a new local file. This is the normal course of action.
            if ($scoped instanceof Profile) {
                // 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']);
            $media = common_get_mime_media($mimetype);

            $basename = basename($_FILES[$param]['name']);

            if ($media === 'image') {
                // Use -1 for the id to avoid adding this temporary file to the DB
                $img = new ImageFile(-1, $_FILES[$param]['tmp_name']);
                // Validate the image by reencoding it. Additionally normalizes old formats to PNG,
                // keeping JPEG and GIF untouched
                $outpath = $img->resizeTo($img->filepath);
                $ext = image_type_to_extension($img->preferredType(), false);
            }

            $filename = self::encodeFilename($basename, $filehash, $ext);
            $filepath = File::path($filename);

            if ($media === 'image') {
                $result = rename($outpath, $filepath);
            } else {
                $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.
                // UX: too specific
                throw new ClientException(_('File could not be moved to destination directory.'));
            }

            if ($media === 'image') {
                return new ImageFile(null, $filepath);
            }
        }
        return new MediaFile($filepath, $mimetype, $filehash);
    }

    public static function fromFilehandle($fh, Profile $scoped=null)
    {
        $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
            // by using getPath we also check whether the file exists
            // and throw a FileNotFoundException with the path if it doesn't.
            $filename = basename($file->getPath());
            $mimetype = $file->mimetype;
        } catch (FileNotFoundException $e) {
            // This happens if the file we have uploaded has disappeared
            // from the local filesystem for some reason. Since we got the
            // File object from a sha256 check in fromFilehandle, it's safe
            // to just copy the uploaded data to disk!

            fseek($fh, 0);  // just to be sure, go to the beginning
            // dump the contents of our filehandle to the path from our exception
            // and report error if it failed.
            if (false === file_put_contents($e->path, fread($fh, filesize($stream['uri'])))) {
                // 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.'));
            }
            if (!chmod($e->path, 0664)) {
                common_log(LOG_ERR, 'Could not chmod uploaded file: '._ve($e->path));
            }

            $filename = basename($file->getPath());
            $mimetype = $file->mimetype;
        } catch (NoResultException $e) {
            if ($scoped instanceof Profile) {
                File::respectsQuota($scoped, filesize($stream['uri']));
            }

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

            $filename = strtolower($filehash) . '.' . File::guessMimeExtension($mimetype);
            $filepath = File::path($filename);

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

            if (!$result) {
                common_log(LOG_ERR, 'File could not be moved (or chmodded) from '._ve($stream['uri']) . ' to ' . _ve($filepath));
                // 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($filename, $mimetype, $filehash);
    }

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

        $mimetype = null;

        // From CodeIgniter
        // We'll need this to validate the MIME info string (e.g. text/plain; charset=us-ascii)
        $regexp = '/^([a-z\-]+\/[a-z0-9\-\.\+]+)(;\s[^\/]+)?$/';
        /**
         * Fileinfo extension - most reliable method
         *
         * Apparently XAMPP, CentOS, cPanel and who knows what
         * other PHP distribution channels EXPLICITLY DISABLE
         * ext/fileinfo, which is otherwise enabled by default
         * since PHP 5.3 ...
         */
        if (function_exists('finfo_file')) {
            $finfo = @finfo_open(FILEINFO_MIME);
            // It is possible that a FALSE value is returned, if there is no magic MIME database
            // file found on the system
            if (is_resource($finfo)) {
                $mime = @finfo_file($finfo, $filepath);
                finfo_close($finfo);
                /* According to the comments section of the PHP manual page,
                 * it is possible that this function returns an empty string
                 * for some files (e.g. if they don't exist in the magic MIME database)
                 */
                if (is_string($mime) && preg_match($regexp, $mime, $matches)) {
                    $mimetype = $matches[1];
                }
            }
        }
        /* This is an ugly hack, but UNIX-type systems provide a "native" way to detect the file type,
         * which is still more secure than depending on the value of $_FILES[$field]['type'], and as it
         * was reported in issue #750 (https://github.com/EllisLab/CodeIgniter/issues/750) - it's better
         * than mime_content_type() as well, hence the attempts to try calling the command line with
         * three different functions.
         *
         * Notes:
         *  - the DIRECTORY_SEPARATOR comparison ensures that we're not on a Windows system
         *  - many system admins would disable the exec(), shell_exec(), popen() and similar functions
         *    due to security concerns, hence the function_usable() checks
         */
        if (DIRECTORY_SEPARATOR !== '\\') {
            $cmd = 'file --brief --mime '.escapeshellarg($filepath).' 2>&1';
            if (empty($mimetype) && function_exists('exec')) {
                /* This might look confusing, as $mime is being populated with all of the output
                 * when set in the second parameter. However, we only need the last line, which is
                 * the actual return value of exec(), and as such - it overwrites anything that could
                 * already be set for $mime previously. This effectively makes the second parameter a
                 * dummy value, which is only put to allow us to get the return status code.
                 */
                $mime = @exec($cmd, $mime, $return_status);
                if ($return_status === 0 && is_string($mime) && preg_match($regexp, $mime, $matches)) {
                    $mimetype = $matches[1];
                }
            }
            if (empty($mimetype) && function_exists('shell_exec')) {
                $mime = @shell_exec($cmd);
                if (strlen($mime) > 0) {
                    $mime = explode("\n", trim($mime));
                    if (preg_match($regexp, $mime[(count($mime) - 1)], $matches)) {
                        $mimetype = $matches[1];
                    }
                }
            }
            if (empty($mimetype) && function_exists('popen')) {
                $proc = @popen($cmd, 'r');
                if (is_resource($proc)) {
                    $mime = @fread($proc, 512);
                    @pclose($proc);
                    if ($mime !== false) {
                        $mime = explode("\n", trim($mime));
                        if (preg_match($regexp, $mime[(count($mime) - 1)], $matches)) {
                            $mimetype = $matches[1];
                        }
                    }
                }
            }
        }
        // Fall back to mime_content_type(), if available (still better than $_FILES[$field]['type'])
        if (empty($mimetype) && function_exists('mime_content_type')) {
            $mimetype = @mime_content_type($filepath);
            // It's possible that mime_content_type() returns FALSE or an empty string
            if ($mimetype == false && strlen($mimetype) > 0) {
                throw new ServerException(_m('Could not determine file\'s MIME type.'));
            }
        }

        // 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/plain',
                              '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_filename_to_mime($originalFilename);
                return $type;
            } catch (UnknownExtensionMimeException $e) {
                // FIXME: I think we should keep the file extension here (supported should be === true here)
            } catch (Exception $e) {
                // Extension parsed but no connected mimetype, 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);
    }

    /**
     * Title for a file, to display in the interface (if there's no better title) and
     * for download filenames
     * @param $file File object
     * @returns string
     */
    public static function getDisplayName(File $file) : string {
        if (empty($file->filename)) {
            return _('Untitled attachment');
        }

        // New file name format is "{bin2hex(original_name.ext)}-{$hash}"
        $filename = self::decodeFilename($file->filename);

        // If there was an error in the match, something's wrong with some piece
        // of code (could be a file with utf8 chars in the name)
        $log_error_msg = "Invalid file name for File with id={$file->id} " .
                       "({$file->filename}). Some plugin probably did something wrong.";
        if ($filename === false) {
            common_log(LOG_ERR, $log_error_msg);
        } elseif ($filename === null) {
            // The old file name format was "{hash}.{ext}" so we didn't have a name
            // This extracts the extension
            $ret = preg_match('/^.+?\.+?(.+)$/', $file->filename, $matches);
            if ($ret !== 1) {
                common_log(LOG_ERR, $log_error_msg);
                return _('Untitled attachment');
            }
            $ext = $matches[1];
            // There's a blacklisted extension array, which could have an alternative
            // extension, such as phps, to replace php. We want to turn it back
            // (currently defaulted to empty, but let's keep the feature)
            $blacklist = common_config('attachments', 'extblacklist');
            if (is_array($blacklist)) {
                foreach ($blacklist as $upload_ext => $safe_ext) {
                    if ($ext === $safe_ext) {
                        $ext = $upload_ext;
                        break;
                    }
                }
            }
            $filename = "untitled.{$ext}";
        }
        return $filename;
    }
}


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', [$this->action, $menu, $label])) {
            $this->action->elementStart('li');
            $this->action->element('h3', null, $label);
            $menu->show();
            $this->action->elementEnd('li');
            Event::handle('EndSubMenu', [$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

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

/**
 * Moderated notice stream, will take into account the scoping of
 * notices as well as whether the profile is moderated somehow,
 * such as by sandboxing or silencing.
 *
 * Inherits $this->scoped from ScopingNoticeStream as the Profile
 * this stream is meant for. Can be null in case we're not logged in.
 *
 * @category  Stream
 * @package   GNUsocial
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @copyright 2016 Free Software Foundation, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      https://gnu.io/social
 */

class ModeratedNoticeStream extends ScopingNoticeStream
{
    protected function filter(Notice $notice)
    {
        if (!parent::filter($notice)) {
            return false;
        }

        if(self::include_or_not($notice) === false) {
            return false;
        }

        // If this is a repeat the repeated notice is moderated
        if($notice->isRepeat()) {
            try {
                $repeated_notice = Notice::getById($notice->repeat_of);
            } catch (Exception $e) {
                // if we can't get the repeated notice by id, something is seriously wrong with it, so don't include it
                return false;
            }

            if(self::include_or_not($repeated_notice) === false) {
                return false;
            }
        }

        return true;
    }

    protected function include_or_not(Notice $notice)
    {
        $profile = $notice->getProfile();

        if ($profile->isSandboxed()) {
            if (!$this->scoped instanceof Profile) {
                // Non-logged in users don't get to see posts by sandboxed users
                return false;
            } elseif (!$profile->sameAs($this->scoped) && !$this->scoped->hasRight(Right::REVIEWSPAM)) {
                // And if we are logged in, deny if scoped user is neither the author nor has the right to review spam
                return false;
            }
        }
    }
}


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;
    }
}