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