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