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