We are no longer offering accounts on this server. Consider https://gitlab.freedesktop.org/ as a place to host projects.

facebookutil.php 14.7 KB
Newer Older
1 2
<?php
/*
3
 * StatusNet - the distributed open-source microblogging tool
4
 * Copyright (C) 2008, 2009, StatusNet, Inc.
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 *
 * 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/>.
 */

Zach Copley's avatar
Zach Copley committed
20
require_once INSTALLDIR . '/plugins/Facebook/facebook/facebook.php';
21 22
require_once INSTALLDIR . '/plugins/Facebook/facebookaction.php';
require_once INSTALLDIR . '/lib/noticelist.php';
23 24

define("FACEBOOK_SERVICE", 2); // Facebook is foreign_service ID 2
25 26
define("FACEBOOK_NOTICE_PREFIX", 1);
define("FACEBOOK_PROMPTED_UPDATE_PREF", 2);
27

28
function getFacebook()
29
{
30 31
    static $facebook = null;

32 33
    $apikey = common_config('facebook', 'apikey');
    $secret = common_config('facebook', 'secret');
34 35 36 37 38

    if ($facebook === null) {
        $facebook = new Facebook($apikey, $secret);
    }

39
    if (empty($facebook)) {
40 41 42 43 44
        common_log(LOG_ERR, 'Could not make new Facebook client obj!',
            __FILE__);
    }

    return $facebook;
45
}
46

