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