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