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