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