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