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