47 48
function isFacebookBound($notice, $flink) {

49 50 51 52
    if (empty($flink)) {
        return false;
    }

53 54 55 56 57 58 59 60
    // Avoid a loop

    if ($notice->source == 'Facebook') {
        common_log(LOG_INFO, "Skipping notice $notice->id because its " .
                   'source is Facebook.');
        return false;
    }

61
    // If the user does not want to broadcast to Facebook, move along
62

63 64 65 66 67 68
    if (!($flink->noticesync & FOREIGN_NOTICE_SEND == FOREIGN_NOTICE_SEND)) {
        common_log(LOG_INFO, "Skipping notice $notice->id " .
            'because user has FOREIGN_NOTICE_SEND bit off.');
        return false;
    }

69 70
    // If it's not a reply, or if the user WANTS to send @-replies,
    // then, yeah, it can go to Facebook.
71 72 73

    if (!preg_match('/@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) ||
        ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
74
        return true;
75 76
    }

77
    return false;
78 79 80 81 82 83

}

function facebookBroadcastNotice($notice)
{
    $facebook = getFacebook();
Zach Copley's avatar
Zach Copley committed
84 85 86 87
    $flink = Foreign_link::getByUserID(
        $notice->profile_id,
        FACEBOOK_SERVICE
    );
88 89 90

    if (isFacebookBound($notice, $flink)) {

91 92
        // Okay, we're good to go, update the FB status

93
        $fbuid = $flink->foreign_id;
94 95
        $user = $flink->getUser();

96
        try {
97

Zach Copley's avatar
Zach Copley committed
98
            // Check permissions
99

Zach Copley's avatar
Zach Copley committed
100 101 102 103
            common_debug(
                'FacebookPlugin - checking for publish_stream permission for user '
                . "$user->nickname ($user->id), Facebook UID: $fbuid"
            );
104

Zach Copley's avatar
Zach Copley committed
105 106 107
            // NOTE: $facebook->api_client->users_hasAppPermission('publish_stream', $fbuid)
            // has been returning bogus results, so we're using FQL to check for
            // publish_stream permission now
108

Zach Copley's avatar
Zach Copley committed
109 110
            $fql = "SELECT publish_stream FROM permissions WHERE uid = $fbuid";
            $result = $facebook->api_client->fql_query($fql);
111

Zach Copley's avatar
Zach Copley committed
112
            $canPublish = 0;
113

Zach Copley's avatar
Zach Copley committed
114 115 116
            if (!empty($result)) {
                $canPublish = $result[0]['publish_stream'];
            }
117

Zach Copley's avatar
Zach Copley committed
118 119 120 121 122 123 124 125 126 127 128 129
            if ($canPublish == 1) {
                common_debug(
                    "FacebookPlugin - $user->nickname ($user->id), Facebook UID: $fbuid "
                    . 'has publish_stream permission.'
                );
            } else {
                common_debug(
                    "FacebookPlugin - $user->nickname ($user->id), Facebook UID: $fbuid "
                    . 'does NOT have publish_stream permission. Facebook '
                    . 'returned: ' . var_export($result, true)
                );
            }
130

Zach Copley's avatar
Zach Copley committed
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
            common_debug(
                'FacebookPlugin - checking for status_update permission for user '
                . "$user->nickname ($user->id), Facebook UID: $fbuid. "
            );

            $canUpdate = $facebook->api_client->users_hasAppPermission(
                'status_update',
                $fbuid
            );

            if ($canUpdate == 1) {
                common_debug(
                    "FacebookPlugin - $user->nickname ($user->id), Facebook UID: $fbuid "
                    . 'has status_update permission.'
                );
            } else {
                common_debug(
                    "FacebookPlugin - $user->nickname ($user->id), Facebook UID: $fbuid "
                    .'does NOT have status_update permission. Facebook '
150
                    . 'returned: ' . var_export($canPublish, true)
Zach Copley's avatar
Zach Copley committed
151 152 153 154 155 156 157 158 159
                );
            }

            // Post to Facebook

            if ($notice->hasAttachments() && $canPublish == 1) {
                publishStream($notice, $user, $fbuid);
            } elseif ($canUpdate == 1 || $canPublish == 1) {
                statusUpdate($notice, $user, $fbuid);
160
            } else {
161
                $msg = "FacebookPlugin - Not sending notice $notice->id to Facebook " .
Zach Copley's avatar
Zach Copley committed
162
                  "because user $user->nickname has not given the " .
163 164 165 166 167 168
                  'Facebook app \'status_update\' or \'publish_stream\' permission.';
                common_log(LOG_WARNING, $msg);
            }

            // Finally, attempt to update the user's profile box

Zach Copley's avatar
Zach Copley committed
169 170
            if ($canPublish == 1 || $canUpdate == 1) {
                updateProfileBox($facebook, $flink, $notice, $user);
171
            }
172 173

        } catch (FacebookRestClientException $e) {
Zach Copley's avatar
Zach Copley committed
174 175 176
            return handleFacebookError($e, $notice, $flink);
        }
    }
177

Zach Copley's avatar
Zach Copley committed
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
    return true;
}

function handleFacebookError($e, $notice, $flink)
{
    $fbuid  = $flink->foreign_id;
    $user   = $flink->getUser();
    $code   = $e->getCode();
    $errmsg = $e->getMessage();

    // XXX: Check for any others?
    switch($code) {
     case 100: // Invalid parameter
        $msg = "FacebookPlugin - Facebook claims notice %d was posted with an invalid parameter (error code 100):"
            . "\"%s\" (Notice details: nickname=%s, user ID=%d, Facebook ID=%d, notice content=\"%s\"). "
            . "Removing notice from the Facebook queue for safety.";
        common_log(
195
            LOG_ERR, sprintf(
Zach Copley's avatar
Zach Copley committed
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
                $msg,
                $notice->id,
                $errmsg,
                $user->nickname,
                $user->id,
                $fbuid,
                $notice->content
            )
        );
        return true;
        break;
     case 200: // Permissions error
     case 250: // Updating status requires the extended permission status_update
        remove_facebook_app($flink);
        return true; // dequeue
        break;
     case 341: // Feed action request limit reached
            $msg = "FacebookPlugin - User %s (User ID=%d, Facebook ID=%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,
                    $notice->id
                )
            );
	// @fixme: We want to rety at a later time when the throttling has expired
	// instead of just giving up.
        return true;
        break;
     default:
        $msg = "FacebookPlugin - Facebook returned an error we don't know how to deal with while trying to "
            . "post notice %d. Error code: %d, error message: \"%s\". (Notice details: "
232 233
            . "nickname=%s, user ID=%d, Facebook ID=%d, notice content=\"%s\"). Removing notice "
	    . "from the Facebook queue for safety.";
Zach Copley's avatar
Zach Copley committed
234
        common_log(
235
            LOG_ERR, sprintf(
Zach Copley's avatar
Zach Copley committed
236 237 238 239 240 241 242 243 244 245
                $msg,
                $notice->id,
                $code,
                $errmsg,
                $user->nickname,
                $user->id,
                $fbuid,
                $notice->content
            )
        );
246
        return true; // dequeue
Zach Copley's avatar
Zach Copley committed
247 248 249
        break;
    }
}
250

