Merge pull request #505 from MrPetovan/task/4116-move-twitteroauth-to-composer
[friendica-addons.git] / statusnet / statusnet.php
1 <?php
2
3 /**
4  * Name: GNU Social Connector
5  * Description: Bidirectional (posting, relaying and reading) connector for GNU Social.
6  * Version: 1.0.5
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 /*
37  * We have to alter the TwitterOAuth class a little bit to work with any GNU Social
38  * installation abroad. Basically it's only make the API path variable and be happy.
39  *
40  * Thank you guys for the Twitter compatible API!
41  */
42
43 define('STATUSNET_DEFAULT_POLL_INTERVAL', 5); // given in minutes
44
45 require_once 'library/twitteroauth.php';
46 require_once 'include/enotify.php';
47
48 use Friendica\App;
49 use Friendica\Content\OEmbed;
50 use Friendica\Content\Text\BBCode;
51 use Friendica\Content\Text\Plaintext;
52 use Friendica\Core\Addon;
53 use Friendica\Core\Config;
54 use Friendica\Core\L10n;
55 use Friendica\Core\PConfig;
56 use Friendica\Model\GContact;
57 use Friendica\Model\Group;
58 use Friendica\Model\Item;
59 use Friendica\Model\Photo;
60 use Friendica\Model\User;
61 use Friendica\Util\Network;
62
63 class StatusNetOAuth extends TwitterOAuth
64 {
65         function get_maxlength()
66         {
67                 $config = $this->get($this->host . 'statusnet/config.json');
68                 return $config->site->textlimit;
69         }
70
71         function accessTokenURL()
72         {
73                 return $this->host . 'oauth/access_token';
74         }
75
76         function authenticateURL()
77         {
78                 return $this->host . 'oauth/authenticate';
79         }
80
81         function authorizeURL()
82         {
83                 return $this->host . 'oauth/authorize';
84         }
85
86         function requestTokenURL()
87         {
88                 return $this->host . 'oauth/request_token';
89         }
90
91         function __construct($apipath, $consumer_key, $consumer_secret, $oauth_token = NULL, $oauth_token_secret = NULL)
92         {
93                 parent::__construct($consumer_key, $consumer_secret, $oauth_token, $oauth_token_secret);
94                 $this->host = $apipath;
95         }
96
97         /**
98          * Make an HTTP request
99          *
100          * @return API results
101          *
102          * Copied here from the twitteroauth library and complemented by applying the proxy settings of friendica
103          */
104         function http($url, $method, $postfields = NULL)
105         {
106                 $this->http_info = [];
107                 $ci = curl_init();
108                 /* Curl settings */
109                 $prx = Config::get('system', 'proxy');
110                 if (strlen($prx)) {
111                         curl_setopt($ci, CURLOPT_HTTPPROXYTUNNEL, 1);
112                         curl_setopt($ci, CURLOPT_PROXY, $prx);
113                         $prxusr = Config::get('system', 'proxyuser');
114                         if (strlen($prxusr)) {
115                                 curl_setopt($ci, CURLOPT_PROXYUSERPWD, $prxusr);
116                         }
117                 }
118                 curl_setopt($ci, CURLOPT_USERAGENT, $this->useragent);
119                 curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout);
120                 curl_setopt($ci, CURLOPT_TIMEOUT, $this->timeout);
121                 curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE);
122                 curl_setopt($ci, CURLOPT_HTTPHEADER, ['Expect:']);
123                 curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer);
124                 curl_setopt($ci, CURLOPT_HEADERFUNCTION, [$this, 'getHeader']);
125                 curl_setopt($ci, CURLOPT_HEADER, FALSE);
126
127                 switch ($method) {
128                         case 'POST':
129                                 curl_setopt($ci, CURLOPT_POST, TRUE);
130                                 if (!empty($postfields)) {
131                                         curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields);
132                                 }
133                                 break;
134                         case 'DELETE':
135                                 curl_setopt($ci, CURLOPT_CUSTOMREQUEST, 'DELETE');
136                                 if (!empty($postfields)) {
137                                         $url = "{$url}?{$postfields}";
138                                 }
139                 }
140
141                 curl_setopt($ci, CURLOPT_URL, $url);
142                 $response = curl_exec($ci);
143                 $this->http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE);
144                 $this->http_info = array_merge($this->http_info, curl_getinfo($ci));
145                 $this->url = $url;
146                 curl_close($ci);
147                 return $response;
148         }
149 }
150
151 function statusnet_install()
152 {
153         //  we need some hooks, for the configuration and for sending tweets
154         Addon::registerHook('connector_settings', 'addon/statusnet/statusnet.php', 'statusnet_settings');
155         Addon::registerHook('connector_settings_post', 'addon/statusnet/statusnet.php', 'statusnet_settings_post');
156         Addon::registerHook('notifier_normal', 'addon/statusnet/statusnet.php', 'statusnet_post_hook');
157         Addon::registerHook('post_local', 'addon/statusnet/statusnet.php', 'statusnet_post_local');
158         Addon::registerHook('jot_networks', 'addon/statusnet/statusnet.php', 'statusnet_jot_nets');
159         Addon::registerHook('cron', 'addon/statusnet/statusnet.php', 'statusnet_cron');
160         Addon::registerHook('prepare_body', 'addon/statusnet/statusnet.php', 'statusnet_prepare_body');
161         Addon::registerHook('check_item_notification', 'addon/statusnet/statusnet.php', 'statusnet_check_item_notification');
162         logger("installed GNU Social");
163 }
164
165 function statusnet_uninstall()
166 {
167         Addon::unregisterHook('connector_settings', 'addon/statusnet/statusnet.php', 'statusnet_settings');
168         Addon::unregisterHook('connector_settings_post', 'addon/statusnet/statusnet.php', 'statusnet_settings_post');
169         Addon::unregisterHook('notifier_normal', 'addon/statusnet/statusnet.php', 'statusnet_post_hook');
170         Addon::unregisterHook('post_local', 'addon/statusnet/statusnet.php', 'statusnet_post_local');
171         Addon::unregisterHook('jot_networks', 'addon/statusnet/statusnet.php', 'statusnet_jot_nets');
172         Addon::unregisterHook('cron', 'addon/statusnet/statusnet.php', 'statusnet_cron');
173         Addon::unregisterHook('prepare_body', 'addon/statusnet/statusnet.php', 'statusnet_prepare_body');
174         Addon::unregisterHook('check_item_notification', 'addon/statusnet/statusnet.php', 'statusnet_check_item_notification');
175
176         // old setting - remove only
177         Addon::unregisterHook('post_local_end', 'addon/statusnet/statusnet.php', 'statusnet_post_hook');
178         Addon::unregisterHook('addon_settings', 'addon/statusnet/statusnet.php', 'statusnet_settings');
179         Addon::unregisterHook('addon_settings_post', 'addon/statusnet/statusnet.php', 'statusnet_settings_post');
180 }
181
182 function statusnet_check_item_notification(App $a, &$notification_data)
183 {
184         $notification_data["profiles"][] = PConfig::get($notification_data["uid"], 'statusnet', 'own_url');
185 }
186
187 function statusnet_jot_nets(App $a, &$b)
188 {
189         if (!local_user()) {
190                 return;
191         }
192
193         $statusnet_post = PConfig::get(local_user(), 'statusnet', 'post');
194         if (intval($statusnet_post) == 1) {
195                 $statusnet_defpost = PConfig::get(local_user(), 'statusnet', 'post_by_default');
196                 $selected = ((intval($statusnet_defpost) == 1) ? ' checked="checked" ' : '');
197                 $b .= '<div class="profile-jot-net"><input type="checkbox" name="statusnet_enable"' . $selected . ' value="1" /> '
198                         . L10n::t('Post to GNU Social') . '</div>';
199         }
200 }
201
202 function statusnet_settings_post(App $a, $post)
203 {
204         if (!local_user()) {
205                 return;
206         }
207         // don't check GNU Social settings if GNU Social submit button is not clicked
208         if (!x($_POST, 'statusnet-submit')) {
209                 return;
210         }
211
212         if (isset($_POST['statusnet-disconnect'])) {
213                 /*               * *
214                  * if the GNU Social-disconnect checkbox is set, clear the GNU Social configuration
215                  */
216                 PConfig::delete(local_user(), 'statusnet', 'consumerkey');
217                 PConfig::delete(local_user(), 'statusnet', 'consumersecret');
218                 PConfig::delete(local_user(), 'statusnet', 'post');
219                 PConfig::delete(local_user(), 'statusnet', 'post_by_default');
220                 PConfig::delete(local_user(), 'statusnet', 'oauthtoken');
221                 PConfig::delete(local_user(), 'statusnet', 'oauthsecret');
222                 PConfig::delete(local_user(), 'statusnet', 'baseapi');
223                 PConfig::delete(local_user(), 'statusnet', 'lastid');
224                 PConfig::delete(local_user(), 'statusnet', 'mirror_posts');
225                 PConfig::delete(local_user(), 'statusnet', 'import');
226                 PConfig::delete(local_user(), 'statusnet', 'create_user');
227                 PConfig::delete(local_user(), 'statusnet', 'own_id');
228         } else {
229                 if (isset($_POST['statusnet-preconf-apiurl'])) {
230                         /*                       * *
231                          * If the user used one of the preconfigured GNU Social server credentials
232                          * use them. All the data are available in the global config.
233                          * Check the API Url never the less and blame the admin if it's not working ^^
234                          */
235                         $globalsn = Config::get('statusnet', 'sites');
236                         foreach ($globalsn as $asn) {
237                                 if ($asn['apiurl'] == $_POST['statusnet-preconf-apiurl']) {
238                                         $apibase = $asn['apiurl'];
239                                         $c = Network::fetchUrl($apibase . 'statusnet/version.xml');
240                                         if (strlen($c) > 0) {
241                                                 PConfig::set(local_user(), 'statusnet', 'consumerkey', $asn['consumerkey']);
242                                                 PConfig::set(local_user(), 'statusnet', 'consumersecret', $asn['consumersecret']);
243                                                 PConfig::set(local_user(), 'statusnet', 'baseapi', $asn['apiurl']);
244                                                 //PConfig::set(local_user(), 'statusnet', 'application_name', $asn['applicationname'] );
245                                         } else {
246                                                 notice(L10n::t('Please contact your site administrator.<br />The provided API URL is not valid.') . EOL . $asn['apiurl'] . EOL);
247                                         }
248                                 }
249                         }
250                         goaway('settings/connectors');
251                 } else {
252                         if (isset($_POST['statusnet-consumersecret'])) {
253                                 //  check if we can reach the API of the GNU Social server
254                                 //  we'll check the API Version for that, if we don't get one we'll try to fix the path but will
255                                 //  resign quickly after this one try to fix the path ;-)
256                                 $apibase = $_POST['statusnet-baseapi'];
257                                 $c = Network::fetchUrl($apibase . 'statusnet/version.xml');
258                                 if (strlen($c) > 0) {
259                                         //  ok the API path is correct, let's save the settings
260                                         PConfig::set(local_user(), 'statusnet', 'consumerkey', $_POST['statusnet-consumerkey']);
261                                         PConfig::set(local_user(), 'statusnet', 'consumersecret', $_POST['statusnet-consumersecret']);
262                                         PConfig::set(local_user(), 'statusnet', 'baseapi', $apibase);
263                                         //PConfig::set(local_user(), 'statusnet', 'application_name', $_POST['statusnet-applicationname'] );
264                                 } else {
265                                         //  the API path is not correct, maybe missing trailing / ?
266                                         $apibase = $apibase . '/';
267                                         $c = Network::fetchUrl($apibase . 'statusnet/version.xml');
268                                         if (strlen($c) > 0) {
269                                                 //  ok the API path is now correct, let's save the settings
270                                                 PConfig::set(local_user(), 'statusnet', 'consumerkey', $_POST['statusnet-consumerkey']);
271                                                 PConfig::set(local_user(), 'statusnet', 'consumersecret', $_POST['statusnet-consumersecret']);
272                                                 PConfig::set(local_user(), 'statusnet', 'baseapi', $apibase);
273                                         } else {
274                                                 //  still not the correct API base, let's do noting
275                                                 notice(L10n::t('We could not contact the GNU Social API with the Path you entered.') . EOL);
276                                         }
277                                 }
278                                 goaway('settings/connectors');
279                         } else {
280                                 if (isset($_POST['statusnet-pin'])) {
281                                         //  if the user supplied us with a PIN from GNU Social, let the magic of OAuth happen
282                                         $api = PConfig::get(local_user(), 'statusnet', 'baseapi');
283                                         $ckey = PConfig::get(local_user(), 'statusnet', 'consumerkey');
284                                         $csecret = PConfig::get(local_user(), 'statusnet', 'consumersecret');
285                                         //  the token and secret for which the PIN was generated were hidden in the settings
286                                         //  form as token and token2, we need a new connection to GNU Social using these token
287                                         //  and secret to request a Access Token with the PIN
288                                         $connection = new StatusNetOAuth($api, $ckey, $csecret, $_POST['statusnet-token'], $_POST['statusnet-token2']);
289                                         $token = $connection->getAccessToken($_POST['statusnet-pin']);
290                                         //  ok, now that we have the Access Token, save them in the user config
291                                         PConfig::set(local_user(), 'statusnet', 'oauthtoken', $token['oauth_token']);
292                                         PConfig::set(local_user(), 'statusnet', 'oauthsecret', $token['oauth_token_secret']);
293                                         PConfig::set(local_user(), 'statusnet', 'post', 1);
294                                         PConfig::set(local_user(), 'statusnet', 'post_taglinks', 1);
295                                         //  reload the Addon Settings page, if we don't do it see Bug #42
296                                         goaway('settings/connectors');
297                                 } else {
298                                         //  if no PIN is supplied in the POST variables, the user has changed the setting
299                                         //  to post a dent for every new __public__ posting to the wall
300                                         PConfig::set(local_user(), 'statusnet', 'post', intval($_POST['statusnet-enable']));
301                                         PConfig::set(local_user(), 'statusnet', 'post_by_default', intval($_POST['statusnet-default']));
302                                         PConfig::set(local_user(), 'statusnet', 'mirror_posts', intval($_POST['statusnet-mirror']));
303                                         PConfig::set(local_user(), 'statusnet', 'import', intval($_POST['statusnet-import']));
304                                         PConfig::set(local_user(), 'statusnet', 'create_user', intval($_POST['statusnet-create_user']));
305
306                                         if (!intval($_POST['statusnet-mirror']))
307                                                 PConfig::delete(local_user(), 'statusnet', 'lastid');
308
309                                         info(L10n::t('GNU Social settings updated.') . EOL);
310                                 }
311                         }
312                 }
313         }
314 }
315
316 function statusnet_settings(App $a, &$s)
317 {
318         if (!local_user()) {
319                 return;
320         }
321         $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/statusnet/statusnet.css' . '" media="all" />' . "\r\n";
322         /*       * *
323          * 1) Check that we have a base api url and a consumer key & secret
324          * 2) If no OAuthtoken & stuff is present, generate button to get some
325          *    allow the user to cancel the connection process at this step
326          * 3) Checkbox for "Send public notices (respect size limitation)
327          */
328         $api     = PConfig::get(local_user(), 'statusnet', 'baseapi');
329         $ckey    = PConfig::get(local_user(), 'statusnet', 'consumerkey');
330         $csecret = PConfig::get(local_user(), 'statusnet', 'consumersecret');
331         $otoken  = PConfig::get(local_user(), 'statusnet', 'oauthtoken');
332         $osecret = PConfig::get(local_user(), 'statusnet', 'oauthsecret');
333         $enabled = PConfig::get(local_user(), 'statusnet', 'post');
334         $checked = (($enabled) ? ' checked="checked" ' : '');
335         $defenabled = PConfig::get(local_user(), 'statusnet', 'post_by_default');
336         $defchecked = (($defenabled) ? ' checked="checked" ' : '');
337         $mirrorenabled = PConfig::get(local_user(), 'statusnet', 'mirror_posts');
338         $mirrorchecked = (($mirrorenabled) ? ' checked="checked" ' : '');
339         $import = PConfig::get(local_user(), 'statusnet', 'import');
340         $importselected = ["", "", ""];
341         $importselected[$import] = ' selected="selected"';
342         //$importenabled = PConfig::get(local_user(),'statusnet','import');
343         //$importchecked = (($importenabled) ? ' checked="checked" ' : '');
344         $create_userenabled = PConfig::get(local_user(), 'statusnet', 'create_user');
345         $create_userchecked = (($create_userenabled) ? ' checked="checked" ' : '');
346
347         $css = (($enabled) ? '' : '-disabled');
348
349         $s .= '<span id="settings_statusnet_inflated" class="settings-block fakelink" style="display: block;" onclick="openClose(\'settings_statusnet_expanded\'); openClose(\'settings_statusnet_inflated\');">';
350         $s .= '<img class="connector' . $css . '" src="images/gnusocial.png" /><h3 class="connector">' . L10n::t('GNU Social Import/Export/Mirror') . '</h3>';
351         $s .= '</span>';
352         $s .= '<div id="settings_statusnet_expanded" class="settings-block" style="display: none;">';
353         $s .= '<span class="fakelink" onclick="openClose(\'settings_statusnet_expanded\'); openClose(\'settings_statusnet_inflated\');">';
354         $s .= '<img class="connector' . $css . '" src="images/gnusocial.png" /><h3 class="connector">' . L10n::t('GNU Social Import/Export/Mirror') . '</h3>';
355         $s .= '</span>';
356
357         if ((!$ckey) && (!$csecret)) {
358                 /*               * *
359                  * no consumer keys
360                  */
361                 $globalsn = Config::get('statusnet', 'sites');
362                 /*               * *
363                  * lets check if we have one or more globally configured GNU Social
364                  * server OAuth credentials in the configuration. If so offer them
365                  * with a little explanation to the user as choice - otherwise
366                  * ignore this option entirely.
367                  */
368                 if (!$globalsn == null) {
369                         $s .= '<h4>' . L10n::t('Globally Available GNU Social OAuthKeys') . '</h4>';
370                         $s .= '<p>' . L10n::t("There are preconfigured OAuth key pairs for some GNU Social servers available. If you are using one of them, please use these credentials. If not feel free to connect to any other GNU Social instance \x28see below\x29.") . '</p>';
371                         $s .= '<div id="statusnet-preconf-wrapper">';
372                         foreach ($globalsn as $asn) {
373                                 $s .= '<input type="radio" name="statusnet-preconf-apiurl" value="' . $asn['apiurl'] . '">' . $asn['sitename'] . '<br />';
374                         }
375                         $s .= '<p></p><div class="clear"></div></div>';
376                         $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
377                 }
378                 $s .= '<h4>' . L10n::t('Provide your own OAuth Credentials') . '</h4>';
379                 $s .= '<p>' . L10n::t('No consumer key pair for GNU Social found. Register your Friendica Account as an desktop client on your GNU Social account, copy the consumer key pair here and enter the API base root.<br />Before you register your own OAuth key pair ask the administrator if there is already a key pair for this Friendica installation at your favorited GNU Social installation.') . '</p>';
380                 $s .= '<div id="statusnet-consumer-wrapper">';
381                 $s .= '<label id="statusnet-consumerkey-label" for="statusnet-consumerkey">' . L10n::t('OAuth Consumer Key') . '</label>';
382                 $s .= '<input id="statusnet-consumerkey" type="text" name="statusnet-consumerkey" size="35" /><br />';
383                 $s .= '<div class="clear"></div>';
384                 $s .= '<label id="statusnet-consumersecret-label" for="statusnet-consumersecret">' . L10n::t('OAuth Consumer Secret') . '</label>';
385                 $s .= '<input id="statusnet-consumersecret" type="text" name="statusnet-consumersecret" size="35" /><br />';
386                 $s .= '<div class="clear"></div>';
387                 $s .= '<label id="statusnet-baseapi-label" for="statusnet-baseapi">' . L10n::t("Base API Path \x28remember the trailing /\x29") . '</label>';
388                 $s .= '<input id="statusnet-baseapi" type="text" name="statusnet-baseapi" size="35" /><br />';
389                 $s .= '<div class="clear"></div>';
390                 //$s .= '<label id="statusnet-applicationname-label" for="statusnet-applicationname">'.L10n::t('GNU Socialapplication name').'</label>';
391                 //$s .= '<input id="statusnet-applicationname" type="text" name="statusnet-applicationname" size="35" /><br />';
392                 $s .= '<p></p><div class="clear"></div>';
393                 $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
394                 $s .= '</div>';
395         } else {
396                 /*               * *
397                  * ok we have a consumer key pair now look into the OAuth stuff
398                  */
399                 if ((!$otoken) && (!$osecret)) {
400                         /*                       * *
401                          * the user has not yet connected the account to GNU Social
402                          * get a temporary OAuth key/secret pair and display a button with
403                          * which the user can request a PIN to connect the account to a
404                          * account at GNU Social
405                          */
406                         $connection = new StatusNetOAuth($api, $ckey, $csecret);
407                         $request_token = $connection->getRequestToken('oob');
408                         $token = $request_token['oauth_token'];
409                         /*                       * *
410                          *  make some nice form
411                          */
412                         $s .= '<p>' . L10n::t('To connect to your GNU Social account click the button below to get a security code from GNU Social which you have to copy into the input box below and submit the form. Only your <strong>public</strong> posts will be posted to GNU Social.') . '</p>';
413                         $s .= '<a href="' . $connection->getAuthorizeURL($token, False) . '" target="_statusnet"><img src="addon/statusnet/signinwithstatusnet.png" alt="' . L10n::t('Log in with GNU Social') . '"></a>';
414                         $s .= '<div id="statusnet-pin-wrapper">';
415                         $s .= '<label id="statusnet-pin-label" for="statusnet-pin">' . L10n::t('Copy the security code from GNU Social here') . '</label>';
416                         $s .= '<input id="statusnet-pin" type="text" name="statusnet-pin" />';
417                         $s .= '<input id="statusnet-token" type="hidden" name="statusnet-token" value="' . $token . '" />';
418                         $s .= '<input id="statusnet-token2" type="hidden" name="statusnet-token2" value="' . $request_token['oauth_token_secret'] . '" />';
419                         $s .= '</div><div class="clear"></div>';
420                         $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
421                         $s .= '<h4>' . L10n::t('Cancel Connection Process') . '</h4>';
422                         $s .= '<div id="statusnet-cancel-wrapper">';
423                         $s .= '<p>' . L10n::t('Current GNU Social API is') . ': ' . $api . '</p>';
424                         $s .= '<label id="statusnet-cancel-label" for="statusnet-cancel">' . L10n::t('Cancel GNU Social Connection') . '</label>';
425                         $s .= '<input id="statusnet-cancel" type="checkbox" name="statusnet-disconnect" value="1" />';
426                         $s .= '</div><div class="clear"></div>';
427                         $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
428                 } else {
429                         /*                       * *
430                          *  we have an OAuth key / secret pair for the user
431                          *  so let's give a chance to disable the postings to GNU Social
432                          */
433                         $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
434                         $details = $connection->get('account/verify_credentials');
435                         $s .= '<div id="statusnet-info" ><img id="statusnet-avatar" src="' . $details->profile_image_url . '" /><p id="statusnet-info-block">' . L10n::t('Currently connected to: ') . '<a href="' . $details->statusnet_profile_url . '" target="_statusnet">' . $details->screen_name . '</a><br /><em>' . $details->description . '</em></p></div>';
436                         $s .= '<p>' . L10n::t('If enabled all your <strong>public</strong> postings can be posted to the associated GNU Social account. You can choose to do so by default (here) or for every posting separately in the posting options when writing the entry.') . '</p>';
437                         if ($a->user['hidewall']) {
438                                 $s .= '<p>' . L10n::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 GNU Social will lead the visitor to a blank page informing the visitor that the access to your profile has been restricted.') . '</p>';
439                         }
440                         $s .= '<div id="statusnet-enable-wrapper">';
441                         $s .= '<label id="statusnet-enable-label" for="statusnet-checkbox">' . L10n::t('Allow posting to GNU Social') . '</label>';
442                         $s .= '<input id="statusnet-checkbox" type="checkbox" name="statusnet-enable" value="1" ' . $checked . '/>';
443                         $s .= '<div class="clear"></div>';
444                         $s .= '<label id="statusnet-default-label" for="statusnet-default">' . L10n::t('Send public postings to GNU Social by default') . '</label>';
445                         $s .= '<input id="statusnet-default" type="checkbox" name="statusnet-default" value="1" ' . $defchecked . '/>';
446                         $s .= '<div class="clear"></div>';
447
448                         $s .= '<label id="statusnet-mirror-label" for="statusnet-mirror">' . L10n::t('Mirror all posts from GNU Social that are no replies or repeated messages') . '</label>';
449                         $s .= '<input id="statusnet-mirror" type="checkbox" name="statusnet-mirror" value="1" ' . $mirrorchecked . '/>';
450
451                         $s .= '<div class="clear"></div>';
452                         $s .= '</div>';
453
454                         $s .= '<label id="statusnet-import-label" for="statusnet-import">' . L10n::t('Import the remote timeline') . '</label>';
455                         //$s .= '<input id="statusnet-import" type="checkbox" name="statusnet-import" value="1" '. $importchecked . '/>';
456
457                         $s .= '<select name="statusnet-import" id="statusnet-import" />';
458                         $s .= '<option value="0" ' . $importselected[0] . '>' . L10n::t("Disabled") . '</option>';
459                         $s .= '<option value="1" ' . $importselected[1] . '>' . L10n::t("Full Timeline") . '</option>';
460                         $s .= '<option value="2" ' . $importselected[2] . '>' . L10n::t("Only Mentions") . '</option>';
461                         $s .= '</select>';
462                         $s .= '<div class="clear"></div>';
463                         /*
464                           $s .= '<label id="statusnet-create_user-label" for="statusnet-create_user">'.L10n::t('Automatically create contacts').'</label>';
465                           $s .= '<input id="statusnet-create_user" type="checkbox" name="statusnet-create_user" value="1" '. $create_userchecked . '/>';
466                           $s .= '<div class="clear"></div>';
467                          */
468                         $s .= '<div id="statusnet-disconnect-wrapper">';
469                         $s .= '<label id="statusnet-disconnect-label" for="statusnet-disconnect">' . L10n::t('Clear OAuth configuration') . '</label>';
470                         $s .= '<input id="statusnet-disconnect" type="checkbox" name="statusnet-disconnect" value="1" />';
471                         $s .= '</div><div class="clear"></div>';
472                         $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
473                 }
474         }
475         $s .= '</div><div class="clear"></div>';
476 }
477
478 function statusnet_post_local(App $a, &$b)
479 {
480         if ($b['edit']) {
481                 return;
482         }
483
484         if (!local_user() || (local_user() != $b['uid'])) {
485                 return;
486         }
487
488         $statusnet_post = PConfig::get(local_user(), 'statusnet', 'post');
489         $statusnet_enable = (($statusnet_post && x($_REQUEST, 'statusnet_enable')) ? intval($_REQUEST['statusnet_enable']) : 0);
490
491         // if API is used, default to the chosen settings
492         if ($b['api_source'] && intval(PConfig::get(local_user(), 'statusnet', 'post_by_default'))) {
493                 $statusnet_enable = 1;
494         }
495
496         if (!$statusnet_enable) {
497                 return;
498         }
499
500         if (strlen($b['postopts'])) {
501                 $b['postopts'] .= ',';
502         }
503
504         $b['postopts'] .= 'statusnet';
505 }
506
507 function statusnet_action(App $a, $uid, $pid, $action)
508 {
509         $api = PConfig::get($uid, 'statusnet', 'baseapi');
510         $ckey = PConfig::get($uid, 'statusnet', 'consumerkey');
511         $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
512         $otoken = PConfig::get($uid, 'statusnet', 'oauthtoken');
513         $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
514
515         $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
516
517         logger("statusnet_action '" . $action . "' ID: " . $pid, LOGGER_DATA);
518
519         switch ($action) {
520                 case "delete":
521                         $result = $connection->post("statuses/destroy/" . $pid);
522                         break;
523                 case "like":
524                         $result = $connection->post("favorites/create/" . $pid);
525                         break;
526                 case "unlike":
527                         $result = $connection->post("favorites/destroy/" . $pid);
528                         break;
529         }
530         logger("statusnet_action '" . $action . "' send, result: " . print_r($result, true), LOGGER_DEBUG);
531 }
532
533 function statusnet_post_hook(App $a, &$b)
534 {
535         /**
536          * Post to GNU Social
537          */
538         if (!PConfig::get($b["uid"], 'statusnet', 'import')) {
539                 if ($b['deleted'] || $b['private'] || ($b['created'] !== $b['edited']))
540                         return;
541         }
542
543         $api = PConfig::get($b["uid"], 'statusnet', 'baseapi');
544         $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $api);
545
546         if ($b['parent'] != $b['id']) {
547                 logger("statusnet_post_hook: parameter " . print_r($b, true), LOGGER_DATA);
548
549                 // Looking if its a reply to a GNU Social post
550                 $hostlength = strlen($hostname) + 2;
551                 if ((substr($b["parent-uri"], 0, $hostlength) != $hostname . "::") && (substr($b["extid"], 0, $hostlength) != $hostname . "::") && (substr($b["thr-parent"], 0, $hostlength) != $hostname . "::")) {
552                         logger("statusnet_post_hook: no GNU Social post " . $b["parent"]);
553                         return;
554                 }
555
556                 $r = q("SELECT `item`.`author-link`, `item`.`uri`, `contact`.`nick` AS contact_nick
557                         FROM `item` INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
558                         WHERE `item`.`uri` = '%s' AND `item`.`uid` = %d LIMIT 1", dbesc($b["thr-parent"]), intval($b["uid"]));
559
560                 if (!count($r)) {
561                         logger("statusnet_post_hook: no parent found " . $b["thr-parent"]);
562                         return;
563                 } else {
564                         $iscomment = true;
565                         $orig_post = $r[0];
566                 }
567
568                 //$nickname = "@[url=".$orig_post["author-link"]."]".$orig_post["contact_nick"]."[/url]";
569                 //$nicknameplain = "@".$orig_post["contact_nick"];
570
571                 $nick = preg_replace("=https?://(.*)/(.*)=ism", "$2", $orig_post["author-link"]);
572
573                 $nickname = "@[url=" . $orig_post["author-link"] . "]" . $nick . "[/url]";
574                 $nicknameplain = "@" . $nick;
575
576                 logger("statusnet_post_hook: comparing " . $nickname . " and " . $nicknameplain . " with " . $b["body"], LOGGER_DEBUG);
577                 if ((strpos($b["body"], $nickname) === false) && (strpos($b["body"], $nicknameplain) === false)) {
578                         $b["body"] = $nickname . " " . $b["body"];
579                 }
580
581                 logger("statusnet_post_hook: parent found " . print_r($orig_post, true), LOGGER_DEBUG);
582         } else {
583                 $iscomment = false;
584
585                 if ($b['private'] || !strstr($b['postopts'], 'statusnet')) {
586                         return;
587                 }
588
589                 // Dont't post if the post doesn't belong to us.
590                 // This is a check for forum postings
591                 $self = dba::selectFirst('contact', ['id'], ['uid' => $b['uid'], 'self' => true]);
592                 if ($b['contact-id'] != $self['id']) {
593                         return;
594                 }
595         }
596
597         if (($b['verb'] == ACTIVITY_POST) && $b['deleted']) {
598                 statusnet_action($a, $b["uid"], substr($orig_post["uri"], $hostlength), "delete");
599         }
600
601         if ($b['verb'] == ACTIVITY_LIKE) {
602                 logger("statusnet_post_hook: parameter 2 " . substr($b["thr-parent"], $hostlength), LOGGER_DEBUG);
603                 if ($b['deleted'])
604                         statusnet_action($a, $b["uid"], substr($b["thr-parent"], $hostlength), "unlike");
605                 else
606                         statusnet_action($a, $b["uid"], substr($b["thr-parent"], $hostlength), "like");
607                 return;
608         }
609
610         if ($b['deleted'] || ($b['created'] !== $b['edited'])) {
611                 return;
612         }
613
614         // if posts comes from GNU Social don't send it back
615         if ($b['extid'] == NETWORK_STATUSNET) {
616                 return;
617         }
618
619         if ($b['app'] == "StatusNet") {
620                 return;
621         }
622
623         logger('GNU Socialpost invoked');
624
625         PConfig::load($b['uid'], 'statusnet');
626
627         $api     = PConfig::get($b['uid'], 'statusnet', 'baseapi');
628         $ckey    = PConfig::get($b['uid'], 'statusnet', 'consumerkey');
629         $csecret = PConfig::get($b['uid'], 'statusnet', 'consumersecret');
630         $otoken  = PConfig::get($b['uid'], 'statusnet', 'oauthtoken');
631         $osecret = PConfig::get($b['uid'], 'statusnet', 'oauthsecret');
632
633         if ($ckey && $csecret && $otoken && $osecret) {
634                 // If it's a repeated message from GNU Social then do a native retweet and exit
635                 if (statusnet_is_retweet($a, $b['uid'], $b['body'])) {
636                         return;
637                 }
638
639                 require_once 'include/bbcode.php';
640                 $dent = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
641                 $max_char = $dent->get_maxlength(); // max. length for a dent
642
643                 PConfig::set($b['uid'], 'statusnet', 'max_char', $max_char);
644
645                 $tempfile = "";
646                 $msgarr = BBCode::toPlaintext($b, $max_char, true, 7);
647                 $msg = $msgarr["text"];
648
649                 if (($msg == "") && isset($msgarr["title"]))
650                         $msg = Plaintext::shorten($msgarr["title"], $max_char - 50);
651
652                 $image = "";
653
654                 if (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
655                         if ((strlen($msgarr["url"]) > 20) &&
656                                 ((strlen($msg . " \n" . $msgarr["url"]) > $max_char))) {
657                                 $msg .= " \n" . Network::shortenUrl($msgarr["url"]);
658                         } else {
659                                 $msg .= " \n" . $msgarr["url"];
660                         }
661                 } elseif (isset($msgarr["image"]) && ($msgarr["type"] != "video")) {
662                         $image = $msgarr["image"];
663                 }
664
665                 if ($image != "") {
666                         $img_str = Network::fetchUrl($image);
667                         $tempfile = tempnam(get_temppath(), "cache");
668                         file_put_contents($tempfile, $img_str);
669                         $postdata = ["status" => $msg, "media[]" => $tempfile];
670                 } else {
671                         $postdata = ["status" => $msg];
672                 }
673
674                 // and now dent it :-)
675                 if (strlen($msg)) {
676                         if ($iscomment) {
677                                 $postdata["in_reply_to_status_id"] = substr($orig_post["uri"], $hostlength);
678                                 logger('statusnet_post send reply ' . print_r($postdata, true), LOGGER_DEBUG);
679                         }
680
681                         // New code that is able to post pictures
682                         require_once "addon/statusnet/codebird.php";
683                         $cb = \CodebirdSN\CodebirdSN::getInstance();
684                         $cb->setAPIEndpoint($api);
685                         $cb->setConsumerKey($ckey, $csecret);
686                         $cb->setToken($otoken, $osecret);
687                         $result = $cb->statuses_update($postdata);
688                         //$result = $dent->post('statuses/update', $postdata);
689                         logger('statusnet_post send, result: ' . print_r($result, true) .
690                                 "\nmessage: " . $msg, LOGGER_DEBUG . "\nOriginal post: " . print_r($b, true) . "\nPost Data: " . print_r($postdata, true));
691
692                         if ($result->source) {
693                                 PConfig::set($b["uid"], "statusnet", "application_name", strip_tags($result->source));
694                         }
695
696                         if ($result->error) {
697                                 logger('Send to GNU Social failed: "' . $result->error . '"');
698                         } elseif ($iscomment) {
699                                 logger('statusnet_post: Update extid ' . $result->id . " for post id " . $b['id']);
700                                 q("UPDATE `item` SET `extid` = '%s', `body` = '%s' WHERE `id` = %d",
701                                         dbesc($hostname . "::" . $result->id),
702                                         dbesc($result->text),
703                                         intval($b['id'])
704                                 );
705                         }
706                 }
707                 if ($tempfile != "") {
708                         unlink($tempfile);
709                 }
710         }
711 }
712
713 function statusnet_addon_admin_post(App $a)
714 {
715         $sites = [];
716
717         foreach ($_POST['sitename'] as $id => $sitename) {
718                 $sitename = trim($sitename);
719                 $apiurl = trim($_POST['apiurl'][$id]);
720                 if (!(substr($apiurl, -1) == '/')) {
721                         $apiurl = $apiurl . '/';
722                 }
723                 $secret = trim($_POST['secret'][$id]);
724                 $key = trim($_POST['key'][$id]);
725                 //$applicationname = ((x($_POST, 'applicationname')) ? notags(trim($_POST['applicationname'][$id])):'');
726                 if ($sitename != "" &&
727                         $apiurl != "" &&
728                         $secret != "" &&
729                         $key != "" &&
730                         !x($_POST['delete'][$id])) {
731
732                         $sites[] = [
733                                 'sitename' => $sitename,
734                                 'apiurl' => $apiurl,
735                                 'consumersecret' => $secret,
736                                 'consumerkey' => $key,
737                                 //'applicationname' => $applicationname
738                         ];
739                 }
740         }
741
742         $sites = Config::set('statusnet', 'sites', $sites);
743 }
744
745 function statusnet_addon_admin(App $a, &$o)
746 {
747         $sites = Config::get('statusnet', 'sites');
748         $sitesform = [];
749         if (is_array($sites)) {
750                 foreach ($sites as $id => $s) {
751                         $sitesform[] = [
752                                 'sitename' => ["sitename[$id]", "Site name", $s['sitename'], ""],
753                                 'apiurl' => ["apiurl[$id]", "Api url", $s['apiurl'], L10n::t("Base API Path \x28remember the trailing /\x29")],
754                                 'secret' => ["secret[$id]", "Secret", $s['consumersecret'], ""],
755                                 'key' => ["key[$id]", "Key", $s['consumerkey'], ""],
756                                 //'applicationname' => Array("applicationname[$id]", "Application name", $s['applicationname'], ""),
757                                 'delete' => ["delete[$id]", "Delete", False, "Check to delete this preset"],
758                         ];
759                 }
760         }
761         /* empty form to add new site */
762         $id++;
763         $sitesform[] = [
764                 'sitename' => ["sitename[$id]", L10n::t("Site name"), "", ""],
765                 'apiurl' => ["apiurl[$id]", "Api url", "", L10n::t("Base API Path \x28remember the trailing /\x29")],
766                 'secret' => ["secret[$id]", L10n::t("Consumer Secret"), "", ""],
767                 'key' => ["key[$id]", L10n::t("Consumer Key"), "", ""],
768                 //'applicationname' => Array("applicationname[$id]", L10n::t("Application name"), "", ""),
769         ];
770
771         $t = get_markup_template("admin.tpl", "addon/statusnet/");
772         $o = replace_macros($t, [
773                 '$submit' => L10n::t('Save Settings'),
774                 '$sites' => $sitesform,
775         ]);
776 }
777
778 function statusnet_prepare_body(App $a, &$b)
779 {
780         if ($b["item"]["network"] != NETWORK_STATUSNET) {
781                 return;
782         }
783
784         if ($b["preview"]) {
785                 $max_char = PConfig::get(local_user(), 'statusnet', 'max_char');
786                 if (intval($max_char) == 0) {
787                         $max_char = 140;
788                 }
789
790                 $item = $b["item"];
791                 $item["plink"] = $a->get_baseurl() . "/display/" . $a->user["nickname"] . "/" . $item["parent"];
792
793                 $r = q("SELECT `item`.`author-link`, `item`.`uri`, `contact`.`nick` AS contact_nick
794                         FROM `item` INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
795                         WHERE `item`.`uri` = '%s' AND `item`.`uid` = %d LIMIT 1",
796                         dbesc($item["thr-parent"]),
797                         intval(local_user()));
798
799                 if (count($r)) {
800                         $orig_post = $r[0];
801                         //$nickname = "@[url=".$orig_post["author-link"]."]".$orig_post["contact_nick"]."[/url]";
802                         //$nicknameplain = "@".$orig_post["contact_nick"];
803
804                         $nick = preg_replace("=https?://(.*)/(.*)=ism", "$2", $orig_post["author-link"]);
805
806                         $nickname = "@[url=" . $orig_post["author-link"] . "]" . $nick . "[/url]";
807                         $nicknameplain = "@" . $nick;
808
809                         if ((strpos($item["body"], $nickname) === false) && (strpos($item["body"], $nicknameplain) === false)) {
810                                 $item["body"] = $nickname . " " . $item["body"];
811                         }
812                 }
813
814                 $msgarr = BBCode::toPlaintext($item, $max_char, true, 7);
815                 $msg = $msgarr["text"];
816
817                 if (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
818                         $msg .= " " . $msgarr["url"];
819                 }
820
821                 if (isset($msgarr["image"])) {
822                         $msg .= " " . $msgarr["image"];
823                 }
824
825                 $b['html'] = nl2br(htmlspecialchars($msg));
826         }
827 }
828
829 function statusnet_cron(App $a, $b)
830 {
831         $last = Config::get('statusnet', 'last_poll');
832
833         $poll_interval = intval(Config::get('statusnet', 'poll_interval'));
834         if (!$poll_interval) {
835                 $poll_interval = STATUSNET_DEFAULT_POLL_INTERVAL;
836         }
837
838         if ($last) {
839                 $next = $last + ($poll_interval * 60);
840                 if ($next > time()) {
841                         logger('statusnet: poll intervall not reached');
842                         return;
843                 }
844         }
845         logger('statusnet: cron_start');
846
847         $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'statusnet' AND `k` = 'mirror_posts' AND `v` = '1' ORDER BY RAND() ");
848         if (count($r)) {
849                 foreach ($r as $rr) {
850                         logger('statusnet: fetching for user ' . $rr['uid']);
851                         statusnet_fetchtimeline($a, $rr['uid']);
852                 }
853         }
854
855         $abandon_days = intval(Config::get('system', 'account_abandon_days'));
856         if ($abandon_days < 1) {
857                 $abandon_days = 0;
858         }
859
860         $abandon_limit = date("Y-m-d H:i:s", time() - $abandon_days * 86400);
861
862         $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'statusnet' AND `k` = 'import' AND `v` ORDER BY RAND()");
863         if (count($r)) {
864                 foreach ($r as $rr) {
865                         if ($abandon_days != 0) {
866                                 $user = q("SELECT `login_date` FROM `user` WHERE uid=%d AND `login_date` >= '%s'", $rr['uid'], $abandon_limit);
867                                 if (!count($user)) {
868                                         logger('abandoned account: timeline from user ' . $rr['uid'] . ' will not be imported');
869                                         continue;
870                                 }
871                         }
872
873                         logger('statusnet: importing timeline from user ' . $rr['uid']);
874                         statusnet_fetchhometimeline($a, $rr["uid"], $rr["v"]);
875                 }
876         }
877
878         logger('statusnet: cron_end');
879
880         Config::set('statusnet', 'last_poll', time());
881 }
882
883 function statusnet_fetchtimeline(App $a, $uid)
884 {
885         $ckey    = PConfig::get($uid, 'statusnet', 'consumerkey');
886         $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
887         $api     = PConfig::get($uid, 'statusnet', 'baseapi');
888         $otoken  = PConfig::get($uid, 'statusnet', 'oauthtoken');
889         $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
890         $lastid  = PConfig::get($uid, 'statusnet', 'lastid');
891
892         require_once 'mod/item.php';
893         require_once 'include/items.php';
894
895         //  get the application name for the SN app
896         //  1st try personal config, then system config and fallback to the
897         //  hostname of the node if neither one is set.
898         $application_name = PConfig::get($uid, 'statusnet', 'application_name');
899         if ($application_name == "") {
900                 $application_name = Config::get('statusnet', 'application_name');
901         }
902         if ($application_name == "") {
903                 $application_name = $a->get_hostname();
904         }
905
906         $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
907
908         $parameters = ["exclude_replies" => true, "trim_user" => true, "contributor_details" => false, "include_rts" => false];
909
910         $first_time = ($lastid == "");
911
912         if ($lastid <> "") {
913                 $parameters["since_id"] = $lastid;
914         }
915
916         $items = $connection->get('statuses/user_timeline', $parameters);
917
918         if (!is_array($items)) {
919                 return;
920         }
921
922         $posts = array_reverse($items);
923
924         if (count($posts)) {
925                 foreach ($posts as $post) {
926                         if ($post->id > $lastid)
927                                 $lastid = $post->id;
928
929                         if ($first_time) {
930                                 continue;
931                         }
932
933                         if ($post->source == "activity") {
934                                 continue;
935                         }
936
937                         if (is_object($post->retweeted_status)) {
938                                 continue;
939                         }
940
941                         if ($post->in_reply_to_status_id != "") {
942                                 continue;
943                         }
944
945                         if (!stristr($post->source, $application_name)) {
946                                 $_SESSION["authenticated"] = true;
947                                 $_SESSION["uid"] = $uid;
948
949                                 unset($_REQUEST);
950                                 $_REQUEST["type"] = "wall";
951                                 $_REQUEST["api_source"] = true;
952                                 $_REQUEST["profile_uid"] = $uid;
953                                 //$_REQUEST["source"] = "StatusNet";
954                                 $_REQUEST["source"] = $post->source;
955                                 $_REQUEST["extid"] = NETWORK_STATUSNET;
956
957                                 if (isset($post->id)) {
958                                         $_REQUEST['message_id'] = item_new_uri($a->get_hostname(), $uid, NETWORK_STATUSNET . ":" . $post->id);
959                                 }
960
961                                 //$_REQUEST["date"] = $post->created_at;
962
963                                 $_REQUEST["title"] = "";
964
965                                 $_REQUEST["body"] = add_page_info_to_body($post->text, true);
966                                 if (is_string($post->place->name)) {
967                                         $_REQUEST["location"] = $post->place->name;
968                                 }
969
970                                 if (is_string($post->place->full_name)) {
971                                         $_REQUEST["location"] = $post->place->full_name;
972                                 }
973
974                                 if (is_array($post->geo->coordinates)) {
975                                         $_REQUEST["coord"] = $post->geo->coordinates[0] . " " . $post->geo->coordinates[1];
976                                 }
977
978                                 if (is_array($post->coordinates->coordinates)) {
979                                         $_REQUEST["coord"] = $post->coordinates->coordinates[1] . " " . $post->coordinates->coordinates[0];
980                                 }
981
982                                 //print_r($_REQUEST);
983                                 if ($_REQUEST["body"] != "") {
984                                         logger('statusnet: posting for user ' . $uid);
985
986                                         item_post($a);
987                                 }
988                         }
989                 }
990         }
991         PConfig::set($uid, 'statusnet', 'lastid', $lastid);
992 }
993
994 function statusnet_address($contact)
995 {
996         $hostname = normalise_link($contact->statusnet_profile_url);
997         $nickname = $contact->screen_name;
998
999         $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $contact->statusnet_profile_url);
1000
1001         $address = $contact->screen_name . "@" . $hostname;
1002
1003         return $address;
1004 }
1005
1006 function statusnet_fetch_contact($uid, $contact, $create_user)
1007 {
1008         if ($contact->statusnet_profile_url == "") {
1009                 return -1;
1010         }
1011
1012         GContact::update(["url" => $contact->statusnet_profile_url,
1013                 "network" => NETWORK_STATUSNET, "photo" => $contact->profile_image_url,
1014                 "name" => $contact->name, "nick" => $contact->screen_name,
1015                 "location" => $contact->location, "about" => $contact->description,
1016                 "addr" => statusnet_address($contact), "generation" => 3]);
1017
1018         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' AND `network` = '%s'LIMIT 1", intval($uid), dbesc(normalise_link($contact->statusnet_profile_url)), dbesc(NETWORK_STATUSNET));
1019
1020         if (!count($r) && !$create_user) {
1021                 return 0;
1022         }
1023
1024         if (count($r) && ($r[0]["readonly"] || $r[0]["blocked"])) {
1025                 logger("statusnet_fetch_contact: Contact '" . $r[0]["nick"] . "' is blocked or readonly.", LOGGER_DEBUG);
1026                 return -1;
1027         }
1028
1029         if (!count($r)) {
1030                 // create contact record
1031                 q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`,
1032                                         `name`, `nick`, `photo`, `network`, `rel`, `priority`,
1033                                         `location`, `about`, `writable`, `blocked`, `readonly`, `pending` )
1034                                         VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', %d, 0, 0, 0 ) ",
1035                         intval($uid),
1036                         dbesc(datetime_convert()),
1037                         dbesc($contact->statusnet_profile_url),
1038                         dbesc(normalise_link($contact->statusnet_profile_url)),
1039                         dbesc(statusnet_address($contact)),
1040                         dbesc(normalise_link($contact->statusnet_profile_url)),
1041                         dbesc(''),
1042                         dbesc(''),
1043                         dbesc($contact->name),
1044                         dbesc($contact->screen_name),
1045                         dbesc($contact->profile_image_url),
1046                         dbesc(NETWORK_STATUSNET),
1047                         intval(CONTACT_IS_FRIEND),
1048                         intval(1),
1049                         dbesc($contact->location),
1050                         dbesc($contact->description),
1051                         intval(1)
1052                 );
1053
1054                 $r = q("SELECT * FROM `contact` WHERE `alias` = '%s' AND `uid` = %d AND `network` = '%s' LIMIT 1",
1055                         dbesc($contact->statusnet_profile_url),
1056                         intval($uid),
1057                         dbesc(NETWORK_STATUSNET));
1058
1059                 if (!count($r)) {
1060                         return false;
1061                 }
1062
1063                 $contact_id = $r[0]['id'];
1064
1065                 Group::addMember(User::getDefaultGroup($uid), $contact_id);
1066
1067                 $photos = Photo::importProfilePhoto($contact->profile_image_url, $uid, $contact_id);
1068
1069                 q("UPDATE `contact` SET `photo` = '%s',
1070                                         `thumb` = '%s',
1071                                         `micro` = '%s',
1072                                         `avatar-date` = '%s'
1073                                 WHERE `id` = %d",
1074                         dbesc($photos[0]),
1075                         dbesc($photos[1]),
1076                         dbesc($photos[2]),
1077                         dbesc(datetime_convert()),
1078                         intval($contact_id)
1079                 );
1080         } else {
1081                 // update profile photos once every two weeks as we have no notification of when they change.
1082                 //$update_photo = (($r[0]['avatar-date'] < datetime_convert('','','now -2 days')) ? true : false);
1083                 $update_photo = ($r[0]['avatar-date'] < datetime_convert('', '', 'now -12 hours'));
1084
1085                 // check that we have all the photos, this has been known to fail on occasion
1086                 if ((!$r[0]['photo']) || (!$r[0]['thumb']) || (!$r[0]['micro']) || ($update_photo)) {
1087                         logger("statusnet_fetch_contact: Updating contact " . $contact->screen_name, LOGGER_DEBUG);
1088
1089                         $photos = Photo::importProfilePhoto($contact->profile_image_url, $uid, $r[0]['id']);
1090
1091                         q("UPDATE `contact` SET `photo` = '%s',
1092                                                 `thumb` = '%s',
1093                                                 `micro` = '%s',
1094                                                 `name-date` = '%s',
1095                                                 `uri-date` = '%s',
1096                                                 `avatar-date` = '%s',
1097                                                 `url` = '%s',
1098                                                 `nurl` = '%s',
1099                                                 `addr` = '%s',
1100                                                 `name` = '%s',
1101                                                 `nick` = '%s',
1102                                                 `location` = '%s',
1103                                                 `about` = '%s'
1104                                         WHERE `id` = %d",
1105                                 dbesc($photos[0]),
1106                                 dbesc($photos[1]),
1107                                 dbesc($photos[2]),
1108                                 dbesc(datetime_convert()),
1109                                 dbesc(datetime_convert()),
1110                                 dbesc(datetime_convert()),
1111                                 dbesc($contact->statusnet_profile_url),
1112                                 dbesc(normalise_link($contact->statusnet_profile_url)),
1113                                 dbesc(statusnet_address($contact)),
1114                                 dbesc($contact->name),
1115                                 dbesc($contact->screen_name),
1116                                 dbesc($contact->location),
1117                                 dbesc($contact->description),
1118                                 intval($r[0]['id'])
1119                         );
1120                 }
1121         }
1122
1123         return $r[0]["id"];
1124 }
1125
1126 function statusnet_fetchuser(App $a, $uid, $screen_name = "", $user_id = "")
1127 {
1128         $ckey    = PConfig::get($uid, 'statusnet', 'consumerkey');
1129         $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1130         $api     = PConfig::get($uid, 'statusnet', 'baseapi');
1131         $otoken  = PConfig::get($uid, 'statusnet', 'oauthtoken');
1132         $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1133
1134         require_once "addon/statusnet/codebird.php";
1135
1136         $cb = \Codebird\Codebird::getInstance();
1137         $cb->setConsumerKey($ckey, $csecret);
1138         $cb->setToken($otoken, $osecret);
1139
1140         $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1141                 intval($uid));
1142
1143         if (count($r)) {
1144                 $self = $r[0];
1145         } else {
1146                 return;
1147         }
1148
1149         $parameters = [];
1150
1151         if ($screen_name != "") {
1152                 $parameters["screen_name"] = $screen_name;
1153         }
1154
1155         if ($user_id != "") {
1156                 $parameters["user_id"] = $user_id;
1157         }
1158
1159         // Fetching user data
1160         $user = $cb->users_show($parameters);
1161
1162         if (!is_object($user)) {
1163                 return;
1164         }
1165
1166         $contact_id = statusnet_fetch_contact($uid, $user, true);
1167
1168         return $contact_id;
1169 }
1170
1171 function statusnet_createpost(App $a, $uid, $post, $self, $create_user, $only_existing_contact)
1172 {
1173         require_once "include/html2bbcode.php";
1174
1175         logger("statusnet_createpost: start", LOGGER_DEBUG);
1176
1177         $api = PConfig::get($uid, 'statusnet', 'baseapi');
1178         $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $api);
1179
1180         $postarray = [];
1181         $postarray['network'] = NETWORK_STATUSNET;
1182         $postarray['gravity'] = 0;
1183         $postarray['uid'] = $uid;
1184         $postarray['wall'] = 0;
1185
1186         if (is_object($post->retweeted_status)) {
1187                 $content = $post->retweeted_status;
1188                 statusnet_fetch_contact($uid, $content->user, false);
1189         } else {
1190                 $content = $post;
1191         }
1192
1193         $postarray['uri'] = $hostname . "::" . $content->id;
1194
1195         $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
1196                         dbesc($postarray['uri']),
1197                         intval($uid)
1198         );
1199
1200         if (count($r)) {
1201                 return [];
1202         }
1203
1204         $contactid = 0;
1205
1206         if ($content->in_reply_to_status_id != "") {
1207
1208                 $parent = $hostname . "::" . $content->in_reply_to_status_id;
1209
1210                 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1211                                 dbesc($parent),
1212                                 intval($uid)
1213                 );
1214                 if (count($r)) {
1215                         $postarray['thr-parent'] = $r[0]["uri"];
1216                         $postarray['parent-uri'] = $r[0]["parent-uri"];
1217                         $postarray['parent'] = $r[0]["parent"];
1218                         $postarray['object-type'] = ACTIVITY_OBJ_COMMENT;
1219                 } else {
1220                         $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
1221                                         dbesc($parent),
1222                                         intval($uid)
1223                         );
1224                         if (count($r)) {
1225                                 $postarray['thr-parent'] = $r[0]['uri'];
1226                                 $postarray['parent-uri'] = $r[0]['parent-uri'];
1227                                 $postarray['parent'] = $r[0]['parent'];
1228                                 $postarray['object-type'] = ACTIVITY_OBJ_COMMENT;
1229                         } else {
1230                                 $postarray['thr-parent'] = $postarray['uri'];
1231                                 $postarray['parent-uri'] = $postarray['uri'];
1232                                 $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
1233                         }
1234                 }
1235
1236                 // Is it me?
1237                 $own_url = PConfig::get($uid, 'statusnet', 'own_url');
1238
1239                 if ($content->user->id == $own_url) {
1240                         $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1241                                 intval($uid));
1242
1243                         if (count($r)) {
1244                                 $contactid = $r[0]["id"];
1245
1246                                 $postarray['owner-name'] = $r[0]["name"];
1247                                 $postarray['owner-link'] = $r[0]["url"];
1248                                 $postarray['owner-avatar'] = $r[0]["photo"];
1249                         } else {
1250                                 return [];
1251                         }
1252                 }
1253                 // Don't create accounts of people who just comment something
1254                 $create_user = false;
1255         } else {
1256                 $postarray['parent-uri'] = $postarray['uri'];
1257                 $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
1258         }
1259
1260         if ($contactid == 0) {
1261                 $contactid = statusnet_fetch_contact($uid, $post->user, $create_user);
1262                 $postarray['owner-name'] = $post->user->name;
1263                 $postarray['owner-link'] = $post->user->statusnet_profile_url;
1264                 $postarray['owner-avatar'] = $post->user->profile_image_url;
1265         }
1266         if (($contactid == 0) && !$only_existing_contact) {
1267                 $contactid = $self['id'];
1268         } elseif ($contactid <= 0) {
1269                 return [];
1270         }
1271
1272         $postarray['contact-id'] = $contactid;
1273
1274         $postarray['verb'] = ACTIVITY_POST;
1275
1276         $postarray['author-name'] = $content->user->name;
1277         $postarray['author-link'] = $content->user->statusnet_profile_url;
1278         $postarray['author-avatar'] = $content->user->profile_image_url;
1279
1280         // To-Do: Maybe unreliable? Can the api be entered without trailing "/"?
1281         $hostname = str_replace("/api/", "/notice/", PConfig::get($uid, 'statusnet', 'baseapi'));
1282
1283         $postarray['plink'] = $hostname . $content->id;
1284         $postarray['app'] = strip_tags($content->source);
1285
1286         if ($content->user->protected) {
1287                 $postarray['private'] = 1;
1288                 $postarray['allow_cid'] = '<' . $self['id'] . '>';
1289         }
1290
1291         $postarray['body'] = html2bbcode($content->statusnet_html);
1292
1293         $converted = statusnet_convertmsg($a, $postarray['body'], false);
1294         $postarray['body'] = $converted["body"];
1295         $postarray['tag'] = $converted["tags"];
1296
1297         $postarray['created'] = datetime_convert('UTC', 'UTC', $content->created_at);
1298         $postarray['edited'] = datetime_convert('UTC', 'UTC', $content->created_at);
1299
1300         if (is_string($content->place->name)) {
1301                 $postarray["location"] = $content->place->name;
1302         }
1303
1304         if (is_string($content->place->full_name)) {
1305                 $postarray["location"] = $content->place->full_name;
1306         }
1307
1308         if (is_array($content->geo->coordinates)) {
1309                 $postarray["coord"] = $content->geo->coordinates[0] . " " . $content->geo->coordinates[1];
1310         }
1311
1312         if (is_array($content->coordinates->coordinates)) {
1313                 $postarray["coord"] = $content->coordinates->coordinates[1] . " " . $content->coordinates->coordinates[0];
1314         }
1315
1316         logger("statusnet_createpost: end", LOGGER_DEBUG);
1317
1318         return $postarray;
1319 }
1320
1321 function statusnet_checknotification(App $a, $uid, $own_url, $top_item, $postarray)
1322 {
1323         // This function necer worked and need cleanup
1324         $user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1",
1325                         intval($uid)
1326         );
1327
1328         if (!count($user)) {
1329                 return;
1330         }
1331
1332         // Is it me?
1333         if (link_compare($user[0]["url"], $postarray['author-link'])) {
1334                 return;
1335         }
1336
1337         $own_user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1338                         intval($uid),
1339                         dbesc($own_url)
1340         );
1341
1342         if (!count($own_user)) {
1343                 return;
1344         }
1345
1346         // Is it me from GNU Social?
1347         if (link_compare($own_user[0]["url"], $postarray['author-link'])) {
1348                 return;
1349         }
1350
1351         $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
1352                         dbesc($postarray['parent-uri']),
1353                         intval($uid)
1354         );
1355
1356         if (count($myconv)) {
1357                 foreach ($myconv as $conv) {
1358                         // now if we find a match, it means we're in this conversation
1359                         if (!link_compare($conv['author-link'], $user[0]["url"]) && !link_compare($conv['author-link'], $own_user[0]["url"])) {
1360                                 continue;
1361                         }
1362
1363                         require_once 'include/enotify.php';
1364
1365                         $conv_parent = $conv['parent'];
1366
1367                         notification([
1368                                 'type' => NOTIFY_COMMENT,
1369                                 'notify_flags' => $user[0]['notify-flags'],
1370                                 'language' => $user[0]['language'],
1371                                 'to_name' => $user[0]['username'],
1372                                 'to_email' => $user[0]['email'],
1373                                 'uid' => $user[0]['uid'],
1374                                 'item' => $postarray,
1375                                 'link' => $a->get_baseurl() . '/display/' . urlencode(Item::getGuidById($top_item)),
1376                                 'source_name' => $postarray['author-name'],
1377                                 'source_link' => $postarray['author-link'],
1378                                 'source_photo' => $postarray['author-avatar'],
1379                                 'verb' => ACTIVITY_POST,
1380                                 'otype' => 'item',
1381                                 'parent' => $conv_parent,
1382                         ]);
1383
1384                         // only send one notification
1385                         break;
1386                 }
1387         }
1388 }
1389
1390 function statusnet_fetchhometimeline(App $a, $uid, $mode = 1)
1391 {
1392         $conversations = [];
1393
1394         $ckey    = PConfig::get($uid, 'statusnet', 'consumerkey');
1395         $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1396         $api     = PConfig::get($uid, 'statusnet', 'baseapi');
1397         $otoken  = PConfig::get($uid, 'statusnet', 'oauthtoken');
1398         $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1399         $create_user = PConfig::get($uid, 'statusnet', 'create_user');
1400
1401         // "create_user" is deactivated, since currently you cannot add users manually by now
1402         $create_user = true;
1403
1404         logger("statusnet_fetchhometimeline: Fetching for user " . $uid, LOGGER_DEBUG);
1405
1406         require_once 'library/twitteroauth.php';
1407         require_once 'include/items.php';
1408
1409         $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1410
1411         $own_contact = statusnet_fetch_own_contact($a, $uid);
1412
1413         $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1414                 intval($own_contact),
1415                 intval($uid));
1416
1417         if (count($r)) {
1418                 $nick = $r[0]["nick"];
1419         } else {
1420                 logger("statusnet_fetchhometimeline: Own GNU Social contact not found for user " . $uid, LOGGER_DEBUG);
1421                 return;
1422         }
1423
1424         $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1425                 intval($uid));
1426
1427         if (count($r)) {
1428                 $self = $r[0];
1429         } else {
1430                 logger("statusnet_fetchhometimeline: Own contact not found for user " . $uid, LOGGER_DEBUG);
1431                 return;
1432         }
1433
1434         $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1435                 intval($uid));
1436         if (!count($u)) {
1437                 logger("statusnet_fetchhometimeline: Own user not found for user " . $uid, LOGGER_DEBUG);
1438                 return;
1439         }
1440
1441         $parameters = ["exclude_replies" => false, "trim_user" => false, "contributor_details" => true, "include_rts" => true];
1442         //$parameters["count"] = 200;
1443
1444         if ($mode == 1) {
1445                 // Fetching timeline
1446                 $lastid = PConfig::get($uid, 'statusnet', 'lasthometimelineid');
1447                 //$lastid = 1;
1448
1449                 $first_time = ($lastid == "");
1450
1451                 if ($lastid != "") {
1452                         $parameters["since_id"] = $lastid;
1453                 }
1454
1455                 $items = $connection->get('statuses/home_timeline', $parameters);
1456
1457                 if (!is_array($items)) {
1458                         if (is_object($items) && isset($items->error)) {
1459                                 $errormsg = $items->error;
1460                         } elseif (is_object($items)) {
1461                                 $errormsg = print_r($items, true);
1462                         } elseif (is_string($items) || is_float($items) || is_int($items)) {
1463                                 $errormsg = $items;
1464                         } else {
1465                                 $errormsg = "Unknown error";
1466                         }
1467
1468                         logger("statusnet_fetchhometimeline: Error fetching home timeline: " . $errormsg, LOGGER_DEBUG);
1469                         return;
1470                 }
1471
1472                 $posts = array_reverse($items);
1473
1474                 logger("statusnet_fetchhometimeline: Fetching timeline for user " . $uid . " " . sizeof($posts) . " items", LOGGER_DEBUG);
1475
1476                 if (count($posts)) {
1477                         foreach ($posts as $post) {
1478
1479                                 if ($post->id > $lastid) {
1480                                         $lastid = $post->id;
1481                                 }
1482
1483                                 if ($first_time) {
1484                                         continue;
1485                                 }
1486
1487                                 if (isset($post->statusnet_conversation_id)) {
1488                                         if (!isset($conversations[$post->statusnet_conversation_id])) {
1489                                                 statusnet_complete_conversation($a, $uid, $self, $create_user, $nick, $post->statusnet_conversation_id);
1490                                                 $conversations[$post->statusnet_conversation_id] = $post->statusnet_conversation_id;
1491                                         }
1492                                 } else {
1493                                         $postarray = statusnet_createpost($a, $uid, $post, $self, $create_user, true);
1494
1495                                         if (trim($postarray['body']) == "") {
1496                                                 continue;
1497                                         }
1498
1499                                         $item = Item::insert($postarray);
1500                                         $postarray["id"] = $item;
1501
1502                                         logger('statusnet_fetchhometimeline: User ' . $self["nick"] . ' posted home timeline item ' . $item);
1503
1504                                         if ($item && !function_exists("check_item_notification")) {
1505                                                 statusnet_checknotification($a, $uid, $nick, $item, $postarray);
1506                                         }
1507                                 }
1508                         }
1509                 }
1510                 PConfig::set($uid, 'statusnet', 'lasthometimelineid', $lastid);
1511         }
1512
1513         // Fetching mentions
1514         $lastid = PConfig::get($uid, 'statusnet', 'lastmentionid');
1515         $first_time = ($lastid == "");
1516
1517         if ($lastid != "") {
1518                 $parameters["since_id"] = $lastid;
1519         }
1520
1521         $items = $connection->get('statuses/mentions_timeline', $parameters);
1522
1523         if (!is_array($items)) {
1524                 logger("statusnet_fetchhometimeline: Error fetching mentions: " . print_r($items, true), LOGGER_DEBUG);
1525                 return;
1526         }
1527
1528         $posts = array_reverse($items);
1529
1530         logger("statusnet_fetchhometimeline: Fetching mentions for user " . $uid . " " . sizeof($posts) . " items", LOGGER_DEBUG);
1531
1532         if (count($posts)) {
1533                 foreach ($posts as $post) {
1534                         if ($post->id > $lastid) {
1535                                 $lastid = $post->id;
1536                         }
1537
1538                         if ($first_time) {
1539                                 continue;
1540                         }
1541
1542                         $postarray = statusnet_createpost($a, $uid, $post, $self, false, false);
1543
1544                         if (isset($post->statusnet_conversation_id)) {
1545                                 if (!isset($conversations[$post->statusnet_conversation_id])) {
1546                                         statusnet_complete_conversation($a, $uid, $self, $create_user, $nick, $post->statusnet_conversation_id);
1547                                         $conversations[$post->statusnet_conversation_id] = $post->statusnet_conversation_id;
1548                                 }
1549                         } else {
1550                                 if (trim($postarray['body']) == "") {
1551                                         continue;
1552                                 }
1553
1554                                 $item = Item::insert($postarray);
1555                                 $postarray["id"] = $item;
1556
1557                                 logger('statusnet_fetchhometimeline: User ' . $self["nick"] . ' posted mention timeline item ' . $item);
1558
1559                                 if ($item && function_exists("check_item_notification")) {
1560                                         check_item_notification($item, $uid, NOTIFY_TAGSELF);
1561                                 }
1562                         }
1563
1564                         $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1565                                 dbesc($postarray['uri']),
1566                                 intval($uid)
1567                         );
1568                         if (count($r)) {
1569                                 $item = $r[0]['id'];
1570                                 $parent_id = $r[0]['parent'];
1571                         }
1572
1573                         if (($item != 0) && !function_exists("check_item_notification")) {
1574                                 require_once 'include/enotify.php';
1575                                 notification([
1576                                         'type'         => NOTIFY_TAGSELF,
1577                                         'notify_flags' => $u[0]['notify-flags'],
1578                                         'language'     => $u[0]['language'],
1579                                         'to_name'      => $u[0]['username'],
1580                                         'to_email'     => $u[0]['email'],
1581                                         'uid'          => $u[0]['uid'],
1582                                         'item'         => $postarray,
1583                                         'link'         => $a->get_baseurl() . '/display/' . urlencode(Item::getGuidById($item)),
1584                                         'source_name'  => $postarray['author-name'],
1585                                         'source_link'  => $postarray['author-link'],
1586                                         'source_photo' => $postarray['author-avatar'],
1587                                         'verb'         => ACTIVITY_TAG,
1588                                         'otype'        => 'item',
1589                                         'parent'       => $parent_id,
1590                                 ]);
1591                         }
1592                 }
1593         }
1594
1595         PConfig::set($uid, 'statusnet', 'lastmentionid', $lastid);
1596 }
1597
1598 function statusnet_complete_conversation(App $a, $uid, $self, $create_user, $nick, $conversation)
1599 {
1600         $ckey    = PConfig::get($uid, 'statusnet', 'consumerkey');
1601         $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1602         $api     = PConfig::get($uid, 'statusnet', 'baseapi');
1603         $otoken  = PConfig::get($uid, 'statusnet', 'oauthtoken');
1604         $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1605         $own_url = PConfig::get($uid, 'statusnet', 'own_url');
1606
1607         require_once 'library/twitteroauth.php';
1608
1609         $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1610
1611         $parameters["count"] = 200;
1612
1613         $items = $connection->get('statusnet/conversation/' . $conversation, $parameters);
1614         if (is_array($items)) {
1615                 $posts = array_reverse($items);
1616
1617                 foreach ($posts as $post) {
1618                         $postarray = statusnet_createpost($a, $uid, $post, $self, false, false);
1619
1620                         if (trim($postarray['body']) == "") {
1621                                 continue;
1622                         }
1623
1624                         $item = Item::insert($postarray);
1625                         $postarray["id"] = $item;
1626
1627                         logger('statusnet_complete_conversation: User ' . $self["nick"] . ' posted home timeline item ' . $item);
1628
1629                         if ($item && !function_exists("check_item_notification")) {
1630                                 statusnet_checknotification($a, $uid, $nick, $item, $postarray);
1631                         }
1632                 }
1633         }
1634 }
1635
1636 function statusnet_convertmsg(App $a, $body, $no_tags = false)
1637 {
1638         require_once "include/items.php";
1639
1640         $body = preg_replace("=\[url\=https?://([0-9]*).([0-9]*).([0-9]*).([0-9]*)/([0-9]*)\](.*?)\[\/url\]=ism", "$1.$2.$3.$4/$5", $body);
1641
1642         $URLSearchString = "^\[\]";
1643         $links = preg_match_all("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER);
1644
1645         $footer = "";
1646         $footerurl = "";
1647         $footerlink = "";
1648         $type = "";
1649
1650         if ($links) {
1651                 foreach ($matches AS $match) {
1652                         $search = "[url=" . $match[1] . "]" . $match[2] . "[/url]";
1653
1654                         logger("statusnet_convertmsg: expanding url " . $match[1], LOGGER_DEBUG);
1655
1656                         $expanded_url = Network::finalUrl($match[1]);
1657
1658                         logger("statusnet_convertmsg: fetching data for " . $expanded_url, LOGGER_DEBUG);
1659
1660                         $oembed_data = OEmbed::fetchURL($expanded_url, true);
1661
1662                         logger("statusnet_convertmsg: fetching data: done", LOGGER_DEBUG);
1663
1664                         if ($type == "") {
1665                                 $type = $oembed_data->type;
1666                         }
1667
1668                         if ($oembed_data->type == "video") {
1669                                 //$body = str_replace($search, "[video]".$expanded_url."[/video]", $body);
1670                                 $type = $oembed_data->type;
1671                                 $footerurl = $expanded_url;
1672                                 $footerlink = "[url=" . $expanded_url . "]" . $expanded_url . "[/url]";
1673
1674                                 $body = str_replace($search, $footerlink, $body);
1675                         } elseif (($oembed_data->type == "photo") && isset($oembed_data->url) && !$dontincludemedia) {
1676                                 $body = str_replace($search, "[url=" . $expanded_url . "][img]" . $oembed_data->url . "[/img][/url]", $body);
1677                         } elseif ($oembed_data->type != "link") {
1678                                 $body = str_replace($search, "[url=" . $expanded_url . "]" . $expanded_url . "[/url]", $body);
1679                         } else {
1680                                 $img_str = Network::fetchUrl($expanded_url, true, $redirects, 4);
1681
1682                                 $tempfile = tempnam(get_temppath(), "cache");
1683                                 file_put_contents($tempfile, $img_str);
1684                                 $mime = image_type_to_mime_type(exif_imagetype($tempfile));
1685                                 unlink($tempfile);
1686
1687                                 if (substr($mime, 0, 6) == "image/") {
1688                                         $type = "photo";
1689                                         $body = str_replace($search, "[img]" . $expanded_url . "[/img]", $body);
1690                                 } else {
1691                                         $type = $oembed_data->type;
1692                                         $footerurl = $expanded_url;
1693                                         $footerlink = "[url=" . $expanded_url . "]" . $expanded_url . "[/url]";
1694
1695                                         $body = str_replace($search, $footerlink, $body);
1696                                 }
1697                         }
1698                 }
1699
1700                 if ($footerurl != "") {
1701                         $footer = add_page_info($footerurl);
1702                 }
1703
1704                 if (($footerlink != "") && (trim($footer) != "")) {
1705                         $removedlink = trim(str_replace($footerlink, "", $body));
1706
1707                         if (($removedlink == "") || strstr($body, $removedlink)) {
1708                                 $body = $removedlink;
1709                         }
1710
1711                         $body .= $footer;
1712                 }
1713         }
1714
1715         if ($no_tags) {
1716                 return ["body" => $body, "tags" => ""];
1717         }
1718
1719         $str_tags = '';
1720
1721         $cnt = preg_match_all("/([!#@])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER);
1722         if ($cnt) {
1723                 foreach ($matches as $mtch) {
1724                         if (strlen($str_tags)) {
1725                                 $str_tags .= ',';
1726                         }
1727
1728                         if ($mtch[1] == "#") {
1729                                 // Replacing the hash tags that are directed to the GNU Social server with internal links
1730                                 $snhash = "#[url=" . $mtch[2] . "]" . $mtch[3] . "[/url]";
1731                                 $frdchash = '#[url=' . $a->get_baseurl() . '/search?tag=' . rawurlencode($mtch[3]) . ']' . $mtch[3] . '[/url]';
1732                                 $body = str_replace($snhash, $frdchash, $body);
1733
1734                                 $str_tags .= $frdchash;
1735                         } else {
1736                                 $str_tags .= "@[url=" . $mtch[2] . "]" . $mtch[3] . "[/url]";
1737                         }
1738                         // To-Do:
1739                         // There is a problem with links with to GNU Social groups, so these links are stored with "@" like friendica groups
1740                         //$str_tags .= $mtch[1]."[url=".$mtch[2]."]".$mtch[3]."[/url]";
1741                 }
1742         }
1743
1744         return ["body" => $body, "tags" => $str_tags];
1745 }
1746
1747 function statusnet_fetch_own_contact(App $a, $uid)
1748 {
1749         $ckey    = PConfig::get($uid, 'statusnet', 'consumerkey');
1750         $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1751         $api     = PConfig::get($uid, 'statusnet', 'baseapi');
1752         $otoken  = PConfig::get($uid, 'statusnet', 'oauthtoken');
1753         $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1754         $own_url = PConfig::get($uid, 'statusnet', 'own_url');
1755
1756         $contact_id = 0;
1757
1758         if ($own_url == "") {
1759                 require_once 'library/twitteroauth.php';
1760
1761                 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1762
1763                 // Fetching user data
1764                 $user = $connection->get('account/verify_credentials');
1765
1766                 PConfig::set($uid, 'statusnet', 'own_url', normalise_link($user->statusnet_profile_url));
1767
1768                 $contact_id = statusnet_fetch_contact($uid, $user, true);
1769         } else {
1770                 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1771                         intval($uid), dbesc($own_url));
1772                 if (count($r)) {
1773                         $contact_id = $r[0]["id"];
1774                 } else {
1775                         PConfig::delete($uid, 'statusnet', 'own_url');
1776                 }
1777         }
1778         return $contact_id;
1779 }
1780
1781 function statusnet_is_retweet(App $a, $uid, $body)
1782 {
1783         $body = trim($body);
1784
1785         // Skip if it isn't a pure repeated messages
1786         // Does it start with a share?
1787         if (strpos($body, "[share") > 0) {
1788                 return false;
1789         }
1790
1791         // Does it end with a share?
1792         if (strlen($body) > (strrpos($body, "[/share]") + 8)) {
1793                 return false;
1794         }
1795
1796         $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "$1", $body);
1797         // Skip if there is no shared message in there
1798         if ($body == $attributes) {
1799                 return false;
1800         }
1801
1802         $link = "";
1803         preg_match("/link='(.*?)'/ism", $attributes, $matches);
1804         if ($matches[1] != "") {
1805                 $link = $matches[1];
1806         }
1807
1808         preg_match('/link="(.*?)"/ism', $attributes, $matches);
1809         if ($matches[1] != "") {
1810                 $link = $matches[1];
1811         }
1812
1813         $ckey    = PConfig::get($uid, 'statusnet', 'consumerkey');
1814         $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1815         $api     = PConfig::get($uid, 'statusnet', 'baseapi');
1816         $otoken  = PConfig::get($uid, 'statusnet', 'oauthtoken');
1817         $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1818         $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $api);
1819
1820         $id = preg_replace("=https?://" . $hostname . "/notice/(.*)=ism", "$1", $link);
1821
1822         if ($id == $link) {
1823                 return false;
1824         }
1825
1826         logger('statusnet_is_retweet: Retweeting id ' . $id . ' for user ' . $uid, LOGGER_DEBUG);
1827
1828         $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1829
1830         $result = $connection->post('statuses/retweet/' . $id);
1831
1832         logger('statusnet_is_retweet: result ' . print_r($result, true), LOGGER_DEBUG);
1833
1834         return isset($result->id);
1835 }