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