]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/FacebookBridge/lib/facebookclient.php
575208cacca8cbee088b3c588680f37db9f741fe
[quix0rs-gnu-social.git] / plugins / FacebookBridge / lib / facebookclient.php
1 <?php
2 /**
3  * StatusNet, the distributed open-source microblogging tool
4  *
5  * Class for communicating with Facebook
6  *
7  * PHP version 5
8  *
9  * LICENCE: This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Affero General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Affero General Public License for more details.
18  *
19  * You should have received a copy of the GNU Affero General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  * @category  Plugin
23  * @package   StatusNet
24  * @author    Craig Andrews <candrews@integralblue.com>
25  * @author    Zach Copley <zach@status.net>
26  * @copyright 2009-2010 StatusNet, Inc.
27  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
28  * @link      http://status.net/
29  */
30
31 if (!defined('STATUSNET')) {
32     exit(1);
33 }
34
35 /**
36  * Class for communication with Facebook
37  *
38  * @category Plugin
39  * @package  StatusNet
40  * @author   Zach Copley <zach@status.net>
41  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
42  * @link     http://status.net/
43  */
44 class Facebookclient
45 {
46     protected $facebook      = null; // Facebook Graph client obj
47     protected $flink         = null; // Foreign_link StatusNet -> Facebook
48     protected $notice        = null; // The user's notice
49     protected $user          = null; // Sender of the notice
50
51     function __construct($notice)
52     {
53         $this->facebook = self::getFacebook();
54         $this->notice   = $notice;
55
56         $this->flink = Foreign_link::getByUserID(
57             $notice->profile_id,
58             FACEBOOK_SERVICE
59         );
60
61         $this->user = $this->flink->getUser();
62     }
63
64     /*
65      * Get an instance of the Facebook Graph SDK object
66      *
67      * @param string $appId     Application
68      * @param string $secret    Facebook API secret
69      *
70      * @return Facebook A Facebook SDK obj
71      */
72     static function getFacebook($appId = null, $secret = null)
73     {
74         // Check defaults and configuration for application ID and secret
75         if (empty($appId)) {
76             $appId = common_config('facebook', 'appid');
77         }
78
79         if (empty($secret)) {
80             $secret = common_config('facebook', 'secret');
81         }
82
83         // If there's no app ID and secret set in the local config, look
84         // for a global one
85         if (empty($appId) || empty($secret)) {
86             $appId  = common_config('facebook', 'global_appid');
87             $secret = common_config('facebook', 'global_secret');
88         }
89
90         return new Facebook(
91             array(
92                'appId'  => $appId,
93                'secret' => $secret,
94                'cookie' => true
95             )
96         );
97     }
98
99     /*
100      * Broadcast a notice to Facebook
101      *
102      * @param Notice $notice    the notice to send
103      */
104     static function facebookBroadcastNotice($notice)
105     {
106         common_debug('Facebook broadcast');
107         $client = new Facebookclient($notice);
108         return $client->sendNotice();
109     }
110
111     /*
112      * Should the notice go to Facebook?
113      */
114     function isFacebookBound() {
115
116         if (empty($this->flink)) {
117             common_log(
118                 LOG_WARN,
119                 sprintf(
120                     "No Foreign_link to Facebook for the author of notice %d.",
121                     $this->notice->id
122                 ),
123                 __FILE__
124             );
125             return false;
126         }
127
128         // Avoid a loop
129         if ($this->notice->source == 'Facebook') {
130             common_log(
131                 LOG_INFO,
132                 sprintf(
133                     'Skipping notice %d because its source is Facebook.',
134                     $this->notice->id
135                 ),
136                 __FILE__
137             );
138             return false;
139         }
140
141         // If the user does not want to broadcast to Facebook, move along
142         if (!($this->flink->noticesync & FOREIGN_NOTICE_SEND == FOREIGN_NOTICE_SEND)) {
143             common_log(
144                 LOG_INFO,
145                 sprintf(
146                     'Skipping notice %d because user has FOREIGN_NOTICE_SEND bit off.',
147                     $this->notice->id
148                 ),
149                 __FILE__
150             );
151             return false;
152         }
153
154         // If it's not a reply, or if the user WANTS to send @-replies,
155         // then, yeah, it can go to Facebook.
156         if (!preg_match('/@[a-zA-Z0-9_]{1,15}\b/u', $this->notice->content) ||
157             ($this->flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
158             return true;
159         }
160
161         return false;
162     }
163
164     /*
165      * Determine whether we should send this notice using the Graph API or the
166      * old REST API and then dispatch
167      */
168     function sendNotice()
169     {
170         // If there's nothing in the credentials field try to send via
171         // the Old Rest API
172
173         if ($this->isFacebookBound()) {
174             common_debug("notice is facebook bound", __FILE__);
175             if (empty($this->flink->credentials)) {
176                 return $this->sendOldRest();
177             } else {
178
179                 // Otherwise we most likely have an access token
180                 return $this->sendGraph();
181             }
182
183         } else {
184             common_debug(
185                 sprintf(
186                     "Skipping notice %d - not bound for Facebook",
187                     $this->notice->id,
188                     __FILE__
189                 )
190             );
191         }
192     }
193
194     /*
195      * Send a notice to Facebook using the Graph API
196      */
197     function sendGraph()
198     {
199         try {
200
201             $fbuid = $this->flink->foreign_id;
202
203             common_debug(
204                 sprintf(
205                     "Attempting use Graph API to post notice %d as a stream item for %s (%d), fbuid %d",
206                     $this->notice->id,
207                     $this->user->nickname,
208                     $this->user->id,
209                     $fbuid
210                 ),
211                 __FILE__
212             );
213
214             $params = array(
215                 'access_token' => $this->flink->credentials,
216                 // XXX: Need to worrry about length of the message?
217                 'message'      => $this->notice->content
218             );
219
220             $attachments = $this->notice->attachments();
221
222             if (!empty($attachments)) {
223
224                 // We can only send one attachment with the Graph API :(
225
226                 $first = array_shift($attachments);
227
228                 if (substr($first->mimetype, 0, 6) == 'image/'
229                     || in_array(
230                         $first->mimetype,
231                         array('application/x-shockwave-flash', 'audio/mpeg' ))) {
232
233                    $params['picture'] = $first->url;
234                    $params['caption'] = 'Click for full size';
235                    $params['source']  = $first->url;
236                 }
237
238             }
239
240             $result = $this->facebook->api(
241                 sprintf('/%s/feed', $fbuid), 'post', $params
242             );
243
244             // Save a mapping
245             Notice_to_item::saveNew($this->notice->id, $result['id']);
246
247             common_log(
248                 LOG_INFO,
249                 sprintf(
250                     "Posted notice %d as a stream item for %s (%d), fbuid %d",
251                     $this->notice->id,
252                     $this->user->nickname,
253                     $this->user->id,
254                     $fbuid
255                 ),
256                 __FILE__
257             );
258
259         } catch (FacebookApiException $e) {
260             return $this->handleFacebookError($e);
261         }
262
263         return true;
264     }
265
266     /*
267      * Send a notice to Facebook using the deprecated Old REST API. We need this
268      * for backwards compatibility. Users who signed up for Facebook bridging
269      * using the old Facebook Canvas application do not have an OAuth 2.0
270      * access token.
271      */
272     function sendOldRest()
273     {
274         try {
275
276             $canPublish = $this->checkPermission('publish_stream');
277             $canUpdate  = $this->checkPermission('status_update');
278
279             // We prefer to use stream.publish, because it can handle
280             // attachments and returns the ID of the published item
281
282             if ($canPublish == 1) {
283                 $this->restPublishStream();
284             } else if ($canUpdate == 1) {
285                 // as a last resort we can just update the user's "status"
286                 $this->restStatusUpdate();
287             } else {
288
289                 $msg = 'Not sending notice %d to Facebook because user %s '
290                      . '(%d), fbuid %d,  does not have \'status_update\' '
291                      . 'or \'publish_stream\' permission.';
292
293                 common_log(
294                     LOG_WARNING,
295                     sprintf(
296                         $msg,
297                         $this->notice->id,
298                         $this->user->nickname,
299                         $this->user->id,
300                         $this->flink->foreign_id
301                     ),
302                     __FILE__
303                 );
304             }
305
306         } catch (FacebookApiException $e) {
307             return $this->handleFacebookError($e);
308         }
309
310         return true;
311     }
312
313     /*
314      * Query Facebook to to see if a user has permission
315      *
316      *
317      *
318      * @param $permission the permission to check for - must be either
319      *                    public_stream or status_update
320      *
321      * @return boolean result
322      */
323     function checkPermission($permission)
324     {
325         if (!in_array($permission, array('publish_stream', 'status_update'))) {
326              throw new ServerException("No such permission!");
327         }
328
329         $fbuid = $this->flink->foreign_id;
330
331         common_debug(
332             sprintf(
333                 'Checking for %s permission for user %s (%d), fbuid %d',
334                 $permission,
335                 $this->user->nickname,
336                 $this->user->id,
337                 $fbuid
338             ),
339             __FILE__
340         );
341
342         $hasPermission = $this->facebook->api(
343             array(
344                 'method'   => 'users.hasAppPermission',
345                 'ext_perm' => $permission,
346                 'uid'      => $fbuid
347             )
348         );
349
350         if ($hasPermission == 1) {
351
352             common_debug(
353                 sprintf(
354                     '%s (%d), fbuid %d has %s permission',
355                     $permission,
356                     $this->user->nickname,
357                     $this->user->id,
358                     $fbuid
359                 ),
360                 __FILE__
361             );
362
363             return true;
364
365         } else {
366
367             $logMsg = '%s (%d), fbuid $fbuid does NOT have %s permission.'
368                     . 'Facebook returned: %s';
369
370             common_debug(
371                 sprintf(
372                     $logMsg,
373                     $this->user->nickname,
374                     $this->user->id,
375                     $permission,
376                     $fbuid,
377                     var_export($result, true)
378                 ),
379                 __FILE__
380             );
381
382             return false;
383
384         }
385     }
386
387     /*
388      * Handle a Facebook API Exception
389      *
390      * @param FacebookApiException $e the exception
391      *
392      */
393     function handleFacebookError($e)
394     {
395         $fbuid  = $this->flink->foreign_id;
396         $errmsg = $e->getMessage();
397         $code   = $e->getCode();
398
399         // The Facebook PHP SDK seems to always set the code attribute
400         // of the Exception to 0; they put the real error code in
401         // the message. Gar!
402         if ($code == 0) {
403             preg_match('/^\(#(?<code>\d+)\)/', $errmsg, $matches);
404             $code = $matches['code'];
405         }
406
407         // XXX: Check for any others?
408         switch($code) {
409          case 100: // Invalid parameter
410             $msg = 'Facebook claims notice %d was posted with an invalid '
411                  . 'parameter (error code 100 - %s) Notice details: '
412                  . '[nickname=%s, user id=%d, fbuid=%d, content="%s"]. '
413                  . 'Dequeing.';
414             common_log(
415                 LOG_ERR, sprintf(
416                     $msg,
417                     $this->notice->id,
418                     $errmsg,
419                     $this->user->nickname,
420                     $this->user->id,
421                     $fbuid,
422                     $this->notice->content
423                 ),
424                 __FILE__
425             );
426             return true;
427             break;
428
429          // @fixme: Facebook returns these 2xx permission errors sometimes
430          // FOR NO GOOD REASON AT ALL! It would be better to retry a few times
431          // over an extended period of time to instead of immediately
432          // disconnecting.
433
434          case 200: // Permissions error
435          case 250: // Updating status requires the extended permission status_update
436             $this->disconnect();
437             return true; // dequeue
438             break;
439          case 341: // Feed action request limit reached
440                 $msg = '%s (userid=%d, fbuid=%d) has exceeded his/her limit '
441                      . 'for posting notices to Facebook today. Dequeuing '
442                      . 'notice %d';
443                 common_log(
444                     LOG_INFO, sprintf(
445                         $msg,
446                         $user->nickname,
447                         $user->id,
448                         $fbuid,
449                         $this->notice->id
450                     ),
451                     __FILE__
452                 );
453             // @fixme: We want to rety at a later time when the throttling has expired
454             // instead of just giving up.
455             return true;
456             break;
457          default:
458             $msg = 'Facebook returned an error we don\'t know how to deal with '
459                  . 'when posting notice %d. Error code: %d, error message: "%s"'
460                  . ' Notice details: [nickname=%s, user id=%d, fbuid=%d, '
461                  . 'notice content="%s"]. Dequeing.';
462             common_log(
463                 LOG_ERR, sprintf(
464                     $msg,
465                     $this->notice->id,
466                     $code,
467                     $errmsg,
468                     $this->user->nickname,
469                     $this->user->id,
470                     $fbuid,
471                     $this->notice->content
472                 ),
473                 __FILE__
474             );
475             return true; // dequeue
476             break;
477         }
478     }
479
480     /*
481      * Publish a notice to Facebook as a status update
482      *
483      * This is the least preferable way to send a notice to Facebook because
484      * it doesn't support attachments and the API method doesn't return
485      * the ID of the post on Facebook.
486      *
487      */
488     function restStatusUpdate()
489     {
490         $fbuid = $this->flink->foreign_id;
491
492         common_debug(
493             sprintf(
494                 "Attempting to post notice %d as a status update for %s (%d), fbuid %d",
495                 $this->notice->id,
496                 $this->user->nickname,
497                 $this->user->id,
498                 $fbuid
499             ),
500             __FILE__
501         );
502
503         $result = $this->facebook->api(
504             array(
505                 'method'               => 'users.setStatus',
506                 'status'               => $this->formatMessage(),
507                 'status_includes_verb' => true,
508                 'uid'                  => $fbuid
509             )
510         );
511
512         if ($result == 1) { // 1 is success
513
514             common_log(
515                 LOG_INFO,
516                 sprintf(
517                     "Posted notice %s as a status update for %s (%d), fbuid %d",
518                     $this->notice->id,
519                     $this->user->nickname,
520                     $this->user->id,
521                     $fbuid
522                 ),
523                 __FILE__
524             );
525
526             // There is no item ID returned for status update so we can't
527             // save a Notice_to_item mapping
528
529         } else {
530
531             $msg = sprintf(
532                 "Error posting notice %s as a status update for %s (%d), fbuid %d - error code: %s",
533                 $this->notice->id,
534                 $this->user->nickname,
535                 $this->user->id,
536                 $fbuid,
537                 $result // will contain 0, or an error
538             );
539
540             throw new FacebookApiException($msg, $result);
541         }
542     }
543
544     /*
545      * Publish a notice to a Facebook user's stream using the old REST API
546      */
547     function restPublishStream()
548     {
549         $fbuid = $this->flink->foreign_id;
550
551         common_debug(
552             sprintf(
553                 'Attempting to post notice %d as stream item for %s (%d) fbuid %d',
554                 $this->notice->id,
555                 $this->user->nickname,
556                 $this->user->id,
557                 $fbuid
558             ),
559             __FILE__
560         );
561
562         $fbattachment = $this->formatAttachments();
563
564         $result = $this->facebook->api(
565             array(
566                 'method'     => 'stream.publish',
567                 'message'    => $this->formatMessage(),
568                 'attachment' => $fbattachment,
569                 'uid'        => $fbuid
570             )
571         );
572
573         if (!empty($result)) { // result will contain the item ID
574
575             // Save a mapping
576             Notice_to_item::saveNew($this->notice->id, $result);
577
578             common_log(
579                 LOG_INFO,
580                 sprintf(
581                     'Posted notice %d as a %s for %s (%d), fbuid %d',
582                     $this->notice->id,
583                     empty($fbattachment) ? 'stream item' : 'stream item with attachment',
584                     $this->user->nickname,
585                     $this->user->id,
586                     $fbuid
587                 ),
588                 __FILE__
589             );
590
591         } else {
592
593             $msg = sprintf(
594                 'Could not post notice %d as a %s for %s (%d), fbuid %d - error code: %s',
595                 $this->notice->id,
596                 empty($fbattachment) ? 'stream item' : 'stream item with attachment',
597                 $this->user->nickname,
598                 $this->user->id,
599                 $result, // result will contain an error code
600                 $fbuid
601             );
602
603             throw new FacebookApiException($msg, $result);
604         }
605     }
606
607     /*
608      * Format the text message of a stream item so it's appropriate for
609      * sending to Facebook. If the notice is too long, truncate it, and
610      * add a linkback to the original notice at the end.
611      *
612      * @return String $txt the formated message
613      */
614     function formatMessage()
615     {
616         // Start with the plaintext source of this notice...
617         $txt = $this->notice->content;
618
619         // Facebook has a 420-char hardcoded max.
620         if (mb_strlen($statustxt) > 420) {
621             $noticeUrl = common_shorten_url($this->notice->uri);
622             $urlLen = mb_strlen($noticeUrl);
623             $txt = mb_substr($statustxt, 0, 420 - ($urlLen + 3)) . ' â€¦ ' . $noticeUrl;
624         }
625
626         return $txt;
627     }
628
629     /*
630      * Format attachments for the old REST API stream.publish method
631      *
632      * Note: Old REST API supports multiple attachments per post
633      *
634      */
635     function formatAttachments()
636     {
637         $attachments = $this->notice->attachments();
638
639         $fbattachment          = array();
640         $fbattachment['media'] = array();
641
642         foreach($attachments as $attachment)
643         {
644             if($enclosure = $attachment->getEnclosure()){
645                 $fbmedia = $this->getFacebookMedia($enclosure);
646             }else{
647                 $fbmedia = $this->getFacebookMedia($attachment);
648             }
649             if($fbmedia){
650                 $fbattachment['media'][]=$fbmedia;
651             }else{
652                 $fbattachment['name'] = ($attachment->title ?
653                                       $attachment->title : $attachment->url);
654                 $fbattachment['href'] = $attachment->url;
655             }
656         }
657         if(count($fbattachment['media'])>0){
658             unset($fbattachment['name']);
659             unset($fbattachment['href']);
660         }
661         return $fbattachment;
662     }
663
664     /**
665      * given a File objects, returns an associative array suitable for Facebook media
666      */
667     function getFacebookMedia($attachment)
668     {
669         $fbmedia    = array();
670
671         if (strncmp($attachment->mimetype, 'image/', strlen('image/')) == 0) {
672             $fbmedia['type']         = 'image';
673             $fbmedia['src']          = $attachment->url;
674             $fbmedia['href']         = $attachment->url;
675         } else if ($attachment->mimetype == 'audio/mpeg') {
676             $fbmedia['type']         = 'mp3';
677             $fbmedia['src']          = $attachment->url;
678         }else if ($attachment->mimetype == 'application/x-shockwave-flash') {
679             $fbmedia['type']         = 'flash';
680
681             // http://wiki.developers.facebook.com/index.php/Attachment_%28Streams%29
682             // says that imgsrc is required... but we have no value to put in it
683             // $fbmedia['imgsrc']='';
684
685             $fbmedia['swfsrc']       = $attachment->url;
686         }else{
687             return false;
688         }
689         return $fbmedia;
690     }
691
692     /*
693      * Disconnect a user from Facebook by deleting his Foreign_link.
694      * Notifies the user his account has been disconnected by email.
695      */
696     function disconnect()
697     {
698         $fbuid = $this->flink->foreign_id;
699
700         common_log(
701             LOG_INFO,
702             sprintf(
703                 'Removing Facebook link for %s (%d), fbuid %d',
704                 $this->user->nickname,
705                 $this->user->id,
706                 $fbuid
707             ),
708             __FILE__
709         );
710
711         $result = $this->flink->delete();
712
713         if (empty($result)) {
714             common_log(
715                 LOG_ERR,
716                 sprintf(
717                     'Could not remove Facebook link for %s (%d), fbuid %d',
718                     $this->user->nickname,
719                     $this->user->id,
720                     $fbuid
721                 ),
722                 __FILE__
723             );
724             common_log_db_error($flink, 'DELETE', __FILE__);
725         }
726
727         // Notify the user that we are removing their Facebook link
728         if (!empty($this->user->email)) {
729             $result = $this->mailFacebookDisconnect();
730
731             if (!$result) {
732
733                 $msg = 'Unable to send email to notify %s (%d), fbuid %d '
734                      . 'about his/her Facebook link being removed.';
735
736                 common_log(
737                     LOG_WARNING,
738                     sprintf(
739                         $msg,
740                         $this->user->nickname,
741                         $this->user->id,
742                         $fbuid
743                     ),
744                     __FILE__
745                 );
746             }
747
748         } else {
749
750             $msg = 'Unable to send email to notify %s (%d), fbuid %d '
751                  . 'about his/her Facebook link being removed because the '
752                  . 'user has not set an email address.';
753
754             common_log(
755                 LOG_WARNING,
756                 sprintf(
757                     $msg,
758                     $this->user->nickname,
759                     $this->user->id,
760                     $fbuid
761                 ),
762                 __FILE__
763             );
764         }
765     }
766
767     /**
768      * Send a mail message to notify a user that her Facebook link
769      * has been terminated.
770      *
771      * @return boolean success flag
772      */
773     function mailFacebookDisconnect()
774     {
775         $profile = $this->user->getProfile();
776
777         $siteName = common_config('site', 'name');
778
779         common_switch_locale($this->user->language);
780
781         $subject = _m('Your Facebook connection has been removed');
782
783         $msg = <<<BODY
784 Hi %1$s,
785
786 We're sorry to inform you we are unable to publish your notice to
787 Facebook, and have removed the connection between your %2$s account and
788 Facebook.
789
790 This may have happened because you have removed permission for %2$s
791 to post on your behalf, or perhaps you have deactivated your Facebook
792 account. You can reconnect your %s account to Facebook at any time by
793 logging in with Facebook again.
794
795 Sincerely,
796
797 %2$s
798 BODY;
799         $body = sprintf(
800             _m($msg),
801             $this->user->nickname,
802             $siteName
803         );
804
805         common_switch_locale();
806
807         $result = mail_to_user($this->user, $subject, $body);
808
809         if (empty($this->user->password)) {
810             $result = self::emailWarn($this->user);
811         }
812
813         return $result;
814     }
815
816     /*
817      * Send the user an email warning that their account has been
818      * disconnected and he/she has no way to login and must contact
819      * the site administrator for help.
820      *
821      * @param User $user the deauthorizing user
822      *
823      */
824     static function emailWarn($user)
825     {
826         $profile = $user->getProfile();
827
828         $siteName  = common_config('site', 'name');
829         $siteEmail = common_config('site', 'email');
830
831         if (empty($siteEmail)) {
832             common_log(
833                 LOG_WARNING,
834                     "No site email address configured. Please set one."
835             );
836         }
837
838         common_switch_locale($user->language);
839
840         $subject = _m('Contact the %s administrator to retrieve your account');
841
842         $msg = <<<BODY
843 Hi %1$s,
844
845 We've noticed you have deauthorized the Facebook connection for your
846 %2$s account.  You have not set a password for your %2$s account yet, so
847 you will not be able to login. If you wish to continue using your %2$s
848 account, please contact the site administrator (%3$s) to set a password.
849
850 Sincerely,
851
852 %2$s
853 BODY;
854         $body = sprintf(
855             _m($msg),
856             $user->nickname,
857             $siteName,
858             $siteEmail
859         );
860
861         common_switch_locale();
862
863         if (mail_to_user($user, $subject, $body)) {
864             common_log(
865                 LOG_INFO,
866                 sprintf(
867                     'Sent account lockout warning to %s (%d)',
868                     $user->nickname,
869                     $user->id
870                 ),
871                 __FILE__
872             );
873         } else {
874             common_log(
875                 LOG_WARNING,
876                 sprintf(
877                     'Unable to send account lockout warning to %s (%d)',
878                     $user->nickname,
879                     $user->id
880                 ),
881                 __FILE__
882             );
883         }
884     }
885
886     /*
887      * Check to see if we have a mapping to a copy of this notice
888      * on Facebook
889      *
890      * @param Notice $notice the notice to check
891      *
892      * @return mixed null if it can't find one, or the id of the Facebook
893      *               stream item
894      */
895     static function facebookStatusId($notice)
896     {
897         $n2i = Notice_to_item::staticGet('notice_id', $notice->id);
898
899         if (empty($n2i)) {
900             return null;
901         } else {
902             return $n2i->item_id;
903         }
904     }
905
906     /*
907      * Save a Foreign_user record of a Facebook user
908      *
909      * @param object $fbuser a Facebook Graph API user obj
910      *                       See: http://developers.facebook.com/docs/reference/api/user
911      * @return mixed $result Id or key
912      *
913      */
914     static function addFacebookUser($fbuser)
915     {
916         // remove any existing, possibly outdated, record
917         $luser = Foreign_user::getForeignUser($fbuser['id'], FACEBOOK_SERVICE);
918
919         if (!empty($luser)) {
920
921             $result = $luser->delete();
922
923             if ($result != false) {
924                 common_log(
925                     LOG_INFO,
926                     sprintf(
927                         'Removed old Facebook user: %s, fbuid %d',
928                         $fbuid['name'],
929                         $fbuid['id']
930                     ),
931                     __FILE__
932                 );
933             }
934         }
935
936         $fuser = new Foreign_user();
937
938         $fuser->nickname = $fbuser['name'];
939         $fuser->uri      = $fbuser['link'];
940         $fuser->id       = $fbuser['id'];
941         $fuser->service  = FACEBOOK_SERVICE;
942         $fuser->created  = common_sql_now();
943
944         $result = $fuser->insert();
945
946         if (empty($result)) {
947             common_log(
948                 LOG_WARNING,
949                     sprintf(
950                         'Failed to add new Facebook user: %s, fbuid %d',
951                         $fbuser['name'],
952                         $fbuser['id']
953                     ),
954                     __FILE__
955             );
956
957             common_log_db_error($fuser, 'INSERT', __FILE__);
958         } else {
959             common_log(
960                 LOG_INFO,
961                 sprintf(
962                     'Added new Facebook user: %s, fbuid %d',
963                     $fbuser['name'],
964                     $fbuser['id']
965                 ),
966                 __FILE__
967             );
968         }
969
970         return $result;
971     }
972
973     /*
974      * Remove an item from a Facebook user's feed if we have a mapping
975      * for it.
976      */
977     function streamRemove()
978     {
979         $n2i = Notice_to_item::staticGet('notice_id', $this->notice->id);
980
981         if (!empty($this->flink) && !empty($n2i)) {
982
983             $result = $this->facebook->api(
984                 array(
985                     'method'  => 'stream.remove',
986                     'post_id' => $n2i->item_id,
987                     'uid'     => $this->flink->foreign_id
988                 )
989             );
990
991             if (!empty($result) && result == true) {
992
993                 common_log(
994                   LOG_INFO,
995                     sprintf(
996                         'Deleted Facebook item: %s for %s (%d), fbuid %d',
997                         $n2i->item_id,
998                         $this->user->nickname,
999                         $this->user->id,
1000                         $this->flink->foreign_id
1001                     ),
1002                     __FILE__
1003                 );
1004
1005                 $n2i->delete();
1006
1007             } else {
1008
1009                 common_log(
1010                   LOG_WARNING,
1011                     sprintf(
1012                         'Could not deleted Facebook item: %s for %s (%d), fbuid %d',
1013                         $n2i->item_id,
1014                         $this->user->nickname,
1015                         $this->user->id,
1016                         $this->flink->foreign_id
1017                     ),
1018                     __FILE__
1019                 );
1020             }
1021         }
1022     }
1023
1024     /*
1025      * Like an item in a Facebook user's feed if we have a mapping
1026      * for it.
1027      */
1028     function like()
1029     {
1030         $n2i = Notice_to_item::staticGet('notice_id', $this->notice->id);
1031
1032         if (!empty($this->flink) && !empty($n2i)) {
1033
1034             $result = $this->facebook->api(
1035                 array(
1036                     'method'  => 'stream.addlike',
1037                     'post_id' => $n2i->item_id,
1038                     'uid'     => $this->flink->foreign_id
1039                 )
1040             );
1041
1042             if (!empty($result) && result == true) {
1043
1044                 common_log(
1045                   LOG_INFO,
1046                     sprintf(
1047                         'Added like for item: %s for %s (%d), fbuid %d',
1048                         $n2i->item_id,
1049                         $this->user->nickname,
1050                         $this->user->id,
1051                         $this->flink->foreign_id
1052                     ),
1053                     __FILE__
1054                 );
1055
1056             } else {
1057
1058                 common_log(
1059                   LOG_WARNING,
1060                     sprintf(
1061                         'Could not like Facebook item: %s for %s (%d), fbuid %d',
1062                         $n2i->item_id,
1063                         $this->user->nickname,
1064                         $this->user->id,
1065                         $this->flink->foreign_id
1066                     ),
1067                     __FILE__
1068                 );
1069             }
1070         }
1071     }
1072
1073     /*
1074      * Unlike an item in a Facebook user's feed if we have a mapping
1075      * for it.
1076      */
1077     function unLike()
1078     {
1079         $n2i = Notice_to_item::staticGet('notice_id', $this->notice->id);
1080
1081         if (!empty($this->flink) && !empty($n2i)) {
1082
1083             $result = $this->facebook->api(
1084                 array(
1085                     'method'  => 'stream.removeLike',
1086                     'post_id' => $n2i->item_id,
1087                     'uid'     => $this->flink->foreign_id
1088                 )
1089             );
1090
1091             if (!empty($result) && result == true) {
1092
1093                 common_log(
1094                   LOG_INFO,
1095                     sprintf(
1096                         'Removed like for item: %s for %s (%d), fbuid %d',
1097                         $n2i->item_id,
1098                         $this->user->nickname,
1099                         $this->user->id,
1100                         $this->flink->foreign_id
1101                     ),
1102                     __FILE__
1103                 );
1104
1105             } else {
1106
1107                 common_log(
1108                   LOG_WARNING,
1109                     sprintf(
1110                         'Could not remove like for Facebook item: %s for %s (%d), fbuid %d',
1111                         $n2i->item_id,
1112                         $this->user->nickname,
1113                         $this->user->id,
1114                         $this->flink->foreign_id
1115                     ),
1116                     __FILE__
1117                 );
1118             }
1119         }
1120     }
1121
1122 }