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