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