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