Zach Copley's avatar
Zach Copley committed
251 252 253 254 255 256 257
function statusUpdate($notice, $user, $fbuid)
{
    common_debug(
        "FacebookPlugin - Attempting to post notice $notice->id "
        . "as a status update for $user->nickname ($user->id), "
        . "Facebook UID: $fbuid"
    );
258

Zach Copley's avatar
Zach Copley committed
259
    $text = formatNotice($notice, $user, $fbuid);
260

Zach Copley's avatar
Zach Copley committed
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
    $facebook = getFacebook();
    $result = $facebook->api_client->users_setStatus(
         $text,
         $fbuid,
         false,
         true
    );

    common_debug('Facebook returned: ' . var_export($result, true));

    common_log(
        LOG_INFO,
        "FacebookPlugin - Posted notice $notice->id as a status "
        . "update for $user->nickname ($user->id), "
        . "Facebook UID: $fbuid"
    );
}
278

Zach Copley's avatar
Zach Copley committed
279 280 281 282 283 284 285
function publishStream($notice, $user, $fbuid)
{
    common_debug(
        "FacebookPlugin - Attempting to post notice $notice->id "
        . "as stream item with attachment for $user->nickname ($user->id), "
        . "Facebook UID: $fbuid"
    );
286

Zach Copley's avatar
Zach Copley committed
287 288
    $text = formatNotice($notice, $user, $fbuid);
    $fbattachment = format_attachments($notice->attachments());
289

Zach Copley's avatar
Zach Copley committed
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
    $facebook = getFacebook();
    $facebook->api_client->stream_publish(
        $text,
        $fbattachment,
        null,
        null,
        $fbuid
    );

    common_log(
        LOG_INFO,
        "FacebookPlugin - Posted notice $notice->id as a stream "
        . "item with attachment for $user->nickname ($user->id), "
        . "Facebook UID: $fbuid"
    );
}
306

Zach Copley's avatar
Zach Copley committed
307 308 309
function formatNotice($notice, $user, $fbuid)
{
    // Get the status 'verb' the user has set, if any
310

Zach Copley's avatar
Zach Copley committed
311 312 313 314
    common_debug(
        "FacebookPlugin - Looking to see if $user->nickname ($user->id), "
        . "Facebook UID: $fbuid has set a verb for Facebook posting..."
    );
315

Zach Copley's avatar
Zach Copley committed
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
    $facebook = getFacebook();
    $verb = trim(
        $facebook->api_client->data_getUserPreference(
            FACEBOOK_NOTICE_PREFIX,
            $fbuid
        )
    );

    common_debug("Facebook returned " . var_export($verb, true));

    $text = null;

    if (!empty($verb)) {
        common_debug("FacebookPlugin - found a verb: $verb");
        $text = trim($verb) . ' ' . $notice->content;
    } else {
        common_debug("FacebookPlugin - no verb found.");
        $text = $notice->content;
334
    }
335

Zach Copley's avatar
Zach Copley committed
336
    return $text;
337
}
338

