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