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