Zach Copley's avatar
Zach Copley committed
339 340 341 342 343 344 345 346 347 348
function updateProfileBox($facebook, $flink, $notice, $user) {

    $facebook = getFacebook();
    $fbaction = new FacebookAction(
        $output = 'php://output',
        $indent = null,
        $facebook,
        $flink
    );

349 350
    $fbuid = $flink->foreign_id;

Zach Copley's avatar
Zach Copley committed
351 352
    common_debug(
          'FacebookPlugin - Attempting to update profile box with '
353
          . "content from notice $notice->id for $user->nickname ($user->id), "
Zach Copley's avatar
Zach Copley committed
354 355 356
          . "Facebook UID: $fbuid"
    );

357
    $fbaction->updateProfileBox($notice);
Zach Copley's avatar
Zach Copley committed
358 359 360 361 362 363

    common_debug(
        'FacebookPlugin - finished updating profile box for '
        . "$user->nickname ($user->id) Facebook UID: $fbuid"
    );

364 365 366 367 368 369 370
}

function format_attachments($attachments)
{
    $fbattachment          = array();
    $fbattachment['media'] = array();

371 372
    foreach($attachments as $attachment)
    {
373 374 375 376 377
        if($enclosure = $attachment->getEnclosure()){
            $fbmedia = get_fbmedia_for_attachment($enclosure);
        }else{
            $fbmedia = get_fbmedia_for_attachment($attachment);
        }
378 379 380 381 382 383 384 385 386 387 388 389 390 391
        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;
}
392

393 394 395 396 397
/**
* given an File objects, returns an associative array suitable for Facebook media
*/
function get_fbmedia_for_attachment($attachment)
{
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
    $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{
416
        return false;
417
    }
418
    return $fbmedia;
419
}
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451

function remove_facebook_app($flink)
{

    $user = $flink->getUser();

    common_log(LOG_INFO, 'Removing Facebook App Foreign link for ' .
        "user $user->nickname (user id: $user->id).");

    $result = $flink->delete();

    if (empty($result)) {
        common_log(LOG_ERR, 'Could not remove Facebook App ' .
            "Foreign_link for $user->nickname (user id: $user->id)!");
        common_log_db_error($flink, 'DELETE', __FILE__);
    }

    // Notify the user that we are removing their FB app access

    $result = mail_facebook_app_removed($user);

    if (!$result) {

        $msg = 'Unable to send email to notify ' .
            "$user->nickname (user id: $user->id) " .
            'that their Facebook app link was ' .
            'removed!';

        common_log(LOG_WARNING, $msg);
    }

}
452 453 454 455 456 457 458 459 460 461 462 463

/**
 * Send a mail message to notify a user that her Facebook Application
 * access has been removed.
 *
 * @param User $user   user whose Facebook app link has been removed
 *
 * @return boolean success flag
 */

function mail_facebook_app_removed($user)
{
464 465
    common_init_locale($user->language);

466 467 468 469 470
    $profile = $user->getProfile();

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

    $subject = sprintf(
471
        _m('Your %1$s Facebook application access has been disabled.',
472 473
            $site_name));

474
    $body = sprintf(_m("Hi, %1\$s. We're sorry to inform you that we are " .
475 476 477 478 479 480 481 482
        'unable to update your Facebook status from %2$s, and have disabled ' .
        'the Facebook application for your account. This may be because ' .
        'you have removed the Facebook application\'s authorization, or ' .
        'have deleted your Facebook account.  You can re-enable the ' .
        'Facebook application and automatic status updating by ' .
        "re-installing the %2\$s Facebook application.\n\nRegards,\n\n%2\$s"),
        $user->nickname, $site_name);

483
    common_init_locale();
484 485 486
    return mail_to_user($user, $subject, $body);

}