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

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

/**
 * Class for communication with Facebook
 *
 * @category Plugin
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class Facebookclient
{
    protected $facebook      = null; // Facebook Graph client obj
    protected $flink         = null; // Foreign_link StatusNet -> Facebook
    protected $notice        = null; // The user's notice
    protected $user          = null; // Sender of the notice

    /**
     *
     * @param Notice $notice the notice to manipulate
     * @param Profile $profile local user to act as; if left empty, the notice's poster will be used.
     */
    function __construct($notice, $profile=null)
    {
        $this->facebook = self::getFacebook();

        if (empty($this->facebook)) {
            throw new FacebookApiException(
                "Could not create Facebook client! Bad application ID or secret?"
            );
        }

        $this->notice = $notice;

        $profile_id = $profile ? $profile->id : $notice->profile_id;
        $this->flink = Foreign_link::getByUserID(
            $profile_id,
            FACEBOOK_SERVICE
        );

        if (!empty($this->flink)) {
            $this->user = $this->flink->getUser();
        }
    }

    /*
     * Get an instance of the Facebook Graph SDK object
     *
     * @param string $appId     Application
     * @param string $secret    Facebook API secret
     *
     * @return Facebook A Facebook SDK obj
     */
    static function getFacebook($appId = null, $secret = null)
    {
        // Check defaults and configuration for application ID and secret
        if (empty($appId)) {
            $appId = common_config('facebook', 'appid');
        }

        if (empty($secret)) {
            $secret = common_config('facebook', 'secret');
        }

        // If there's no app ID and secret set in the local config, look
        // for a global one
        if (empty($appId) || empty($secret)) {
            $appId  = common_config('facebook', 'global_appid');
            $secret = common_config('facebook', 'global_secret');
        }

        if (empty($appId)) {
            common_log(
                LOG_WARNING,
                "Couldn't find Facebook application ID!",
                __FILE__
            );
        }

        if (empty($secret)) {
            common_log(
                LOG_WARNING,
                "Couldn't find Facebook application ID!",
                __FILE__
            );
        }

        return new Facebook(
            array(
               'appId'  => $appId,
               'secret' => $secret,
               'cookie' => true
            )
        );
    }

    /*
     * Broadcast a notice to Facebook
     *
     * @param Notice $notice    the notice to send
     */
    static function facebookBroadcastNotice($notice)
    {
        $client = new Facebookclient($notice);
        return $client->sendNotice();
    }

    /*
     * Should the notice go to Facebook?
     */
    function isFacebookBound() {

        if (empty($this->flink)) {
            // User hasn't setup bridging
            return false;
        }

        // Avoid a loop
        if ($this->notice->source == 'Facebook') {
            common_log(
                LOG_INFO,
                sprintf(
                    'Skipping notice %d because its source is Facebook.',
                    $this->notice->id
                ),
                __FILE__
            );
            return false;
        }

        // If the user does not want to broadcast to Facebook, move along
        if (!($this->flink->noticesync & FOREIGN_NOTICE_SEND == FOREIGN_NOTICE_SEND)) {
            common_log(
                LOG_INFO,
                sprintf(
                    'Skipping notice %d because user has FOREIGN_NOTICE_SEND bit off.',
                    $this->notice->id
                ),
                __FILE__
            );
            return false;
        }

        // If it's not a reply, or if the user WANTS to send @-replies,
        // then, yeah, it can go to Facebook.
        if (!preg_match('/@[a-zA-Z0-9_]{1,15}\b/u', $this->notice->content) ||
            ($this->flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
            return true;
        }

        return false;
    }

    /*
     * Determine whether we should send this notice using the Graph API or the
     * old REST API and then dispatch
     */
    function sendNotice()
    {
        // If there's nothing in the credentials field try to send via
        // the Old Rest API

        if ($this->isFacebookBound()) {
            common_debug("notice is facebook bound", __FILE__);
            if (empty($this->flink->credentials)) {
                return $this->sendOldRest();
            } else {

                // Otherwise we most likely have an access token
                return $this->sendGraph();
            }
        }

        // dequeue
        return true;
    }

    /*
     * Send a notice to Facebook using the Graph API
     */
    function sendGraph()
    {
        try {

            $fbuid = $this->flink->foreign_id;

            common_debug(
                sprintf(
                    "Attempting use Graph API to post notice %d as a stream item for %s (%d), fbuid %d",
                    $this->notice->id,
                    $this->user->nickname,
                    $this->user->id,
                    $fbuid
                ),
                __FILE__
            );

            $params = array(
                'access_token' => $this->flink->credentials,
                // XXX: Need to worrry about length of the message?
                'message'      => $this->notice->content
            );

            $attachments = $this->notice->attachments();

            if (!empty($attachments)) {

                // We can only send one attachment with the Graph API :(

                $first = array_shift($attachments);

                if (substr($first->mimetype, 0, 6) == 'image/'
                    || in_array(
                        $first->mimetype,
                        array('application/x-shockwave-flash', 'audio/mpeg' ))) {

                   $params['picture'] = $first->url;
                   $params['caption'] = 'Click for full size';
                   $params['source']  = $first->url;
                }

            }

            $result = $this->facebook->api(
                sprintf('/%s/feed', $fbuid), 'post', $params
            );

            // Save a mapping
            Notice_to_item::saveNew($this->notice->id, $result['id']);

            common_log(
                LOG_INFO,
                sprintf(
                    "Posted notice %d as a stream item for %s (%d), fbuid %d",
                    $this->notice->id,
                    $this->user->nickname,
                    $this->user->id,
                    $fbuid
                ),
                __FILE__
            );

        } catch (FacebookApiException $e) {
            return $this->handleFacebookError($e);
        }

        return true;
    }

    /*
     * Send a notice to Facebook using the deprecated Old REST API. We need this
     * for backwards compatibility. Users who signed up for Facebook bridging
     * using the old Facebook Canvas application do not have an OAuth 2.0
     * access token.
     */
    function sendOldRest()
    {
        try {

            $canPublish = $this->checkPermission('publish_stream');
            $canUpdate  = $this->checkPermission('status_update');

            // We prefer to use stream.publish, because it can handle
            // attachments and returns the ID of the published item

            if ($canPublish == 1) {
                $this->restPublishStream();
            } else if ($canUpdate == 1) {
                // as a last resort we can just update the user's "status"
                $this->restStatusUpdate();
            } else {

                $msg = 'Not sending notice %d to Facebook because user %s '
                     . '(%d), fbuid %d,  does not have \'status_update\' '
                     . 'or \'publish_stream\' permission.';

                common_log(
                    LOG_WARNING,
                    sprintf(
                        $msg,
                        $this->notice->id,
                        $this->user->nickname,
                        $this->user->id,
                        $this->flink->foreign_id
                    ),
                    __FILE__
                );
            }

        } catch (FacebookApiException $e) {
            return $this->handleFacebookError($e);
        }

        return true;
    }

    /*
     * Query Facebook to to see if a user has permission
     *
     *
     *
     * @param $permission the permission to check for - must be either
     *                    public_stream or status_update
     *
     * @return boolean result
     */
    function checkPermission($permission)
    {
        if (!in_array($permission, array('publish_stream', 'status_update'))) {
             throw new ServerException("No such permission!");
        }

        $fbuid = $this->flink->foreign_id;

        common_debug(
            sprintf(
                'Checking for %s permission for user %s (%d), fbuid %d',
                $permission,
                $this->user->nickname,
                $this->user->id,
                $fbuid
            ),
            __FILE__
        );

        $hasPermission = $this->facebook->api(
            array(
                'method'   => 'users.hasAppPermission',
                'ext_perm' => $permission,
                'uid'      => $fbuid
            )
        );

        if ($hasPermission == 1) {

            common_debug(
                sprintf(
                    '%s (%d), fbuid %d has %s permission',
                    $permission,
                    $this->user->nickname,
                    $this->user->id,
                    $fbuid
                ),
                __FILE__
            );

            return true;

        } else {

            $logMsg = '%s (%d), fbuid $fbuid does NOT have %s permission.'
                    . 'Facebook returned: %s';

            common_debug(
                sprintf(
                    $logMsg,
                    $this->user->nickname,
                    $this->user->id,
                    $permission,
                    $fbuid,
                    var_export($result, true)
                ),
                __FILE__
            );

            return false;

        }
    }

    /*
     * Handle a Facebook API Exception
     *
     * @param FacebookApiException $e the exception
     *
     */
    function handleFacebookError($e)
    {
        $fbuid  = $this->flink->foreign_id;
        $errmsg = $e->getMessage();
        $code   = $e->getCode();

        // The Facebook PHP SDK seems to always set the code attribute
        // of the Exception to 0; they put the real error code in
        // the message. Gar!
        if ($code == 0) {
            preg_match('/^\(#(?<code>\d+)\)/', $errmsg, $matches);
            $code = $matches['code'];
        }

        // XXX: Check for any others?
        switch($code) {
         case 100: // Invalid parameter
            $msg = 'Facebook claims notice %d was posted with an invalid '
                 . 'parameter (error code 100 - %s) Notice details: '
                 . '[nickname=%s, user id=%d, fbuid=%d, content="%s"]. '
                 . 'Dequeing.';
            common_log(
                LOG_ERR, sprintf(
                    $msg,
                    $this->notice->id,
                    $errmsg,
                    $this->user->nickname,
                    $this->user->id,
                    $fbuid,
                    $this->notice->content
                ),
                __FILE__
            );
            return true;
            break;
         case 200: // Permissions error
         case 250: // Updating status requires the extended permission status_update
            $this->disconnect();
            return true; // dequeue
            break;
         case 341: // Feed action request limit reached
                $msg = '%s (userid=%d, fbuid=%d) has exceeded his/her limit '
                     . 'for posting notices to Facebook today. Dequeuing '
                     . 'notice %d';
                common_log(
                    LOG_INFO, sprintf(
                        $msg,
                        $user->nickname,
                        $user->id,
                        $fbuid,
                        $this->notice->id
                    ),
                    __FILE__
                );
            // @fixme: We want to rety at a later time when the throttling has expired
            // instead of just giving up.
            return true;
            break;
         default:
            $msg = 'Facebook returned an error we don\'t know how to deal with '
                 . 'when posting notice %d. Error code: %d, error message: "%s"'
                 . ' Notice details: [nickname=%s, user id=%d, fbuid=%d, '
                 . 'notice content="%s"]. Dequeing.';
            common_log(
                LOG_ERR, sprintf(
                    $msg,
                    $this->notice->id,
                    $code,
                    $errmsg,
                    $this->user->nickname,
                    $this->user->id,
                    $fbuid,
                    $this->notice->content
                ),
                __FILE__
            );
            return true; // dequeue
            break;
        }
    }

    /*
     * Publish a notice to Facebook as a status update
     *
     * This is the least preferable way to send a notice to Facebook because
     * it doesn't support attachments and the API method doesn't return
     * the ID of the post on Facebook.
     *
     */
    function restStatusUpdate()
    {
        $fbuid = $this->flink->foreign_id;

        common_debug(
            sprintf(
                "Attempting to post notice %d as a status update for %s (%d), fbuid %d",
                $this->notice->id,
                $this->user->nickname,
                $this->user->id,
                $fbuid
            ),
            __FILE__
        );

        $result = $this->facebook->api(
            array(
                'method'               => 'users.setStatus',
                'status'               => $this->formatMessage(),
                'status_includes_verb' => true,
                'uid'                  => $fbuid
            )
        );

        if ($result == 1) { // 1 is success

            common_log(
                LOG_INFO,
                sprintf(
                    "Posted notice %s as a status update for %s (%d), fbuid %d",
                    $this->notice->id,
                    $this->user->nickname,
                    $this->user->id,
                    $fbuid
                ),
                __FILE__
            );

            // There is no item ID returned for status update so we can't
            // save a Notice_to_item mapping

        } else {

            $msg = sprintf(
                "Error posting notice %s as a status update for %s (%d), fbuid %d - error code: %s",
                $this->notice->id,
                $this->user->nickname,
                $this->user->id,
                $fbuid,
                $result // will contain 0, or an error
            );

            throw new FacebookApiException($msg, $result);
        }
    }

    /*
     * Publish a notice to a Facebook user's stream using the old REST API
     */
    function restPublishStream()
    {
        $fbuid = $this->flink->foreign_id;

        common_debug(
            sprintf(
                'Attempting to post notice %d as stream item for %s (%d) fbuid %d',
                $this->notice->id,
                $this->user->nickname,
                $this->user->id,
                $fbuid
            ),
            __FILE__
        );

        $fbattachment = $this->formatAttachments();

        $result = $this->facebook->api(
            array(
                'method'     => 'stream.publish',
                'message'    => $this->formatMessage(),
                'attachment' => $fbattachment,
                'uid'        => $fbuid
            )
        );

        if (!empty($result)) { // result will contain the item ID

            // Save a mapping
            Notice_to_item::saveNew($this->notice->id, $result);

            common_log(
                LOG_INFO,
                sprintf(
                    'Posted notice %d as a %s for %s (%d), fbuid %d',
                    $this->notice->id,
                    empty($fbattachment) ? 'stream item' : 'stream item with attachment',
                    $this->user->nickname,
                    $this->user->id,
                    $fbuid
                ),
                __FILE__
            );

        } else {

            $msg = sprintf(
                'Could not post notice %d as a %s for %s (%d), fbuid %d - error code: %s',
                $this->notice->id,
                empty($fbattachment) ? 'stream item' : 'stream item with attachment',
                $this->user->nickname,
                $this->user->id,
                $result, // result will contain an error code
                $fbuid
            );

            throw new FacebookApiException($msg, $result);
        }
    }

    /*
     * Format the text message of a stream item so it's appropriate for
     * sending to Facebook. If the notice is too long, truncate it, and
     * add a linkback to the original notice at the end.
     *
     * @return String $txt the formated message
     */
    function formatMessage()
    {
        // Start with the plaintext source of this notice...
        $txt = $this->notice->content;

        // Facebook has a 420-char hardcoded max.
        if (mb_strlen($statustxt) > 420) {
            $noticeUrl = common_shorten_url($this->notice->uri);
            $urlLen = mb_strlen($noticeUrl);
            $txt = mb_substr($statustxt, 0, 420 - ($urlLen + 3)) . ' … ' . $noticeUrl;
        }

        return $txt;
    }

    /*
     * Format attachments for the old REST API stream.publish method
     *
     * Note: Old REST API supports multiple attachments per post
     *
     */
    function formatAttachments()
    {
        $attachments = $this->notice->attachments();

        $fbattachment          = array();
        $fbattachment['media'] = array();

        foreach($attachments as $attachment)
        {
            if($enclosure = $attachment->getEnclosure()){
                $fbmedia = $this->getFacebookMedia($enclosure);
            }else{
                $fbmedia = $this->getFacebookMedia($attachment);
            }
            if($fbmedia){
                $fbattachment['media'][]=$fbmedia;
            }else{
                $fbattachment['name'] = ($attachment->title ?
                                      $attachment->title : $attachment->url);
                $fbattachment['href'] = $attachment->url;
            }
        }
        if(count($fbattachment['media'])>0){
            unset($fbattachment['name']);
            unset($fbattachment['href']);
        }
        return $fbattachment;
    }

    /**
     * given a File objects, returns an associative array suitable for Facebook media
     */
    function getFacebookMedia($attachment)
    {
        $fbmedia    = array();

        if (strncmp($attachment->mimetype, 'image/', strlen('image/')) == 0) {
            $fbmedia['type']         = 'image';
            $fbmedia['src']          = $attachment->url;
            $fbmedia['href']         = $attachment->url;
        } else if ($attachment->mimetype == 'audio/mpeg') {
            $fbmedia['type']         = 'mp3';
            $fbmedia['src']          = $attachment->url;
        }else if ($attachment->mimetype == 'application/x-shockwave-flash') {
            $fbmedia['type']         = 'flash';

            // http://wiki.developers.facebook.com/index.php/Attachment_%28Streams%29
            // says that imgsrc is required... but we have no value to put in it
            // $fbmedia['imgsrc']='';

            $fbmedia['swfsrc']       = $attachment->url;
        }else{
            return false;
        }
        return $fbmedia;
    }

    /*
     * Disconnect a user from Facebook by deleting his Foreign_link.
     * Notifies the user his account has been disconnected by email.
     */
    function disconnect()
    {
        $fbuid = $this->flink->foreign_id;

        common_log(
            LOG_INFO,
            sprintf(
                'Removing Facebook link for %s (%d), fbuid %d',
                $this->user->nickname,
                $this->user->id,
                $fbuid
            ),
            __FILE__
        );

        $result = $this->flink->delete();

        if (empty($result)) {
            common_log(
                LOG_ERR,
                sprintf(
                    'Could not remove Facebook link for %s (%d), fbuid %d',
                    $this->user->nickname,
                    $this->user->id,
                    $fbuid
                ),
                __FILE__
            );
            common_log_db_error($flink, 'DELETE', __FILE__);
        }

        // Notify the user that we are removing their Facebook link
        if (!empty($this->user->email)) {
            $result = $this->mailFacebookDisconnect();

            if (!$result) {

                $msg = 'Unable to send email to notify %s (%d), fbuid %d '
                     . 'about his/her Facebook link being removed.';

                common_log(
                    LOG_WARNING,
                    sprintf(
                        $msg,
                        $this->user->nickname,
                        $this->user->id,
                        $fbuid
                    ),
                    __FILE__
                );
            }

        } else {

            $msg = 'Unable to send email to notify %s (%d), fbuid %d '
                 . 'about his/her Facebook link being removed because the '
                 . 'user has not set an email address.';

            common_log(
                LOG_WARNING,
                sprintf(
                    $msg,
                    $this->user->nickname,
                    $this->user->id,
                    $fbuid
                ),
                __FILE__
            );
        }
    }

    /**
     * Send a mail message to notify a user that her Facebook link
     * has been terminated.
     *
     * @return boolean success flag
     */
    function mailFacebookDisconnect()
    {
        $profile = $this->user->getProfile();

        $siteName = common_config('site', 'name');

        common_switch_locale($this->user->language);

        $subject = _m('Your Facebook connection has been removed');

        $msg = <<<BODY
Hi %1$s,

We're sorry to inform you we are unable to publish your notice to
Facebook, and have removed the connection between your %2$s account and
Facebook.

This may have happened because you have removed permission for %2$s
to post on your behalf, or perhaps you have deactivated your Facebook
account. You can reconnect your %s account to Facebook at any time by
logging in with Facebook again.

Sincerely,

%2$s
BODY;
        $body = sprintf(
            _m($msg),
            $this->user->nickname,
            $siteName
        );

        common_switch_locale();

        $result = mail_to_user($this->user, $subject, $body);

        if (empty($this->user->password)) {
            $result = self::emailWarn($this->user);
        }

        return $result;
    }

    /*
     * Send the user an email warning that their account has been
     * disconnected and he/she has no way to login and must contact
     * the site administrator for help.
     *
     * @param User $user the deauthorizing user
     *
     */
    static function emailWarn($user)
    {
        $profile = $user->getProfile();

        $siteName  = common_config('site', 'name');
        $siteEmail = common_config('site', 'email');

        if (empty($siteEmail)) {
            common_log(
                LOG_WARNING,
                    "No site email address configured. Please set one."
            );
        }

        common_switch_locale($user->language);

        $subject = _m('Contact the %s administrator to retrieve your account');

        $msg = <<<BODY
Hi %1$s,

We've noticed you have deauthorized the Facebook connection for your
%2$s account.  You have not set a password for your %2$s account yet, so
you will not be able to login. If you wish to continue using your %2$s
account, please contact the site administrator (%3$s) to set a password.

Sincerely,

%2$s
BODY;
        $body = sprintf(
            _m($msg),
            $user->nickname,
            $siteName,
            $siteEmail
        );

        common_switch_locale();

        if (mail_to_user($user, $subject, $body)) {
            common_log(
                LOG_INFO,
                sprintf(
                    'Sent account lockout warning to %s (%d)',
                    $user->nickname,
                    $user->id
                ),
                __FILE__
            );
        } else {
            common_log(
                LOG_WARNING,
                sprintf(
                    'Unable to send account lockout warning to %s (%d)',
                    $user->nickname,
                    $user->id
                ),
                __FILE__
            );
        }
    }

    /*
     * Check to see if we have a mapping to a copy of this notice
     * on Facebook
     *
     * @param Notice $notice the notice to check
     *
     * @return mixed null if it can't find one, or the id of the Facebook
     *               stream item
     */
    static function facebookStatusId($notice)
    {
        $n2i = Notice_to_item::staticGet('notice_id', $notice->id);

        if (empty($n2i)) {
            return null;
        } else {
            return $n2i->item_id;
        }
    }

    /*
     * Save a Foreign_user record of a Facebook user
     *
     * @param object $fbuser a Facebook Graph API user obj
     *                       See: http://developers.facebook.com/docs/reference/api/user
     * @return mixed $result Id or key
     *
     */
    static function addFacebookUser($fbuser)
    {
        // remove any existing, possibly outdated, record
        $luser = Foreign_user::getForeignUser($fbuser['id'], FACEBOOK_SERVICE);

        if (!empty($luser)) {

            $result = $luser->delete();

            if ($result != false) {
                common_log(
                    LOG_INFO,
                    sprintf(
                        'Removed old Facebook user: %s, fbuid %d',
                        $fbuid['name'],
                        $fbuid['id']
                    ),
                    __FILE__
                );
            }
        }

        $fuser = new Foreign_user();

        $fuser->nickname = $fbuser['name'];
        $fuser->uri      = $fbuser['link'];
        $fuser->id       = $fbuser['id'];
        $fuser->service  = FACEBOOK_SERVICE;
        $fuser->created  = common_sql_now();

        $result = $fuser->insert();

        if (empty($result)) {
            common_log(
                LOG_WARNING,
                    sprintf(
                        'Failed to add new Facebook user: %s, fbuid %d',
                        $fbuser['name'],
                        $fbuser['id']
                    ),
                    __FILE__
            );

            common_log_db_error($fuser, 'INSERT', __FILE__);
        } else {
            common_log(
                LOG_INFO,
                sprintf(
                    'Added new Facebook user: %s, fbuid %d',
                    $fbuser['name'],
                    $fbuser['id']
                ),
                __FILE__
            );
        }

        return $result;
    }

    /*
     * Remove an item from a Facebook user's feed if we have a mapping
     * for it.
     */
    function streamRemove()
    {
        $n2i = Notice_to_item::staticGet('notice_id', $this->notice->id);

        if (!empty($this->flink) && !empty($n2i)) {

            try {

                $result = $this->facebook->api(
                    array(
                        'method'  => 'stream.remove',
                        'post_id' => $n2i->item_id,
                        'uid'     => $this->flink->foreign_id
                    )
                );

                if (!empty($result) && result == true) {

                    common_log(
                      LOG_INFO,
                        sprintf(
                            'Deleted Facebook item: %s for %s (%d), fbuid %d',
                            $n2i->item_id,
                            $this->user->nickname,
                            $this->user->id,
                            $this->flink->foreign_id
                        ),
                        __FILE__
                    );

                    $n2i->delete();

                } else {
                    throw new FaceboookApiException(var_export($result, true));
                }

            } catch (FacebookApiException $e) {
                common_log(
                  LOG_WARNING,
                    sprintf(
                        'Could not deleted Facebook item: %s for %s (%d), '
                            . 'fbuid %d - (API error: %s) item already deleted '
                            . 'on Facebook? ',
                        $n2i->item_id,
                        $this->user->nickname,
                        $this->user->id,
                        $this->flink->foreign_id,
                        $e
                    ),
                    __FILE__
                );
            }
        }
    }

    /*
     * Like an item in a Facebook user's feed if we have a mapping
     * for it.
     */
    function like()
    {
        $n2i = Notice_to_item::staticGet('notice_id', $this->notice->id);

        if (!empty($this->flink) && !empty($n2i)) {

            try {

                $result = $this->facebook->api(
                    array(
                        'method'  => 'stream.addlike',
                        'post_id' => $n2i->item_id,
                        'uid'     => $this->flink->foreign_id
                    )
                );

                if (!empty($result) && result == true) {

                    common_log(
                      LOG_INFO,
                        sprintf(
                            'Added like for item: %s for %s (%d), fbuid %d',
                            $n2i->item_id,
                            $this->user->nickname,
                            $this->user->id,
                            $this->flink->foreign_id
                        ),
                        __FILE__
                    );

                } else {
                    throw new FacebookApiException(var_export($result, true));
                }

            } catch (FacebookApiException $e) {
                common_log(
                  LOG_WARNING,
                    sprintf(
                        'Could not like Facebook item: %s for %s (%d), '
                            . 'fbuid %d (API error: %s)',
                        $n2i->item_id,
                        $this->user->nickname,
                        $this->user->id,
                        $this->flink->foreign_id,
                        $e
                    ),
                    __FILE__
                );
            }
        }
    }

    /*
     * Unlike an item in a Facebook user's feed if we have a mapping
     * for it.
     */
    function unLike()
    {
        $n2i = Notice_to_item::staticGet('notice_id', $this->notice->id);

        if (!empty($this->flink) && !empty($n2i)) {

            try {

                $result = $this->facebook->api(
                    array(
                        'method'  => 'stream.removeLike',
                        'post_id' => $n2i->item_id,
                        'uid'     => $this->flink->foreign_id
                    )
                );

                if (!empty($result) && result == true) {

                    common_log(
                      LOG_INFO,
                        sprintf(
                            'Removed like for item: %s for %s (%d), fbuid %d',
                            $n2i->item_id,
                            $this->user->nickname,
                            $this->user->id,
                            $this->flink->foreign_id
                        ),
                        __FILE__
                    );

                } else {
                    throw new FacebookApiException(var_export($result, true));
                }

            } catch (FacebookApiException $e) {
                  common_log(
                  LOG_WARNING,
                    sprintf(
                        'Could not remove like for Facebook item: %s for %s '
                          . '(%d), fbuid %d (API error: %s)',
                        $n2i->item_id,
                        $this->user->nickname,
                        $this->user->id,
                        $this->flink->foreign_id,
                        $e
                    ),
                    __FILE__
                );
            }
        }
    }

}
