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