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