ccfdeb1fbf9cf04d458b167e9ae2e890c2f51a98
[friendica-addons.git] / twitter / twitter.php
1 <?php
2
3 /**
4  * Name: Twitter Connector
5  * Description: Bidirectional (posting, relaying and reading) connector for Twitter.
6  * Version: 1.0.4
7  * Author: Tobias Diekershoff <https://f.diekershoff.de/profile/tobias>
8  * Author: Michael Vogel <https://pirati.ca/profile/heluecht>
9  *
10  * Copyright (c) 2011-2013 Tobias Diekershoff, Michael Vogel
11  * All rights reserved.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions are met:
15  *    * Redistributions of source code must retain the above copyright notice,
16  *     this list of conditions and the following disclaimer.
17  *    * Redistributions in binary form must reproduce the above
18  *    * copyright notice, this list of conditions and the following disclaimer in
19  *      the documentation and/or other materials provided with the distribution.
20  *    * Neither the name of the <organization> nor the names of its contributors
21  *      may be used to endorse or promote products derived from this software
22  *      without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27  * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT,
28  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
32  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
33  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34  *
35  */
36 /*   Twitter Plugin for Friendica
37  *
38  *   Author: Tobias Diekershoff
39  *           tobias.diekershoff@gmx.net
40  *
41  *   License:3-clause BSD license
42  *
43  *   Configuration:
44  *     To use this plugin you need a OAuth Consumer key pair (key & secret)
45  *     you can get it from Twitter at https://twitter.com/apps
46  *
47  *     Register your Friendica site as "Client" application with "Read & Write" access
48  *     we do not need "Twitter as login". When you've registered the app you get the
49  *     OAuth Consumer key and secret pair for your application/site.
50  *
51  *     Add this key pair to your global .htconfig.php or use the admin panel.
52  *
53  *     $a->config['twitter']['consumerkey'] = 'your consumer_key here';
54  *     $a->config['twitter']['consumersecret'] = 'your consumer_secret here';
55  *
56  *     To activate the plugin itself add it to the $a->config['system']['addon']
57  *     setting. After this, your user can configure their Twitter account settings
58  *     from "Settings -> Plugin Settings".
59  *
60  *     Requirements: PHP5, curl [Slinky library]
61  */
62
63 use Friendica\App;
64 use Friendica\Content\OEmbed;
65 use Friendica\Core\Config;
66 use Friendica\Core\PConfig;
67 use Friendica\Core\Worker;
68 use Friendica\Model\GContact;
69 use Friendica\Model\Group;
70 use Friendica\Model\Photo;
71 use Friendica\Model\User;
72 use Friendica\Object\Image;
73
74 require_once 'include/enotify.php';
75
76 define('TWITTER_DEFAULT_POLL_INTERVAL', 5); // given in minutes
77
78 function twitter_install()
79 {
80         //  we need some hooks, for the configuration and for sending tweets
81         register_hook('connector_settings', 'addon/twitter/twitter.php', 'twitter_settings');
82         register_hook('connector_settings_post', 'addon/twitter/twitter.php', 'twitter_settings_post');
83         register_hook('post_local', 'addon/twitter/twitter.php', 'twitter_post_local');
84         register_hook('notifier_normal', 'addon/twitter/twitter.php', 'twitter_post_hook');
85         register_hook('jot_networks', 'addon/twitter/twitter.php', 'twitter_jot_nets');
86         register_hook('cron', 'addon/twitter/twitter.php', 'twitter_cron');
87         register_hook('queue_predeliver', 'addon/twitter/twitter.php', 'twitter_queue_hook');
88         register_hook('follow', 'addon/twitter/twitter.php', 'twitter_follow');
89         register_hook('expire', 'addon/twitter/twitter.php', 'twitter_expire');
90         register_hook('prepare_body', 'addon/twitter/twitter.php', 'twitter_prepare_body');
91         register_hook('check_item_notification', 'addon/twitter/twitter.php', 'twitter_check_item_notification');
92         logger("installed twitter");
93 }
94
95 function twitter_uninstall()
96 {
97         unregister_hook('connector_settings', 'addon/twitter/twitter.php', 'twitter_settings');
98         unregister_hook('connector_settings_post', 'addon/twitter/twitter.php', 'twitter_settings_post');
99         unregister_hook('post_local', 'addon/twitter/twitter.php', 'twitter_post_local');
100         unregister_hook('notifier_normal', 'addon/twitter/twitter.php', 'twitter_post_hook');
101         unregister_hook('jot_networks', 'addon/twitter/twitter.php', 'twitter_jot_nets');
102         unregister_hook('cron', 'addon/twitter/twitter.php', 'twitter_cron');
103         unregister_hook('queue_predeliver', 'addon/twitter/twitter.php', 'twitter_queue_hook');
104         unregister_hook('follow', 'addon/twitter/twitter.php', 'twitter_follow');
105         unregister_hook('expire', 'addon/twitter/twitter.php', 'twitter_expire');
106         unregister_hook('prepare_body', 'addon/twitter/twitter.php', 'twitter_prepare_body');
107         unregister_hook('check_item_notification', 'addon/twitter/twitter.php', 'twitter_check_item_notification');
108
109         // old setting - remove only
110         unregister_hook('post_local_end', 'addon/twitter/twitter.php', 'twitter_post_hook');
111         unregister_hook('plugin_settings', 'addon/twitter/twitter.php', 'twitter_settings');
112         unregister_hook('plugin_settings_post', 'addon/twitter/twitter.php', 'twitter_settings_post');
113 }
114
115 function twitter_check_item_notification(App $a, &$notification_data)
116 {
117         $own_id = PConfig::get($notification_data["uid"], 'twitter', 'own_id');
118
119         $own_user = q("SELECT `url` FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
120                         intval($notification_data["uid"]),
121                         dbesc("twitter::".$own_id)
122         );
123
124         if ($own_user) {
125                 $notification_data["profiles"][] = $own_user[0]["url"];
126         }
127 }
128
129 function twitter_follow(App $a, &$contact)
130 {
131         logger("twitter_follow: Check if contact is twitter contact. " . $contact["url"], LOGGER_DEBUG);
132
133         if (!strstr($contact["url"], "://twitter.com") && !strstr($contact["url"], "@twitter.com")) {
134                 return;
135         }
136
137         // contact seems to be a twitter contact, so continue
138         $nickname = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $contact["url"]);
139         $nickname = str_replace("@twitter.com", "", $nickname);
140
141         $uid = $a->user["uid"];
142
143         $ckey    = Config::get('twitter', 'consumerkey');
144         $csecret = Config::get('twitter', 'consumersecret');
145         $otoken  = PConfig::get($uid, 'twitter', 'oauthtoken');
146         $osecret = PConfig::get($uid, 'twitter', 'oauthsecret');
147
148         require_once "addon/twitter/codebird.php";
149
150         $cb = \Codebird\Codebird::getInstance();
151         $cb->setConsumerKey($ckey, $csecret);
152         $cb->setToken($otoken, $osecret);
153
154         $parameters = array();
155         $parameters["screen_name"] = $nickname;
156
157         $user = $cb->friendships_create($parameters);
158
159         twitter_fetchuser($a, $uid, $nickname);
160
161         $r = q("SELECT name,nick,url,addr,batch,notify,poll,request,confirm,poco,photo,priority,network,alias,pubkey
162                 FROM `contact` WHERE `uid` = %d AND `nick` = '%s'",
163                                 intval($uid),
164                                 dbesc($nickname));
165         if (count($r)) {
166                 $contact["contact"] = $r[0];
167         }
168 }
169
170 function twitter_jot_nets(App $a, &$b)
171 {
172         if (!local_user()) {
173                 return;
174         }
175
176         $tw_post = PConfig::get(local_user(), 'twitter', 'post');
177         if (intval($tw_post) == 1) {
178                 $tw_defpost = PConfig::get(local_user(), 'twitter', 'post_by_default');
179                 $selected = ((intval($tw_defpost) == 1) ? ' checked="checked" ' : '');
180                 $b .= '<div class="profile-jot-net"><input type="checkbox" name="twitter_enable"' . $selected . ' value="1" /> '
181                         . t('Post to Twitter') . '</div>';
182         }
183 }
184
185 function twitter_settings_post(App $a, $post)
186 {
187         if (!local_user()) {
188                 return;
189         }
190         // don't check twitter settings if twitter submit button is not clicked
191         if (!x($_POST, 'twitter-submit')) {
192                 return;
193         }
194
195         if (isset($_POST['twitter-disconnect'])) {
196                 /*               * *
197                  * if the twitter-disconnect checkbox is set, clear the OAuth key/secret pair
198                  * from the user configuration
199                  */
200                 PConfig::delete(local_user(), 'twitter', 'consumerkey');
201                 PConfig::delete(local_user(), 'twitter', 'consumersecret');
202                 PConfig::delete(local_user(), 'twitter', 'oauthtoken');
203                 PConfig::delete(local_user(), 'twitter', 'oauthsecret');
204                 PConfig::delete(local_user(), 'twitter', 'post');
205                 PConfig::delete(local_user(), 'twitter', 'post_by_default');
206                 PConfig::delete(local_user(), 'twitter', 'lastid');
207                 PConfig::delete(local_user(), 'twitter', 'mirror_posts');
208                 PConfig::delete(local_user(), 'twitter', 'import');
209                 PConfig::delete(local_user(), 'twitter', 'create_user');
210                 PConfig::delete(local_user(), 'twitter', 'own_id');
211         } else {
212                 if (isset($_POST['twitter-pin'])) {
213                         //  if the user supplied us with a PIN from Twitter, let the magic of OAuth happen
214                         logger('got a Twitter PIN');
215                         require_once 'library/twitteroauth.php';
216                         $ckey    = Config::get('twitter', 'consumerkey');
217                         $csecret = Config::get('twitter', 'consumersecret');
218                         //  the token and secret for which the PIN was generated were hidden in the settings
219                         //  form as token and token2, we need a new connection to Twitter using these token
220                         //  and secret to request a Access Token with the PIN
221                         $connection = new TwitterOAuth($ckey, $csecret, $_POST['twitter-token'], $_POST['twitter-token2']);
222                         $token = $connection->getAccessToken($_POST['twitter-pin']);
223                         //  ok, now that we have the Access Token, save them in the user config
224                         PConfig::set(local_user(), 'twitter', 'oauthtoken', $token['oauth_token']);
225                         PConfig::set(local_user(), 'twitter', 'oauthsecret', $token['oauth_token_secret']);
226                         PConfig::set(local_user(), 'twitter', 'post', 1);
227                         //  reload the Addon Settings page, if we don't do it see Bug #42
228                         goaway('settings/connectors');
229                 } else {
230                         //  if no PIN is supplied in the POST variables, the user has changed the setting
231                         //  to post a tweet for every new __public__ posting to the wall
232                         PConfig::set(local_user(), 'twitter', 'post', intval($_POST['twitter-enable']));
233                         PConfig::set(local_user(), 'twitter', 'post_by_default', intval($_POST['twitter-default']));
234                         PConfig::set(local_user(), 'twitter', 'mirror_posts', intval($_POST['twitter-mirror']));
235                         PConfig::set(local_user(), 'twitter', 'import', intval($_POST['twitter-import']));
236                         PConfig::set(local_user(), 'twitter', 'create_user', intval($_POST['twitter-create_user']));
237
238                         if (!intval($_POST['twitter-mirror'])) {
239                                 PConfig::delete(local_user(), 'twitter', 'lastid');
240                         }
241
242                         info(t('Twitter settings updated.') . EOL);
243                 }
244         }
245 }
246
247 function twitter_settings(App $a, &$s)
248 {
249         if (!local_user()) {
250                 return;
251         }
252         $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/twitter/twitter.css' . '" media="all" />' . "\r\n";
253         /*       * *
254          * 1) Check that we have global consumer key & secret
255          * 2) If no OAuthtoken & stuff is present, generate button to get some
256          * 3) Checkbox for "Send public notices (280 chars only)
257          */
258         $ckey    = Config::get('twitter', 'consumerkey' );
259         $csecret = Config::get('twitter', 'consumersecret' );
260         $otoken  = PConfig::get(local_user(), 'twitter', 'oauthtoken'  );
261         $osecret = PConfig::get(local_user(), 'twitter', 'oauthsecret' );
262
263         $enabled            = intval(PConfig::get(local_user(), 'twitter', 'post'));
264         $defenabled         = intval(PConfig::get(local_user(), 'twitter', 'post_by_default'));
265         $mirrorenabled      = intval(PConfig::get(local_user(), 'twitter', 'mirror_posts'));
266         $importenabled      = intval(PConfig::get(local_user(), 'twitter', 'import'));
267         $create_userenabled = intval(PConfig::get(local_user(), 'twitter', 'create_user'));
268
269         $css = (($enabled) ? '' : '-disabled');
270
271         $s .= '<span id="settings_twitter_inflated" class="settings-block fakelink" style="display: block;" onclick="openClose(\'settings_twitter_expanded\'); openClose(\'settings_twitter_inflated\');">';
272         $s .= '<img class="connector' . $css . '" src="images/twitter.png" /><h3 class="connector">' . t('Twitter Import/Export/Mirror') . '</h3>';
273         $s .= '</span>';
274         $s .= '<div id="settings_twitter_expanded" class="settings-block" style="display: none;">';
275         $s .= '<span class="fakelink" onclick="openClose(\'settings_twitter_expanded\'); openClose(\'settings_twitter_inflated\');">';
276         $s .= '<img class="connector' . $css . '" src="images/twitter.png" /><h3 class="connector">' . t('Twitter Import/Export/Mirror') . '</h3>';
277         $s .= '</span>';
278
279         if ((!$ckey) && (!$csecret)) {
280                 /*               * *
281                  * no global consumer keys
282                  * display warning and skip personal config
283                  */
284                 $s .= '<p>' . t('No consumer key pair for Twitter found. Please contact your site administrator.') . '</p>';
285         } else {
286                 /*               * *
287                  * ok we have a consumer key pair now look into the OAuth stuff
288                  */
289                 if ((!$otoken) && (!$osecret)) {
290                         /*                       * *
291                          * the user has not yet connected the account to twitter...
292                          * get a temporary OAuth key/secret pair and display a button with
293                          * which the user can request a PIN to connect the account to a
294                          * account at Twitter.
295                          */
296                         require_once 'library/twitteroauth.php';
297                         $connection = new TwitterOAuth($ckey, $csecret);
298                         $request_token = $connection->getRequestToken();
299                         $token = $request_token['oauth_token'];
300                         /*                       * *
301                          *  make some nice form
302                          */
303                         $s .= '<p>' . t('At this Friendica instance the Twitter plugin was enabled but you have not yet connected your account to your Twitter account. To do so click the button below to get a PIN from Twitter which you have to copy into the input box below and submit the form. Only your <strong>public</strong> posts will be posted to Twitter.') . '</p>';
304                         $s .= '<a href="' . $connection->getAuthorizeURL($token) . '" target="_twitter"><img src="addon/twitter/lighter.png" alt="' . t('Log in with Twitter') . '"></a>';
305                         $s .= '<div id="twitter-pin-wrapper">';
306                         $s .= '<label id="twitter-pin-label" for="twitter-pin">' . t('Copy the PIN from Twitter here') . '</label>';
307                         $s .= '<input id="twitter-pin" type="text" name="twitter-pin" />';
308                         $s .= '<input id="twitter-token" type="hidden" name="twitter-token" value="' . $token . '" />';
309                         $s .= '<input id="twitter-token2" type="hidden" name="twitter-token2" value="' . $request_token['oauth_token_secret'] . '" />';
310                         $s .= '</div><div class="clear"></div>';
311                         $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="twitter-submit" class="settings-submit" value="' . t('Save Settings') . '" /></div>';
312                 } else {
313                         /*                       * *
314                          *  we have an OAuth key / secret pair for the user
315                          *  so let's give a chance to disable the postings to Twitter
316                          */
317                         require_once 'library/twitteroauth.php';
318                         $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
319                         $details = $connection->get('account/verify_credentials');
320
321                         $field_checkbox = get_markup_template('field_checkbox.tpl');
322
323                         $s .= '<div id="twitter-info" >
324                                 <p>' . t('Currently connected to: ') . '<a href="https://twitter.com/' . $details->screen_name . '" target="_twitter">' . $details->screen_name . '</a>
325                                         <button type="submit" name="twitter-disconnect" value="1">' . t('Disconnect') . '</button>
326                                 </p>
327                                 <p id="twitter-info-block">
328                                         <a href="https://twitter.com/' . $details->screen_name . '" target="_twitter"><img id="twitter-avatar" src="' . $details->profile_image_url . '" /></a>
329                                         <em>' . $details->description . '</em>
330                                 </p>
331                         </div>';
332                         $s .= '<div class="clear"></div>';
333
334                         $s .= replace_macros($field_checkbox, array(
335                                 '$field' => array('twitter-enable', t('Allow posting to Twitter'), $enabled, t('If enabled all your <strong>public</strong> postings can be posted to the associated Twitter account. You can choose to do so by default (here) or for every posting separately in the posting options when writing the entry.'))
336                         ));
337                         if ($a->user['hidewall']) {
338                                 $s .= '<p>' . t('<strong>Note</strong>: Due to your privacy settings (<em>Hide your profile details from unknown viewers?</em>) the link potentially included in public postings relayed to Twitter will lead the visitor to a blank page informing the visitor that the access to your profile has been restricted.') . '</p>';
339                         }
340                         $s .= replace_macros($field_checkbox, array(
341                                 '$field' => array('twitter-default', t('Send public postings to Twitter by default'), $defenabled, '')
342                         ));
343                         $s .= replace_macros($field_checkbox, array(
344                                 '$field' => array('twitter-mirror', t('Mirror all posts from twitter that are no replies'), $mirrorenabled, '')
345                         ));
346                         $s .= replace_macros($field_checkbox, array(
347                                 '$field' => array('twitter-import', t('Import the remote timeline'), $importenabled, '')
348                         ));
349                         $s .= replace_macros($field_checkbox, array(
350                                 '$field' => array('twitter-create_user', t('Automatically create contacts'), $create_userenabled, '')
351                         ));
352
353                         $s .= '<div class="clear"></div>';
354                         $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="twitter-submit" class="settings-submit" value="' . t('Save Settings') . '" /></div>';
355                 }
356         }
357         $s .= '</div><div class="clear"></div>';
358 }
359
360 function twitter_post_local(App $a, &$b)
361 {
362         if ($b['edit']) {
363                 return;
364         }
365
366         if (!local_user() || (local_user() != $b['uid'])) {
367                 return;
368         }
369
370         $twitter_post = intval(PConfig::get(local_user(), 'twitter', 'post'));
371         $twitter_enable = (($twitter_post && x($_REQUEST, 'twitter_enable')) ? intval($_REQUEST['twitter_enable']) : 0);
372
373         // if API is used, default to the chosen settings
374         if ($b['api_source'] && intval(PConfig::get(local_user(), 'twitter', 'post_by_default'))) {
375                 $twitter_enable = 1;
376         }
377
378         if (!$twitter_enable) {
379                 return;
380         }
381
382         if (strlen($b['postopts'])) {
383                 $b['postopts'] .= ',';
384         }
385
386         $b['postopts'] .= 'twitter';
387 }
388
389 function twitter_action(App $a, $uid, $pid, $action)
390 {
391         $ckey = Config::get('twitter', 'consumerkey');
392         $csecret = Config::get('twitter', 'consumersecret');
393         $otoken = PConfig::get($uid, 'twitter', 'oauthtoken');
394         $osecret = PConfig::get($uid, 'twitter', 'oauthsecret');
395
396         require_once "addon/twitter/codebird.php";
397
398         $cb = \Codebird\Codebird::getInstance();
399         $cb->setConsumerKey($ckey, $csecret);
400         $cb->setToken($otoken, $osecret);
401
402         $post = array('id' => $pid);
403
404         logger("twitter_action '" . $action . "' ID: " . $pid . " data: " . print_r($post, true), LOGGER_DATA);
405
406         switch ($action) {
407                 case "delete":
408                         // To-Do: $result = $cb->statuses_destroy($post);
409                         break;
410                 case "like":
411                         $result = $cb->favorites_create($post);
412                         break;
413                 case "unlike":
414                         $result = $cb->favorites_destroy($post);
415                         break;
416         }
417         logger("twitter_action '" . $action . "' send, result: " . print_r($result, true), LOGGER_DEBUG);
418 }
419
420 function twitter_post_hook(App $a, &$b)
421 {
422         // Post to Twitter
423         require_once "include/network.php";
424
425         if (!PConfig::get($b["uid"], 'twitter', 'import')
426                 && ($b['deleted'] || $b['private'] || ($b['created'] !== $b['edited']))) {
427                 return;
428         }
429
430         if ($b['parent'] != $b['id']) {
431                 logger("twitter_post_hook: parameter " . print_r($b, true), LOGGER_DATA);
432
433                 // Looking if its a reply to a twitter post
434                 if ((substr($b["parent-uri"], 0, 9) != "twitter::") && (substr($b["extid"], 0, 9) != "twitter::") && (substr($b["thr-parent"], 0, 9) != "twitter::")) {
435                         logger("twitter_post_hook: no twitter post " . $b["parent"]);
436                         return;
437                 }
438
439                 $r = q("SELECT * FROM item WHERE item.uri = '%s' AND item.uid = %d LIMIT 1",
440                         dbesc($b["thr-parent"]),
441                         intval($b["uid"]));
442
443                 if (!count($r)) {
444                         logger("twitter_post_hook: no parent found " . $b["thr-parent"]);
445                         return;
446                 } else {
447                         $iscomment = true;
448                         $orig_post = $r[0];
449                 }
450
451
452                 $nicknameplain = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $orig_post["author-link"]);
453                 $nickname = "@[url=" . $orig_post["author-link"] . "]" . $nicknameplain . "[/url]";
454                 $nicknameplain = "@" . $nicknameplain;
455
456                 logger("twitter_post_hook: comparing " . $nickname . " and " . $nicknameplain . " with " . $b["body"], LOGGER_DEBUG);
457                 if ((strpos($b["body"], $nickname) === false) && (strpos($b["body"], $nicknameplain) === false)) {
458                         $b["body"] = $nickname . " " . $b["body"];
459                 }
460
461                 logger("twitter_post_hook: parent found " . print_r($orig_post, true), LOGGER_DATA);
462         } else {
463                 $iscomment = false;
464
465                 if ($b['private'] || !strstr($b['postopts'], 'twitter')) {
466                         return;
467                 }
468
469                 // Dont't post if the post doesn't belong to us.
470                 // This is a check for forum postings
471                 $self = dba::selectFirst('contact', ['id'], ['uid' => $b['uid'], 'self' => true]);
472                 if ($b['contact-id'] != $self['id']) {
473                         return;
474                 }
475         }
476
477         if (($b['verb'] == ACTIVITY_POST) && $b['deleted']) {
478                 twitter_action($a, $b["uid"], substr($orig_post["uri"], 9), "delete");
479         }
480
481         if ($b['verb'] == ACTIVITY_LIKE) {
482                 logger("twitter_post_hook: parameter 2 " . substr($b["thr-parent"], 9), LOGGER_DEBUG);
483                 if ($b['deleted'])
484                         twitter_action($a, $b["uid"], substr($b["thr-parent"], 9), "unlike");
485                 else
486                         twitter_action($a, $b["uid"], substr($b["thr-parent"], 9), "like");
487                 return;
488         }
489
490         if ($b['deleted'] || ($b['created'] !== $b['edited'])) {
491                 return;
492         }
493
494         // if post comes from twitter don't send it back
495         if ($b['extid'] == NETWORK_TWITTER) {
496                 return;
497         }
498
499         if ($b['app'] == "Twitter") {
500                 return;
501         }
502
503         logger('twitter post invoked');
504
505         PConfig::load($b['uid'], 'twitter');
506
507         $ckey    = Config::get('twitter', 'consumerkey');
508         $csecret = Config::get('twitter', 'consumersecret');
509         $otoken  = PConfig::get($b['uid'], 'twitter', 'oauthtoken');
510         $osecret = PConfig::get($b['uid'], 'twitter', 'oauthsecret');
511
512         if ($ckey && $csecret && $otoken && $osecret) {
513                 logger('twitter: we have customer key and oauth stuff, going to send.', LOGGER_DEBUG);
514
515                 // If it's a repeated message from twitter then do a native retweet and exit
516                 if (twitter_is_retweet($a, $b['uid'], $b['body'])) {
517                         return;
518                 }
519
520                 require_once 'library/twitteroauth.php';
521                 require_once 'include/bbcode.php';
522                 $tweet = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
523
524                 $max_char = 280;
525                 require_once "include/plaintext.php";
526                 $msgarr = plaintext($b, $max_char, true, 8);
527                 $msg = $msgarr["text"];
528
529                 if (($msg == "") && isset($msgarr["title"])) {
530                         $msg = shortenmsg($msgarr["title"], $max_char - 50);
531                 }
532
533                 $image = "";
534
535                 if (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
536                         $msg .= "\n" . $msgarr["url"];
537                 }
538
539                 if (isset($msgarr["image"]) && ($msgarr["type"] != "video")) {
540                         $image = $msgarr["image"];
541                 }
542
543                 // and now tweet it :-)
544                 if (strlen($msg) && ($image != "")) {
545                         $img_str = fetch_url($image);
546
547                         $tempfile = tempnam(get_temppath(), "cache");
548                         file_put_contents($tempfile, $img_str);
549
550                         // Twitter had changed something so that the old library doesn't work anymore
551                         // so we are using a new library for twitter
552                         // To-Do:
553                         // Switching completely to this library with all functions
554                         require_once "addon/twitter/codebird.php";
555
556                         $cb = \Codebird\Codebird::getInstance();
557                         $cb->setConsumerKey($ckey, $csecret);
558                         $cb->setToken($otoken, $osecret);
559
560                         $post = array('status' => $msg, 'media[]' => $tempfile);
561
562                         if ($iscomment) {
563                                 $post["in_reply_to_status_id"] = substr($orig_post["uri"], 9);
564                         }
565
566                         $result = $cb->statuses_updateWithMedia($post);
567                         unlink($tempfile);
568
569                         logger('twitter_post_with_media send, result: ' . print_r($result, true), LOGGER_DEBUG);
570
571                         if ($result->source) {
572                                 Config::set("twitter", "application_name", strip_tags($result->source));
573                         }
574
575                         if ($result->errors || $result->error) {
576                                 logger('Send to Twitter failed: "' . print_r($result->errors, true) . '"');
577
578                                 // Workaround: Remove the picture link so that the post can be reposted without it
579                                 $msg .= " " . $image;
580                                 $image = "";
581                         } elseif ($iscomment) {
582                                 logger('twitter_post: Update extid ' . $result->id_str . " for post id " . $b['id']);
583                                 q("UPDATE `item` SET `extid` = '%s', `body` = '%s' WHERE `id` = %d",
584                                         dbesc("twitter::" . $result->id_str),
585                                         dbesc($result->text),
586                                         intval($b['id'])
587                                 );
588                         }
589                 }
590
591                 if (strlen($msg) && ($image == "")) {
592 // -----------------
593                         $max_char = 280;
594                         require_once "include/plaintext.php";
595                         $msgarr = plaintext($b, $max_char, true, 8);
596                         $msg = $msgarr["text"];
597
598                         if (($msg == "") && isset($msgarr["title"])) {
599                                 $msg = shortenmsg($msgarr["title"], $max_char - 50);
600                         }
601
602                         if (isset($msgarr["url"])) {
603                                 $msg .= "\n" . $msgarr["url"];
604                         }
605 // -----------------
606                         $url = 'statuses/update';
607                         $post = array('status' => $msg, 'weighted_character_count' => 'true');
608
609                         if ($iscomment) {
610                                 $post["in_reply_to_status_id"] = substr($orig_post["uri"], 9);
611                         }
612
613                         $result = $tweet->post($url, $post);
614                         logger('twitter_post send, result: ' . print_r($result, true), LOGGER_DEBUG);
615
616                         if ($result->source) {
617                                 Config::set("twitter", "application_name", strip_tags($result->source));
618                         }
619
620                         if ($result->errors) {
621                                 logger('Send to Twitter failed: "' . print_r($result->errors, true) . '"');
622
623                                 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `self`", intval($b['uid']));
624                                 if (count($r)) {
625                                         $a->contact = $r[0]["id"];
626                                 }
627
628                                 $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $post));
629                                 require_once 'include/queue_fn.php';
630                                 add_to_queue($a->contact, NETWORK_TWITTER, $s);
631                                 notice(t('Twitter post failed. Queued for retry.') . EOL);
632                         } elseif ($iscomment) {
633                                 logger('twitter_post: Update extid ' . $result->id_str . " for post id " . $b['id']);
634                                 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d",
635                                         dbesc("twitter::" . $result->id_str),
636                                         intval($b['id'])
637                                 );
638                         }
639                 }
640         }
641 }
642
643 function twitter_plugin_admin_post(App $a)
644 {
645         $consumerkey     = x($_POST, 'consumerkey')     ? notags(trim($_POST['consumerkey']))     : '';
646         $consumersecret  = x($_POST, 'consumersecret')  ? notags(trim($_POST['consumersecret']))  : '';
647         Config::set('twitter', 'consumerkey', $consumerkey);
648         Config::set('twitter', 'consumersecret', $consumersecret);
649         info(t('Settings updated.') . EOL);
650 }
651
652 function twitter_plugin_admin(App $a, &$o)
653 {
654         $t = get_markup_template("admin.tpl", "addon/twitter/");
655
656         $o = replace_macros($t, array(
657                 '$submit' => t('Save Settings'),
658                 // name, label, value, help, [extra values]
659                 '$consumerkey' => array('consumerkey', t('Consumer key'), Config::get('twitter', 'consumerkey'), ''),
660                 '$consumersecret' => array('consumersecret', t('Consumer secret'), Config::get('twitter', 'consumersecret'), ''),
661         ));
662 }
663
664 function twitter_cron(App $a, $b)
665 {
666         $last = Config::get('twitter', 'last_poll');
667
668         $poll_interval = intval(Config::get('twitter', 'poll_interval'));
669         if (!$poll_interval) {
670                 $poll_interval = TWITTER_DEFAULT_POLL_INTERVAL;
671         }
672
673         if ($last) {
674                 $next = $last + ($poll_interval * 60);
675                 if ($next > time()) {
676                         logger('twitter: poll intervall not reached');
677                         return;
678                 }
679         }
680         logger('twitter: cron_start');
681
682         $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'mirror_posts' AND `v` = '1'");
683         if (count($r)) {
684                 foreach ($r as $rr) {
685                         logger('twitter: fetching for user ' . $rr['uid']);
686                         Worker::add(PRIORITY_MEDIUM, "addon/twitter/twitter_sync.php", 1, (int) $rr['uid']);
687                 }
688         }
689
690         $abandon_days = intval(Config::get('system', 'account_abandon_days'));
691         if ($abandon_days < 1)
692                 $abandon_days = 0;
693
694         $abandon_limit = date("Y-m-d H:i:s", time() - $abandon_days * 86400);
695
696         $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'import' AND `v` = '1'");
697         if (count($r)) {
698                 foreach ($r as $rr) {
699                         if ($abandon_days != 0) {
700                                 $user = q("SELECT `login_date` FROM `user` WHERE uid=%d AND `login_date` >= '%s'", $rr['uid'], $abandon_limit);
701                                 if (!count($user)) {
702                                         logger('abandoned account: timeline from user ' . $rr['uid'] . ' will not be imported');
703                                         continue;
704                                 }
705                         }
706
707                         logger('twitter: importing timeline from user ' . $rr['uid']);
708                         Worker::add(PRIORITY_MEDIUM, "addon/twitter/twitter_sync.php", 2, (int) $rr['uid']);
709                         /*
710                           // To-Do
711                           // check for new contacts once a day
712                           $last_contact_check = PConfig::get($rr['uid'],'pumpio','contact_check');
713                           if($last_contact_check)
714                           $next_contact_check = $last_contact_check + 86400;
715                           else
716                           $next_contact_check = 0;
717
718                           if($next_contact_check <= time()) {
719                           pumpio_getallusers($a, $rr["uid"]);
720                           PConfig::set($rr['uid'],'pumpio','contact_check',time());
721                           }
722                          */
723                 }
724         }
725
726         logger('twitter: cron_end');
727
728         Config::set('twitter', 'last_poll', time());
729 }
730
731 function twitter_expire(App $a, $b)
732 {
733         $days = Config::get('twitter', 'expire');
734
735         if ($days == 0) {
736                 return;
737         }
738
739         if (method_exists('dba', 'delete')) {
740                 $r = dba::select('item', array('id'), array('deleted' => true, 'network' => NETWORK_TWITTER));
741                 while ($row = dba::fetch($r)) {
742                         dba::delete('item', array('id' => $row['id']));
743                 }
744                 dba::close($r);
745         } else {
746                 $r = q("DELETE FROM `item` WHERE `deleted` AND `network` = '%s'", dbesc(NETWORK_TWITTER));
747         }
748
749         require_once "include/items.php";
750
751         logger('twitter_expire: expire_start');
752
753         $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'import' AND `v` = '1' ORDER BY RAND()");
754         if (count($r)) {
755                 foreach ($r as $rr) {
756                         logger('twitter_expire: user ' . $rr['uid']);
757                         item_expire($rr['uid'], $days, NETWORK_TWITTER, true);
758                 }
759         }
760
761         logger('twitter_expire: expire_end');
762 }
763
764 function twitter_prepare_body(App $a, &$b)
765 {
766         if ($b["item"]["network"] != NETWORK_TWITTER) {
767                 return;
768         }
769
770         if ($b["preview"]) {
771                 $max_char = 280;
772                 require_once "include/plaintext.php";
773                 $item = $b["item"];
774                 $item["plink"] = $a->get_baseurl() . "/display/" . $a->user["nickname"] . "/" . $item["parent"];
775
776                 $r = q("SELECT `author-link` FROM item WHERE item.uri = '%s' AND item.uid = %d LIMIT 1",
777                         dbesc($item["thr-parent"]),
778                         intval(local_user()));
779
780                 if (count($r)) {
781                         $orig_post = $r[0];
782
783                         $nicknameplain = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $orig_post["author-link"]);
784                         $nickname = "@[url=" . $orig_post["author-link"] . "]" . $nicknameplain . "[/url]";
785                         $nicknameplain = "@" . $nicknameplain;
786
787                         if ((strpos($item["body"], $nickname) === false) && (strpos($item["body"], $nicknameplain) === false)) {
788                                 $item["body"] = $nickname . " " . $item["body"];
789                         }
790                 }
791
792                 $msgarr = plaintext($item, $max_char, true, 8);
793                 $msg = $msgarr["text"];
794
795                 if (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
796                         $msg .= " " . $msgarr["url"];
797                 }
798
799                 if (isset($msgarr["image"])) {
800                         $msg .= " " . $msgarr["image"];
801                 }
802
803                 $b['html'] = nl2br(htmlspecialchars($msg));
804         }
805 }
806
807 /**
808  * @brief Build the item array for the mirrored post
809  *
810  * @param App $a Application class
811  * @param integer $uid User id
812  * @param object $post Twitter object with the post
813  *
814  * @return array item data to be posted
815  */
816 function twitter_do_mirrorpost(App $a, $uid, $post)
817 {
818         $datarray["type"] = "wall";
819         $datarray["api_source"] = true;
820         $datarray["profile_uid"] = $uid;
821         $datarray["extid"] = NETWORK_TWITTER;
822         $datarray['message_id'] = item_new_uri($a->get_hostname(), $uid, NETWORK_TWITTER . ":" . $post->id);
823         $datarray['object'] = json_encode($post);
824         $datarray["title"] = "";
825
826         if (is_object($post->retweeted_status)) {
827                 // We don't support nested shares, so we mustn't show quotes as shares on retweets
828                 $item = twitter_createpost($a, $uid, $post->retweeted_status, array('id' => 0), false, false, true);
829
830                 $datarray['body'] = "\n" . share_header($item['author-name'], $item['author-link'], $item['author-avatar'], "", $item['created'], $item['plink']);
831
832                 $datarray['body'] .= $item['body'] . '[/share]';
833         } else {
834                 $item = twitter_createpost($a, $uid, $post, array('id' => 0), false, false, false);
835
836                 $datarray['body'] = $item['body'];
837         }
838
839         $datarray["source"] = $item['app'];
840         $datarray["verb"] = $item['verb'];
841
842         if (isset($item["location"])) {
843                 $datarray["location"] = $item["location"];
844         }
845
846         if (isset($item["coord"])) {
847                 $datarray["coord"] = $item["coord"];
848         }
849
850         return $datarray;
851 }
852
853 function twitter_fetchtimeline(App $a, $uid)
854 {
855         $ckey    = Config::get('twitter', 'consumerkey');
856         $csecret = Config::get('twitter', 'consumersecret');
857         $otoken  = PConfig::get($uid, 'twitter', 'oauthtoken');
858         $osecret = PConfig::get($uid, 'twitter', 'oauthsecret');
859         $lastid  = PConfig::get($uid, 'twitter', 'lastid');
860
861         $application_name = Config::get('twitter', 'application_name');
862
863         if ($application_name == "") {
864                 $application_name = $a->get_hostname();
865         }
866
867         $has_picture = false;
868
869         require_once 'mod/item.php';
870         require_once 'include/items.php';
871         require_once 'mod/share.php';
872
873         require_once 'library/twitteroauth.php';
874         $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
875
876         $parameters = array("exclude_replies" => true, "trim_user" => false, "contributor_details" => true, "include_rts" => true, "tweet_mode" => "extended");
877
878         $first_time = ($lastid == "");
879
880         if ($lastid != "") {
881                 $parameters["since_id"] = $lastid;
882         }
883
884         $items = $connection->get('statuses/user_timeline', $parameters);
885
886         if (!is_array($items)) {
887                 return;
888         }
889
890         $posts = array_reverse($items);
891
892         if (count($posts)) {
893                 foreach ($posts as $post) {
894                         if ($post->id_str > $lastid) {
895                                 $lastid = $post->id_str;
896                                 PConfig::set($uid, 'twitter', 'lastid', $lastid);
897                         }
898
899                         if ($first_time) {
900                                 continue;
901                         }
902
903                         if (!stristr($post->source, $application_name)) {
904                                 $_SESSION["authenticated"] = true;
905                                 $_SESSION["uid"] = $uid;
906
907                                 $_REQUEST = twitter_do_mirrorpost($a, $uid, $post);
908
909                                 logger('twitter: posting for user ' . $uid);
910
911                                 item_post($a);
912                         }
913                 }
914         }
915         PConfig::set($uid, 'twitter', 'lastid', $lastid);
916 }
917
918 function twitter_queue_hook(App $a, &$b)
919 {
920         $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
921                 dbesc(NETWORK_TWITTER)
922         );
923         if (!count($qi)) {
924                 return;
925         }
926
927         require_once 'include/queue_fn.php';
928
929         foreach ($qi as $x) {
930                 if ($x['network'] !== NETWORK_TWITTER) {
931                         continue;
932                 }
933
934                 logger('twitter_queue: run');
935
936                 $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid`
937                         WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1",
938                         intval($x['cid'])
939                 );
940                 if (!count($r)) {
941                         continue;
942                 }
943
944                 $user = $r[0];
945
946                 $ckey    = Config::get('twitter', 'consumerkey');
947                 $csecret = Config::get('twitter', 'consumersecret');
948                 $otoken  = PConfig::get($user['uid'], 'twitter', 'oauthtoken');
949                 $osecret = PConfig::get($user['uid'], 'twitter', 'oauthsecret');
950
951                 $success = false;
952
953                 if ($ckey && $csecret && $otoken && $osecret) {
954                         logger('twitter_queue: able to post');
955
956                         $z = unserialize($x['content']);
957
958                         require_once "addon/twitter/codebird.php";
959
960                         $cb = \Codebird\Codebird::getInstance();
961                         $cb->setConsumerKey($ckey, $csecret);
962                         $cb->setToken($otoken, $osecret);
963
964                         if ($z['url'] == "statuses/update") {
965                                 $result = $cb->statuses_update($z['post']);
966                         }
967
968                         logger('twitter_queue: post result: ' . print_r($result, true), LOGGER_DEBUG);
969
970                         if ($result->errors) {
971                                 logger('twitter_queue: Send to Twitter failed: "' . print_r($result->errors, true) . '"');
972                         } else {
973                                 $success = true;
974                                 remove_queue_item($x['id']);
975                         }
976                 } else {
977                         logger("twitter_queue: Error getting tokens for user " . $user['uid']);
978                 }
979
980                 if (!$success) {
981                         logger('twitter_queue: delayed');
982                         update_queue_time($x['id']);
983                 }
984         }
985 }
986
987 function twitter_fix_avatar($avatar)
988 {
989         $new_avatar = str_replace("_normal.", ".", $avatar);
990
991         $info = Image::getInfoFromURL($new_avatar);
992         if (!$info) {
993                 $new_avatar = $avatar;
994         }
995
996         return $new_avatar;
997 }
998
999 function twitter_fetch_contact($uid, $contact, $create_user)
1000 {
1001         if ($contact->id_str == "") {
1002                 return -1;
1003         }
1004
1005         $avatar = twitter_fix_avatar($contact->profile_image_url_https);
1006
1007         GContact::update(array("url" => "https://twitter.com/" . $contact->screen_name,
1008                 "network" => NETWORK_TWITTER, "photo" => $avatar, "hide" => true,
1009                 "name" => $contact->name, "nick" => $contact->screen_name,
1010                 "location" => $contact->location, "about" => $contact->description,
1011                 "addr" => $contact->screen_name . "@twitter.com", "generation" => 2));
1012
1013         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1014                 intval($uid), dbesc("twitter::" . $contact->id_str));
1015
1016         if (!count($r) && !$create_user) {
1017                 return 0;
1018         }
1019
1020         if (count($r) && ($r[0]["readonly"] || $r[0]["blocked"])) {
1021                 logger("twitter_fetch_contact: Contact '" . $r[0]["nick"] . "' is blocked or readonly.", LOGGER_DEBUG);
1022                 return -1;
1023         }
1024
1025         if (!count($r)) {
1026                 // create contact record
1027                 q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`,
1028                                         `name`, `nick`, `photo`, `network`, `rel`, `priority`,
1029                                         `location`, `about`, `writable`, `blocked`, `readonly`, `pending`)
1030                                         VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', %d, 0, 0, 0)",
1031                         intval($uid),
1032                         dbesc(datetime_convert()),
1033                         dbesc("https://twitter.com/" . $contact->screen_name),
1034                         dbesc(normalise_link("https://twitter.com/" . $contact->screen_name)),
1035                         dbesc($contact->screen_name."@twitter.com"),
1036                         dbesc("twitter::" . $contact->id_str),
1037                         dbesc(''),
1038                         dbesc("twitter::" . $contact->id_str),
1039                         dbesc($contact->name),
1040                         dbesc($contact->screen_name),
1041                         dbesc($avatar),
1042                         dbesc(NETWORK_TWITTER),
1043                         intval(CONTACT_IS_FRIEND),
1044                         intval(1),
1045                         dbesc($contact->location),
1046                         dbesc($contact->description),
1047                         intval(1)
1048                 );
1049
1050                 $r = q("SELECT * FROM `contact` WHERE `alias` = '%s' AND `uid` = %d LIMIT 1",
1051                         dbesc("twitter::".$contact->id_str),
1052                         intval($uid)
1053                 );
1054
1055                 if (!count($r)) {
1056                         return false;
1057                 }
1058
1059                 $contact_id = $r[0]['id'];
1060
1061                 Group::addMember(User::getDefaultGroup($uid), $contact_id);
1062
1063                 $photos = Photo::importProfilePhoto($avatar, $uid, $contact_id, true);
1064
1065                 if ($photos) {
1066                         q("UPDATE `contact` SET `photo` = '%s',
1067                                                 `thumb` = '%s',
1068                                                 `micro` = '%s',
1069                                                 `name-date` = '%s',
1070                                                 `uri-date` = '%s',
1071                                                         `avatar-date` = '%s'
1072                                         WHERE `id` = %d",
1073                                 dbesc($photos[0]),
1074                                 dbesc($photos[1]),
1075                                 dbesc($photos[2]),
1076                                 dbesc(datetime_convert()),
1077                                 dbesc(datetime_convert()),
1078                                 dbesc(datetime_convert()),
1079                                 intval($contact_id)
1080                         );
1081                 }
1082         } else {
1083                 // update profile photos once every two weeks as we have no notification of when they change.
1084                 //$update_photo = (($r[0]['avatar-date'] < datetime_convert('','','now -2 days')) ? true : false);
1085                 $update_photo = ($r[0]['avatar-date'] < datetime_convert('', '', 'now -12 hours'));
1086
1087                 // check that we have all the photos, this has been known to fail on occasion
1088                 if ((!$r[0]['photo']) || (!$r[0]['thumb']) || (!$r[0]['micro']) || ($update_photo)) {
1089                         logger("twitter_fetch_contact: Updating contact " . $contact->screen_name, LOGGER_DEBUG);
1090
1091                         $photos = Photo::importProfilePhoto($avatar, $uid, $r[0]['id'], true);
1092
1093                         if ($photos) {
1094                                 q("UPDATE `contact` SET `photo` = '%s',
1095                                                         `thumb` = '%s',
1096                                                         `micro` = '%s',
1097                                                         `name-date` = '%s',
1098                                                         `uri-date` = '%s',
1099                                                         `avatar-date` = '%s',
1100                                                         `url` = '%s',
1101                                                         `nurl` = '%s',
1102                                                         `addr` = '%s',
1103                                                         `name` = '%s',
1104                                                         `nick` = '%s',
1105                                                         `location` = '%s',
1106                                                         `about` = '%s'
1107                                                 WHERE `id` = %d",
1108                                         dbesc($photos[0]),
1109                                         dbesc($photos[1]),
1110                                         dbesc($photos[2]),
1111                                         dbesc(datetime_convert()),
1112                                         dbesc(datetime_convert()),
1113                                         dbesc(datetime_convert()),
1114                                         dbesc("https://twitter.com/".$contact->screen_name),
1115                                         dbesc(normalise_link("https://twitter.com/".$contact->screen_name)),
1116                                         dbesc($contact->screen_name."@twitter.com"),
1117                                         dbesc($contact->name),
1118                                         dbesc($contact->screen_name),
1119                                         dbesc($contact->location),
1120                                         dbesc($contact->description),
1121                                         intval($r[0]['id'])
1122                                 );
1123                         }
1124                 }
1125         }
1126
1127         return $r[0]["id"];
1128 }
1129
1130 function twitter_fetchuser(App $a, $uid, $screen_name = "", $user_id = "")
1131 {
1132         $ckey    = Config::get('twitter', 'consumerkey');
1133         $csecret = Config::get('twitter', 'consumersecret');
1134         $otoken  = PConfig::get($uid, 'twitter', 'oauthtoken');
1135         $osecret = PConfig::get($uid, 'twitter', 'oauthsecret');
1136
1137         require_once "addon/twitter/codebird.php";
1138
1139         $cb = \Codebird\Codebird::getInstance();
1140         $cb->setConsumerKey($ckey, $csecret);
1141         $cb->setToken($otoken, $osecret);
1142
1143         $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1144                 intval($uid));
1145
1146         if (count($r)) {
1147                 $self = $r[0];
1148         } else {
1149                 return;
1150         }
1151
1152         $parameters = array();
1153
1154         if ($screen_name != "") {
1155                 $parameters["screen_name"] = $screen_name;
1156         }
1157
1158         if ($user_id != "") {
1159                 $parameters["user_id"] = $user_id;
1160         }
1161
1162         // Fetching user data
1163         $user = $cb->users_show($parameters);
1164
1165         if (!is_object($user)) {
1166                 return;
1167         }
1168
1169         $contact_id = twitter_fetch_contact($uid, $user, true);
1170
1171         return $contact_id;
1172 }
1173
1174 function twitter_expand_entities(App $a, $body, $item, $no_tags = false, $picture)
1175 {
1176         require_once "include/network.php";
1177
1178         $tags = "";
1179
1180         $plain = $body;
1181
1182         if (isset($item->entities->urls)) {
1183                 $type = "";
1184                 $footerurl = "";
1185                 $footerlink = "";
1186                 $footer = "";
1187
1188                 foreach ($item->entities->urls AS $url) {
1189                         $plain = str_replace($url->url, '', $plain);
1190
1191                         if ($url->url && $url->expanded_url && $url->display_url) {
1192                                 $expanded_url = original_url($url->expanded_url);
1193
1194                                 $oembed_data = OEmbed::fetchURL($expanded_url);
1195
1196                                 // Quickfix: Workaround for URL with "[" and "]" in it
1197                                 if (strpos($expanded_url, "[") || strpos($expanded_url, "]")) {
1198                                         $expanded_url = $url->url;
1199                                 }
1200
1201                                 if ($type == "") {
1202                                         $type = $oembed_data->type;
1203                                 }
1204
1205                                 if ($oembed_data->type == "video") {
1206                                         //$body = str_replace($url->url,
1207                                         //              "[video]".$expanded_url."[/video]", $body);
1208                                         //$dontincludemedia = true;
1209                                         $type = $oembed_data->type;
1210                                         $footerurl = $expanded_url;
1211                                         $footerlink = "[url=" . $expanded_url . "]" . $expanded_url . "[/url]";
1212
1213                                         $body = str_replace($url->url, $footerlink, $body);
1214                                         //} elseif (($oembed_data->type == "photo") AND isset($oembed_data->url) AND !$dontincludemedia) {
1215                                 } elseif (($oembed_data->type == "photo") && isset($oembed_data->url)) {
1216                                         $body = str_replace($url->url, "[url=" . $expanded_url . "][img]" . $oembed_data->url . "[/img][/url]", $body);
1217                                         //$dontincludemedia = true;
1218                                 } elseif ($oembed_data->type != "link") {
1219                                         $body = str_replace($url->url, "[url=" . $expanded_url . "]" . $expanded_url . "[/url]", $body);
1220                                 } else {
1221                                         $img_str = fetch_url($expanded_url, true, $redirects, 4);
1222
1223                                         $tempfile = tempnam(get_temppath(), "cache");
1224                                         file_put_contents($tempfile, $img_str);
1225                                         $mime = image_type_to_mime_type(exif_imagetype($tempfile));
1226                                         unlink($tempfile);
1227
1228                                         if (substr($mime, 0, 6) == "image/") {
1229                                                 $type = "photo";
1230                                                 $body = str_replace($url->url, "[img]" . $expanded_url . "[/img]", $body);
1231                                                 //$dontincludemedia = true;
1232                                         } else {
1233                                                 $type = $oembed_data->type;
1234                                                 $footerurl = $expanded_url;
1235                                                 $footerlink = "[url=" . $expanded_url . "]" . $expanded_url . "[/url]";
1236
1237                                                 $body = str_replace($url->url, $footerlink, $body);
1238                                         }
1239                                 }
1240                         }
1241                 }
1242
1243                 if ($footerurl != "") {
1244                         $footer = add_page_info($footerurl, false, $picture);
1245                 }
1246
1247                 if (($footerlink != "") && (trim($footer) != "")) {
1248                         $removedlink = trim(str_replace($footerlink, "", $body));
1249
1250                         if (($removedlink == "") || strstr($body, $removedlink)) {
1251                                 $body = $removedlink;
1252                         }
1253
1254                         $body .= $footer;
1255                 }
1256
1257                 if (($footer == "") && ($picture != "")) {
1258                         $body .= "\n\n[img]" . $picture . "[/img]\n";
1259                 } elseif (($footer == "") && ($picture == "")) {
1260                         $body = add_page_info_to_body($body);
1261                 }
1262
1263                 if ($no_tags) {
1264                         return array("body" => $body, "tags" => "", "plain" => $plain);
1265                 }
1266
1267                 $tags_arr = array();
1268
1269                 foreach ($item->entities->hashtags AS $hashtag) {
1270                         $url = "#[url=" . $a->get_baseurl() . "/search?tag=" . rawurlencode($hashtag->text) . "]" . $hashtag->text . "[/url]";
1271                         $tags_arr["#" . $hashtag->text] = $url;
1272                         $body = str_replace("#" . $hashtag->text, $url, $body);
1273                 }
1274
1275                 foreach ($item->entities->user_mentions AS $mention) {
1276                         $url = "@[url=https://twitter.com/" . rawurlencode($mention->screen_name) . "]" . $mention->screen_name . "[/url]";
1277                         $tags_arr["@" . $mention->screen_name] = $url;
1278                         $body = str_replace("@" . $mention->screen_name, $url, $body);
1279                 }
1280
1281                 // it seems as if the entities aren't always covering all mentions. So the rest will be checked here
1282                 $tags = get_tags($body);
1283
1284                 if (count($tags)) {
1285                         foreach ($tags as $tag) {
1286                                 if (strstr(trim($tag), " ")) {
1287                                         continue;
1288                                 }
1289
1290                                 if (strpos($tag, '#') === 0) {
1291                                         if (strpos($tag, '[url=')) {
1292                                                 continue;
1293                                         }
1294
1295                                         // don't link tags that are already embedded in links
1296                                         if (preg_match('/\[(.*?)' . preg_quote($tag, '/') . '(.*?)\]/', $body)) {
1297                                                 continue;
1298                                         }
1299                                         if (preg_match('/\[(.*?)\]\((.*?)' . preg_quote($tag, '/') . '(.*?)\)/', $body)) {
1300                                                 continue;
1301                                         }
1302
1303                                         $basetag = str_replace('_', ' ', substr($tag, 1));
1304                                         $url = '#[url=' . $a->get_baseurl() . '/search?tag=' . rawurlencode($basetag) . ']' . $basetag . '[/url]';
1305                                         $body = str_replace($tag, $url, $body);
1306                                         $tags_arr["#" . $basetag] = $url;
1307                                 } elseif (strpos($tag, '@') === 0) {
1308                                         if (strpos($tag, '[url=')) {
1309                                                 continue;
1310                                         }
1311
1312                                         $basetag = substr($tag, 1);
1313                                         $url = '@[url=https://twitter.com/' . rawurlencode($basetag) . ']' . $basetag . '[/url]';
1314                                         $body = str_replace($tag, $url, $body);
1315                                         $tags_arr["@" . $basetag] = $url;
1316                                 }
1317                         }
1318                 }
1319
1320                 $tags = implode($tags_arr, ",");
1321         }
1322         return array("body" => $body, "tags" => $tags, "plain" => $plain);
1323 }
1324
1325 /**
1326  * @brief Fetch media entities and add media links to the body
1327  *
1328  * @param object $post Twitter object with the post
1329  * @param array $postarray Array of the item that is about to be posted
1330  *
1331  * @return $picture string Returns a a single picture string if it isn't a media post
1332  */
1333 function twitter_media_entities($post, &$postarray)
1334 {
1335         // There are no media entities? So we quit.
1336         if (!is_array($post->extended_entities->media)) {
1337                 return "";
1338         }
1339
1340         // When the post links to an external page, we only take one picture.
1341         // We only do this when there is exactly one media.
1342         if ((count($post->entities->urls) > 0) && (count($post->extended_entities->media) == 1)) {
1343                 $picture = "";
1344                 foreach ($post->extended_entities->media AS $medium) {
1345                         if (isset($medium->media_url_https)) {
1346                                 $picture = $medium->media_url_https;
1347                                 $postarray['body'] = str_replace($medium->url, "", $postarray['body']);
1348                         }
1349                 }
1350                 return $picture;
1351         }
1352
1353         // This is a pure media post, first search for all media urls
1354         $media = array();
1355         foreach ($post->extended_entities->media AS $medium) {
1356                 switch ($medium->type) {
1357                         case 'photo':
1358                                 $media[$medium->url] .= "\n[img]" . $medium->media_url_https . "[/img]";
1359                                 $postarray['object-type'] = ACTIVITY_OBJ_IMAGE;
1360                                 break;
1361                         case 'video':
1362                         case 'animated_gif':
1363                                 $media[$medium->url] .= "\n[img]" . $medium->media_url_https . "[/img]";
1364                                 $postarray['object-type'] = ACTIVITY_OBJ_VIDEO;
1365                                 if (is_array($medium->video_info->variants)) {
1366                                         $bitrate = 0;
1367                                         // We take the video with the highest bitrate
1368                                         foreach ($medium->video_info->variants AS $variant) {
1369                                                 if (($variant->content_type == "video/mp4") && ($variant->bitrate >= $bitrate)) {
1370                                                         $media[$medium->url] = "\n[video]" . $variant->url . "[/video]";
1371                                                         $bitrate = $variant->bitrate;
1372                                                 }
1373                                         }
1374                                 }
1375                                 break;
1376                         // The following code will only be activated for test reasons
1377                         //default:
1378                         //      $postarray['body'] .= print_r($medium, true);
1379                 }
1380         }
1381
1382         // Now we replace the media urls.
1383         foreach ($media AS $key => $value) {
1384                 $postarray['body'] = str_replace($key, "\n" . $value . "\n", $postarray['body']);
1385         }
1386         return "";
1387 }
1388
1389 function twitter_createpost(App $a, $uid, $post, $self, $create_user, $only_existing_contact, $noquote)
1390 {
1391         $postarray = array();
1392         $postarray['network'] = NETWORK_TWITTER;
1393         $postarray['gravity'] = 0;
1394         $postarray['uid'] = $uid;
1395         $postarray['wall'] = 0;
1396         $postarray['uri'] = "twitter::" . $post->id_str;
1397         $postarray['object'] = json_encode($post);
1398
1399         // Don't import our own comments
1400         $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
1401                         dbesc($postarray['uri']),
1402                         intval($uid)
1403         );
1404
1405         if (count($r)) {
1406                 logger("Item with extid " . $postarray['uri'] . " found.", LOGGER_DEBUG);
1407                 return array();
1408         }
1409
1410         $contactid = 0;
1411
1412         if ($post->in_reply_to_status_id_str != "") {
1413                 $parent = "twitter::" . $post->in_reply_to_status_id_str;
1414
1415                 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1416                                 dbesc($parent),
1417                                 intval($uid)
1418                 );
1419                 if (count($r)) {
1420                         $postarray['thr-parent'] = $r[0]["uri"];
1421                         $postarray['parent-uri'] = $r[0]["parent-uri"];
1422                         $postarray['parent'] = $r[0]["parent"];
1423                         $postarray['object-type'] = ACTIVITY_OBJ_COMMENT;
1424                 } else {
1425                         $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
1426                                         dbesc($parent),
1427                                         intval($uid)
1428                         );
1429                         if (count($r)) {
1430                                 $postarray['thr-parent'] = $r[0]['uri'];
1431                                 $postarray['parent-uri'] = $r[0]['parent-uri'];
1432                                 $postarray['parent'] = $r[0]['parent'];
1433                                 $postarray['object-type'] = ACTIVITY_OBJ_COMMENT;
1434                         } else {
1435                                 $postarray['thr-parent'] = $postarray['uri'];
1436                                 $postarray['parent-uri'] = $postarray['uri'];
1437                                 $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
1438                         }
1439                 }
1440
1441                 // Is it me?
1442                 $own_id = PConfig::get($uid, 'twitter', 'own_id');
1443
1444                 if ($post->user->id_str == $own_id) {
1445                         $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1446                                 intval($uid));
1447
1448                         if (count($r)) {
1449                                 $contactid = $r[0]["id"];
1450
1451                                 $postarray['owner-name']   = $r[0]["name"];
1452                                 $postarray['owner-link']   = $r[0]["url"];
1453                                 $postarray['owner-avatar'] = $r[0]["photo"];
1454                         } else {
1455                                 logger("No self contact for user " . $uid, LOGGER_DEBUG);
1456                                 return array();
1457                         }
1458                 }
1459                 // Don't create accounts of people who just comment something
1460                 $create_user = false;
1461         } else {
1462                 $postarray['parent-uri'] = $postarray['uri'];
1463                 $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
1464         }
1465
1466         if ($contactid == 0) {
1467                 $contactid = twitter_fetch_contact($uid, $post->user, $create_user);
1468
1469                 $postarray['owner-name'] = $post->user->name;
1470                 $postarray['owner-link'] = "https://twitter.com/" . $post->user->screen_name;
1471                 $postarray['owner-avatar'] = twitter_fix_avatar($post->user->profile_image_url_https);
1472         }
1473
1474         if (($contactid == 0) && !$only_existing_contact) {
1475                 $contactid = $self['id'];
1476         } elseif ($contactid <= 0) {
1477                 logger("Contact ID is zero or less than zero.", LOGGER_DEBUG);
1478                 return array();
1479         }
1480
1481         $postarray['contact-id'] = $contactid;
1482
1483         $postarray['verb'] = ACTIVITY_POST;
1484         $postarray['author-name'] = $postarray['owner-name'];
1485         $postarray['author-link'] = $postarray['owner-link'];
1486         $postarray['author-avatar'] = $postarray['owner-avatar'];
1487         $postarray['plink'] = "https://twitter.com/" . $post->user->screen_name . "/status/" . $post->id_str;
1488         $postarray['app'] = strip_tags($post->source);
1489
1490         if ($post->user->protected) {
1491                 $postarray['private'] = 1;
1492                 $postarray['allow_cid'] = '<' . $self['id'] . '>';
1493         }
1494
1495         if (is_string($post->full_text)) {
1496                 $postarray['body'] = $post->full_text;
1497         } else {
1498                 $postarray['body'] = $post->text;
1499         }
1500
1501         // When the post contains links then use the correct object type
1502         if (count($post->entities->urls) > 0) {
1503                 $postarray['object-type'] = ACTIVITY_OBJ_BOOKMARK;
1504         }
1505
1506         // Search for media links
1507         $picture = twitter_media_entities($post, $postarray);
1508
1509         $converted = twitter_expand_entities($a, $postarray['body'], $post, false, $picture);
1510         $postarray['body'] = $converted["body"];
1511         $postarray['tag'] = $converted["tags"];
1512         $postarray['created'] = datetime_convert('UTC', 'UTC', $post->created_at);
1513         $postarray['edited'] = datetime_convert('UTC', 'UTC', $post->created_at);
1514
1515         $statustext = $converted["plain"];
1516
1517         if (is_string($post->place->name)) {
1518                 $postarray["location"] = $post->place->name;
1519         }
1520         if (is_string($post->place->full_name)) {
1521                 $postarray["location"] = $post->place->full_name;
1522         }
1523         if (is_array($post->geo->coordinates)) {
1524                 $postarray["coord"] = $post->geo->coordinates[0] . " " . $post->geo->coordinates[1];
1525         }
1526         if (is_array($post->coordinates->coordinates)) {
1527                 $postarray["coord"] = $post->coordinates->coordinates[1] . " " . $post->coordinates->coordinates[0];
1528         }
1529         if (is_object($post->retweeted_status)) {
1530                 $retweet = twitter_createpost($a, $uid, $post->retweeted_status, $self, false, false, $noquote);
1531
1532                 $retweet['object'] = $postarray['object'];
1533                 $retweet['private'] = $postarray['private'];
1534                 $retweet['allow_cid'] = $postarray['allow_cid'];
1535                 $retweet['contact-id'] = $postarray['contact-id'];
1536                 $retweet['owner-name'] = $postarray['owner-name'];
1537                 $retweet['owner-link'] = $postarray['owner-link'];
1538                 $retweet['owner-avatar'] = $postarray['owner-avatar'];
1539
1540                 $postarray = $retweet;
1541         }
1542
1543         if (is_object($post->quoted_status) && !$noquote) {
1544                 $quoted = twitter_createpost($a, $uid, $post->quoted_status, $self, false, false, true);
1545
1546                 $postarray['body'] = $statustext;
1547
1548                 $postarray['body'] .= "\n" . share_header($quoted['author-name'], $quoted['author-link'], $quoted['author-avatar'], "", $quoted['created'], $quoted['plink']);
1549
1550                 $postarray['body'] .= $quoted['body'] . '[/share]';
1551         }
1552
1553         return $postarray;
1554 }
1555
1556 function twitter_checknotification(App $a, $uid, $own_id, $top_item, $postarray)
1557 {
1558         /// TODO: this whole function doesn't seem to work. Needs complete check
1559         $user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1",
1560                         intval($uid)
1561         );
1562
1563         if (!count($user)) {
1564                 return;
1565         }
1566
1567         // Is it me?
1568         if (link_compare($user[0]["url"], $postarray['author-link'])) {
1569                 return;
1570         }
1571
1572         $own_user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1573                         intval($uid),
1574                         dbesc("twitter::".$own_id)
1575         );
1576
1577         if (!count($own_user)) {
1578                 return;
1579         }
1580
1581         // Is it me from twitter?
1582         if (link_compare($own_user[0]["url"], $postarray['author-link'])) {
1583                 return;
1584         }
1585
1586         $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
1587                         dbesc($postarray['parent-uri']),
1588                         intval($uid)
1589         );
1590
1591         if (count($myconv)) {
1592                 foreach ($myconv as $conv) {
1593                         // now if we find a match, it means we're in this conversation
1594                         if (!link_compare($conv['author-link'], $user[0]["url"]) && !link_compare($conv['author-link'], $own_user[0]["url"])) {
1595                                 continue;
1596                         }
1597
1598                         require_once 'include/enotify.php';
1599
1600                         $conv_parent = $conv['parent'];
1601
1602                         notification(array(
1603                                 'type' => NOTIFY_COMMENT,
1604                                 'notify_flags' => $user[0]['notify-flags'],
1605                                 'language' => $user[0]['language'],
1606                                 'to_name' => $user[0]['username'],
1607                                 'to_email' => $user[0]['email'],
1608                                 'uid' => $user[0]['uid'],
1609                                 'item' => $postarray,
1610                                 'link' => $a->get_baseurl() . '/display/' . urlencode(get_item_guid($top_item)),
1611                                 'source_name' => $postarray['author-name'],
1612                                 'source_link' => $postarray['author-link'],
1613                                 'source_photo' => $postarray['author-avatar'],
1614                                 'verb' => ACTIVITY_POST,
1615                                 'otype' => 'item',
1616                                 'parent' => $conv_parent,
1617                         ));
1618
1619                         // only send one notification
1620                         break;
1621                 }
1622         }
1623 }
1624
1625 function twitter_fetchparentposts(App $a, $uid, $post, $connection, $self, $own_id)
1626 {
1627         logger("twitter_fetchparentposts: Fetching for user " . $uid . " and post " . $post->id_str, LOGGER_DEBUG);
1628
1629         $posts = array();
1630
1631         while ($post->in_reply_to_status_id_str != "") {
1632                 $parameters = array("trim_user" => false, "tweet_mode" => "extended", "id" => $post->in_reply_to_status_id_str);
1633
1634                 $post = $connection->get('statuses/show', $parameters);
1635
1636                 if (!count($post)) {
1637                         logger("twitter_fetchparentposts: Can't fetch post " . $parameters->id, LOGGER_DEBUG);
1638                         break;
1639                 }
1640
1641                 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1642                                 dbesc("twitter::".$post->id_str),
1643                                 intval($uid)
1644                 );
1645
1646                 if (count($r)) {
1647                         break;
1648                 }
1649
1650                 $posts[] = $post;
1651         }
1652
1653         logger("twitter_fetchparentposts: Fetching " . count($posts) . " parents", LOGGER_DEBUG);
1654
1655         $posts = array_reverse($posts);
1656
1657         if (count($posts)) {
1658                 foreach ($posts as $post) {
1659                         $postarray = twitter_createpost($a, $uid, $post, $self, false, false, false);
1660
1661                         if (trim($postarray['body']) == "")
1662                                 continue;
1663
1664                         $item = item_store($postarray);
1665                         $postarray["id"] = $item;
1666
1667                         logger('twitter_fetchparentpost: User ' . $self["nick"] . ' posted parent timeline item ' . $item);
1668
1669                         if ($item && !function_exists("check_item_notification")) {
1670                                 twitter_checknotification($a, $uid, $own_id, $item, $postarray);
1671                         }
1672                 }
1673         }
1674 }
1675
1676 function twitter_fetchhometimeline(App $a, $uid)
1677 {
1678         $ckey    = Config::get('twitter', 'consumerkey');
1679         $csecret = Config::get('twitter', 'consumersecret');
1680         $otoken  = PConfig::get($uid, 'twitter', 'oauthtoken');
1681         $osecret = PConfig::get($uid, 'twitter', 'oauthsecret');
1682         $create_user = PConfig::get($uid, 'twitter', 'create_user');
1683         $mirror_posts = PConfig::get($uid, 'twitter', 'mirror_posts');
1684
1685         logger("twitter_fetchhometimeline: Fetching for user " . $uid, LOGGER_DEBUG);
1686
1687         $application_name = Config::get('twitter', 'application_name');
1688
1689         if ($application_name == "") {
1690                 $application_name = $a->get_hostname();
1691         }
1692
1693         require_once 'library/twitteroauth.php';
1694         require_once 'include/items.php';
1695
1696         $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
1697
1698         $own_contact = twitter_fetch_own_contact($a, $uid);
1699
1700         $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1701                 intval($own_contact),
1702                 intval($uid));
1703
1704         if (count($r)) {
1705                 $own_id = $r[0]["nick"];
1706         } else {
1707                 logger("twitter_fetchhometimeline: Own twitter contact not found for user " . $uid, LOGGER_DEBUG);
1708                 return;
1709         }
1710
1711         $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1712                 intval($uid));
1713
1714         if (count($r)) {
1715                 $self = $r[0];
1716         } else {
1717                 logger("twitter_fetchhometimeline: Own contact not found for user " . $uid, LOGGER_DEBUG);
1718                 return;
1719         }
1720
1721         $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1722                 intval($uid));
1723         if (!count($u)) {
1724                 logger("twitter_fetchhometimeline: Own user not found for user " . $uid, LOGGER_DEBUG);
1725                 return;
1726         }
1727
1728         $parameters = array("exclude_replies" => false, "trim_user" => false, "contributor_details" => true, "include_rts" => true, "tweet_mode" => "extended");
1729         //$parameters["count"] = 200;
1730         // Fetching timeline
1731         $lastid = PConfig::get($uid, 'twitter', 'lasthometimelineid');
1732
1733         $first_time = ($lastid == "");
1734
1735         if ($lastid != "") {
1736                 $parameters["since_id"] = $lastid;
1737         }
1738
1739         $items = $connection->get('statuses/home_timeline', $parameters);
1740
1741         if (!is_array($items)) {
1742                 logger("twitter_fetchhometimeline: Error fetching home timeline: " . print_r($items, true), LOGGER_DEBUG);
1743                 return;
1744         }
1745
1746         $posts = array_reverse($items);
1747
1748         logger("twitter_fetchhometimeline: Fetching timeline for user " . $uid . " " . sizeof($posts) . " items", LOGGER_DEBUG);
1749
1750         if (count($posts)) {
1751                 foreach ($posts as $post) {
1752                         if ($post->id_str > $lastid) {
1753                                 $lastid = $post->id_str;
1754                                 PConfig::set($uid, 'twitter', 'lasthometimelineid', $lastid);
1755                         }
1756
1757                         if ($first_time) {
1758                                 continue;
1759                         }
1760
1761                         if (stristr($post->source, $application_name) && $post->user->screen_name == $own_id) {
1762                                 logger("twitter_fetchhometimeline: Skip previously sended post", LOGGER_DEBUG);
1763                                 continue;
1764                         }
1765
1766                         if ($mirror_posts && $post->user->screen_name == $own_id && $post->in_reply_to_status_id_str == "") {
1767                                 logger("twitter_fetchhometimeline: Skip post that will be mirrored", LOGGER_DEBUG);
1768                                 continue;
1769                         }
1770
1771                         if ($post->in_reply_to_status_id_str != "") {
1772                                 twitter_fetchparentposts($a, $uid, $post, $connection, $self, $own_id);
1773                         }
1774
1775                         $postarray = twitter_createpost($a, $uid, $post, $self, $create_user, true, false);
1776
1777                         if (trim($postarray['body']) == "") {
1778                                 continue;
1779                         }
1780
1781                         $item = item_store($postarray);
1782                         $postarray["id"] = $item;
1783
1784                         logger('twitter_fetchhometimeline: User ' . $self["nick"] . ' posted home timeline item ' . $item);
1785
1786                         if ($item && !function_exists("check_item_notification")) {
1787                                 twitter_checknotification($a, $uid, $own_id, $item, $postarray);
1788                         }
1789                 }
1790         }
1791         PConfig::set($uid, 'twitter', 'lasthometimelineid', $lastid);
1792
1793         // Fetching mentions
1794         $lastid = PConfig::get($uid, 'twitter', 'lastmentionid');
1795
1796         $first_time = ($lastid == "");
1797
1798         if ($lastid != "") {
1799                 $parameters["since_id"] = $lastid;
1800         }
1801
1802         $items = $connection->get('statuses/mentions_timeline', $parameters);
1803
1804         if (!is_array($items)) {
1805                 logger("twitter_fetchhometimeline: Error fetching mentions: " . print_r($items, true), LOGGER_DEBUG);
1806                 return;
1807         }
1808
1809         $posts = array_reverse($items);
1810
1811         logger("twitter_fetchhometimeline: Fetching mentions for user " . $uid . " " . sizeof($posts) . " items", LOGGER_DEBUG);
1812
1813         if (count($posts)) {
1814                 foreach ($posts as $post) {
1815                         if ($post->id_str > $lastid) {
1816                                 $lastid = $post->id_str;
1817                         }
1818
1819                         if ($first_time) {
1820                                 continue;
1821                         }
1822
1823                         if ($post->in_reply_to_status_id_str != "") {
1824                                 twitter_fetchparentposts($a, $uid, $post, $connection, $self, $own_id);
1825                         }
1826
1827                         $postarray = twitter_createpost($a, $uid, $post, $self, false, false, false);
1828
1829                         if (trim($postarray['body']) == "") {
1830                                 continue;
1831                         }
1832
1833                         $item = item_store($postarray);
1834                         $postarray["id"] = $item;
1835
1836                         if ($item && function_exists("check_item_notification")) {
1837                                 check_item_notification($item, $uid, NOTIFY_TAGSELF);
1838                         }
1839
1840                         if (!isset($postarray["parent"]) || ($postarray["parent"] == 0)) {
1841                                 $postarray["parent"] = $item;
1842                         }
1843
1844                         logger('twitter_fetchhometimeline: User ' . $self["nick"] . ' posted mention timeline item ' . $item);
1845
1846                         if ($item == 0) {
1847                                 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1848                                         dbesc($postarray['uri']),
1849                                         intval($uid)
1850                                 );
1851                                 if (count($r)) {
1852                                         $item = $r[0]['id'];
1853                                         $parent_id = $r[0]['parent'];
1854                                 }
1855                         } else
1856                                 $parent_id = $postarray['parent'];
1857
1858                         if (($item != 0) && !function_exists("check_item_notification")) {
1859                                 require_once 'include/enotify.php';
1860                                 notification(array(
1861                                         'type'         => NOTIFY_TAGSELF,
1862                                         'notify_flags' => $u[0]['notify-flags'],
1863                                         'language'     => $u[0]['language'],
1864                                         'to_name'      => $u[0]['username'],
1865                                         'to_email'     => $u[0]['email'],
1866                                         'uid'          => $u[0]['uid'],
1867                                         'item'         => $postarray,
1868                                         'link'         => $a->get_baseurl() . '/display/' . urlencode(get_item_guid($item)),
1869                                         'source_name'  => $postarray['author-name'],
1870                                         'source_link'  => $postarray['author-link'],
1871                                         'source_photo' => $postarray['author-avatar'],
1872                                         'verb'         => ACTIVITY_TAG,
1873                                         'otype'        => 'item',
1874                                         'parent'       => $parent_id
1875                                 ));
1876                         }
1877                 }
1878         }
1879
1880         PConfig::set($uid, 'twitter', 'lastmentionid', $lastid);
1881 }
1882
1883 function twitter_fetch_own_contact(App $a, $uid)
1884 {
1885         $ckey    = Config::get('twitter', 'consumerkey');
1886         $csecret = Config::get('twitter', 'consumersecret');
1887         $otoken  = PConfig::get($uid, 'twitter', 'oauthtoken');
1888         $osecret = PConfig::get($uid, 'twitter', 'oauthsecret');
1889
1890         $own_id = PConfig::get($uid, 'twitter', 'own_id');
1891
1892         $contact_id = 0;
1893
1894         if ($own_id == "") {
1895                 require_once 'library/twitteroauth.php';
1896
1897                 $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
1898
1899                 // Fetching user data
1900                 $user = $connection->get('account/verify_credentials');
1901
1902                 PConfig::set($uid, 'twitter', 'own_id', $user->id_str);
1903
1904                 $contact_id = twitter_fetch_contact($uid, $user, true);
1905         } else {
1906                 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1907                         intval($uid),
1908                         dbesc("twitter::" . $own_id));
1909                 if (count($r)) {
1910                         $contact_id = $r[0]["id"];
1911                 } else {
1912                         PConfig::delete($uid, 'twitter', 'own_id');
1913                 }
1914         }
1915
1916         return $contact_id;
1917 }
1918
1919 function twitter_is_retweet(App $a, $uid, $body)
1920 {
1921         $body = trim($body);
1922
1923         // Skip if it isn't a pure repeated messages
1924         // Does it start with a share?
1925         if (strpos($body, "[share") > 0) {
1926                 return false;
1927         }
1928
1929         // Does it end with a share?
1930         if (strlen($body) > (strrpos($body, "[/share]") + 8)) {
1931                 return false;
1932         }
1933
1934         $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "$1", $body);
1935         // Skip if there is no shared message in there
1936         if ($body == $attributes) {
1937                 return false;
1938         }
1939
1940         $link = "";
1941         preg_match("/link='(.*?)'/ism", $attributes, $matches);
1942         if ($matches[1] != "") {
1943                 $link = $matches[1];
1944         }
1945
1946         preg_match('/link="(.*?)"/ism', $attributes, $matches);
1947         if ($matches[1] != "") {
1948                 $link = $matches[1];
1949         }
1950
1951         $id = preg_replace("=https?://twitter.com/(.*)/status/(.*)=ism", "$2", $link);
1952         if ($id == $link) {
1953                 return false;
1954         }
1955
1956         logger('twitter_is_retweet: Retweeting id ' . $id . ' for user ' . $uid, LOGGER_DEBUG);
1957
1958         $ckey    = Config::get('twitter', 'consumerkey');
1959         $csecret = Config::get('twitter', 'consumersecret');
1960         $otoken  = PConfig::get($uid, 'twitter', 'oauthtoken');
1961         $osecret = PConfig::get($uid, 'twitter', 'oauthsecret');
1962
1963         require_once 'library/twitteroauth.php';
1964         $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
1965
1966         $result = $connection->post('statuses/retweet/' . $id);
1967
1968         logger('twitter_is_retweet: result ' . print_r($result, true), LOGGER_DEBUG);
1969
1970         return !isset($result->errors);
1971 }