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