Merge pull request #539 from MrPetovan/bug/4555-remove-active-users-feature
[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 Codebird\Codebird;
42 use CodebirdSN\CodebirdSN;
43 use Friendica\App;
44 use Friendica\Content\OEmbed;
45 use Friendica\Content\Text\BBCode;
46 use Friendica\Content\Text\Plaintext;
47 use Friendica\Core\Addon;
48 use Friendica\Core\Config;
49 use Friendica\Core\L10n;
50 use Friendica\Core\PConfig;
51 use Friendica\Model\GContact;
52 use Friendica\Model\Group;
53 use Friendica\Model\Item;
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 = BBCode::toPlaintext($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\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 = BBCode::toPlaintext($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\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         require_once "include/html2bbcode.php";
1072
1073         logger("statusnet_createpost: start", LOGGER_DEBUG);
1074
1075         $api = PConfig::get($uid, 'statusnet', 'baseapi');
1076         $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $api);
1077
1078         $postarray = [];
1079         $postarray['network'] = NETWORK_STATUSNET;
1080         $postarray['gravity'] = 0;
1081         $postarray['uid'] = $uid;
1082         $postarray['wall'] = 0;
1083
1084         if (is_object($post->retweeted_status)) {
1085                 $content = $post->retweeted_status;
1086                 statusnet_fetch_contact($uid, $content->user, false);
1087         } else {
1088                 $content = $post;
1089         }
1090
1091         $postarray['uri'] = $hostname . "::" . $content->id;
1092
1093         $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
1094                         dbesc($postarray['uri']),
1095                         intval($uid)
1096         );
1097
1098         if (count($r)) {
1099                 return [];
1100         }
1101
1102         $contactid = 0;
1103
1104         if ($content->in_reply_to_status_id != "") {
1105
1106                 $parent = $hostname . "::" . $content->in_reply_to_status_id;
1107
1108                 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1109                                 dbesc($parent),
1110                                 intval($uid)
1111                 );
1112                 if (count($r)) {
1113                         $postarray['thr-parent'] = $r[0]["uri"];
1114                         $postarray['parent-uri'] = $r[0]["parent-uri"];
1115                         $postarray['parent'] = $r[0]["parent"];
1116                         $postarray['object-type'] = ACTIVITY_OBJ_COMMENT;
1117                 } else {
1118                         $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
1119                                         dbesc($parent),
1120                                         intval($uid)
1121                         );
1122                         if (count($r)) {
1123                                 $postarray['thr-parent'] = $r[0]['uri'];
1124                                 $postarray['parent-uri'] = $r[0]['parent-uri'];
1125                                 $postarray['parent'] = $r[0]['parent'];
1126                                 $postarray['object-type'] = ACTIVITY_OBJ_COMMENT;
1127                         } else {
1128                                 $postarray['thr-parent'] = $postarray['uri'];
1129                                 $postarray['parent-uri'] = $postarray['uri'];
1130                                 $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
1131                         }
1132                 }
1133
1134                 // Is it me?
1135                 $own_url = PConfig::get($uid, 'statusnet', 'own_url');
1136
1137                 if ($content->user->id == $own_url) {
1138                         $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1139                                 intval($uid));
1140
1141                         if (count($r)) {
1142                                 $contactid = $r[0]["id"];
1143
1144                                 $postarray['owner-name'] = $r[0]["name"];
1145                                 $postarray['owner-link'] = $r[0]["url"];
1146                                 $postarray['owner-avatar'] = $r[0]["photo"];
1147                         } else {
1148                                 return [];
1149                         }
1150                 }
1151                 // Don't create accounts of people who just comment something
1152                 $create_user = false;
1153         } else {
1154                 $postarray['parent-uri'] = $postarray['uri'];
1155                 $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
1156         }
1157
1158         if ($contactid == 0) {
1159                 $contactid = statusnet_fetch_contact($uid, $post->user, $create_user);
1160                 $postarray['owner-name'] = $post->user->name;
1161                 $postarray['owner-link'] = $post->user->statusnet_profile_url;
1162                 $postarray['owner-avatar'] = $post->user->profile_image_url;
1163         }
1164         if (($contactid == 0) && !$only_existing_contact) {
1165                 $contactid = $self['id'];
1166         } elseif ($contactid <= 0) {
1167                 return [];
1168         }
1169
1170         $postarray['contact-id'] = $contactid;
1171
1172         $postarray['verb'] = ACTIVITY_POST;
1173
1174         $postarray['author-name'] = $content->user->name;
1175         $postarray['author-link'] = $content->user->statusnet_profile_url;
1176         $postarray['author-avatar'] = $content->user->profile_image_url;
1177
1178         // To-Do: Maybe unreliable? Can the api be entered without trailing "/"?
1179         $hostname = str_replace("/api/", "/notice/", PConfig::get($uid, 'statusnet', 'baseapi'));
1180
1181         $postarray['plink'] = $hostname . $content->id;
1182         $postarray['app'] = strip_tags($content->source);
1183
1184         if ($content->user->protected) {
1185                 $postarray['private'] = 1;
1186                 $postarray['allow_cid'] = '<' . $self['id'] . '>';
1187         }
1188
1189         $postarray['body'] = html2bbcode($content->statusnet_html);
1190
1191         $converted = statusnet_convertmsg($a, $postarray['body'], false);
1192         $postarray['body'] = $converted["body"];
1193         $postarray['tag'] = $converted["tags"];
1194
1195         $postarray['created'] = DateTimeFormat::utc($content->created_at);
1196         $postarray['edited'] = DateTimeFormat::utc($content->created_at);
1197
1198         if (is_string($content->place->name)) {
1199                 $postarray["location"] = $content->place->name;
1200         }
1201
1202         if (is_string($content->place->full_name)) {
1203                 $postarray["location"] = $content->place->full_name;
1204         }
1205
1206         if (is_array($content->geo->coordinates)) {
1207                 $postarray["coord"] = $content->geo->coordinates[0] . " " . $content->geo->coordinates[1];
1208         }
1209
1210         if (is_array($content->coordinates->coordinates)) {
1211                 $postarray["coord"] = $content->coordinates->coordinates[1] . " " . $content->coordinates->coordinates[0];
1212         }
1213
1214         logger("statusnet_createpost: end", LOGGER_DEBUG);
1215
1216         return $postarray;
1217 }
1218
1219 function statusnet_checknotification(App $a, $uid, $own_url, $top_item, $postarray)
1220 {
1221         // This function necer worked and need cleanup
1222         $user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1",
1223                         intval($uid)
1224         );
1225
1226         if (!count($user)) {
1227                 return;
1228         }
1229
1230         // Is it me?
1231         if (link_compare($user[0]["url"], $postarray['author-link'])) {
1232                 return;
1233         }
1234
1235         $own_user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1236                         intval($uid),
1237                         dbesc($own_url)
1238         );
1239
1240         if (!count($own_user)) {
1241                 return;
1242         }
1243
1244         // Is it me from GNU Social?
1245         if (link_compare($own_user[0]["url"], $postarray['author-link'])) {
1246                 return;
1247         }
1248
1249         $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
1250                         dbesc($postarray['parent-uri']),
1251                         intval($uid)
1252         );
1253
1254         if (count($myconv)) {
1255                 foreach ($myconv as $conv) {
1256                         // now if we find a match, it means we're in this conversation
1257                         if (!link_compare($conv['author-link'], $user[0]["url"]) && !link_compare($conv['author-link'], $own_user[0]["url"])) {
1258                                 continue;
1259                         }
1260
1261                         require_once 'include/enotify.php';
1262
1263                         $conv_parent = $conv['parent'];
1264
1265                         notification([
1266                                 'type' => NOTIFY_COMMENT,
1267                                 'notify_flags' => $user[0]['notify-flags'],
1268                                 'language' => $user[0]['language'],
1269                                 'to_name' => $user[0]['username'],
1270                                 'to_email' => $user[0]['email'],
1271                                 'uid' => $user[0]['uid'],
1272                                 'item' => $postarray,
1273                                 'link' => $a->get_baseurl() . '/display/' . urlencode(Item::getGuidById($top_item)),
1274                                 'source_name' => $postarray['author-name'],
1275                                 'source_link' => $postarray['author-link'],
1276                                 'source_photo' => $postarray['author-avatar'],
1277                                 'verb' => ACTIVITY_POST,
1278                                 'otype' => 'item',
1279                                 'parent' => $conv_parent,
1280                         ]);
1281
1282                         // only send one notification
1283                         break;
1284                 }
1285         }
1286 }
1287
1288 function statusnet_fetchhometimeline(App $a, $uid, $mode = 1)
1289 {
1290         $conversations = [];
1291
1292         $ckey    = PConfig::get($uid, 'statusnet', 'consumerkey');
1293         $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1294         $api     = PConfig::get($uid, 'statusnet', 'baseapi');
1295         $otoken  = PConfig::get($uid, 'statusnet', 'oauthtoken');
1296         $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1297         $create_user = PConfig::get($uid, 'statusnet', 'create_user');
1298
1299         // "create_user" is deactivated, since currently you cannot add users manually by now
1300         $create_user = true;
1301
1302         logger("statusnet_fetchhometimeline: Fetching for user " . $uid, LOGGER_DEBUG);
1303
1304         require_once 'include/items.php';
1305
1306         $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1307
1308         $own_contact = statusnet_fetch_own_contact($a, $uid);
1309
1310         $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1311                 intval($own_contact),
1312                 intval($uid));
1313
1314         if (count($r)) {
1315                 $nick = $r[0]["nick"];
1316         } else {
1317                 logger("statusnet_fetchhometimeline: Own GNU Social contact not found for user " . $uid, LOGGER_DEBUG);
1318                 return;
1319         }
1320
1321         $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1322                 intval($uid));
1323
1324         if (count($r)) {
1325                 $self = $r[0];
1326         } else {
1327                 logger("statusnet_fetchhometimeline: Own contact not found for user " . $uid, LOGGER_DEBUG);
1328                 return;
1329         }
1330
1331         $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1332                 intval($uid));
1333         if (!count($u)) {
1334                 logger("statusnet_fetchhometimeline: Own user not found for user " . $uid, LOGGER_DEBUG);
1335                 return;
1336         }
1337
1338         $parameters = ["exclude_replies" => false, "trim_user" => false, "contributor_details" => true, "include_rts" => true];
1339         //$parameters["count"] = 200;
1340
1341         if ($mode == 1) {
1342                 // Fetching timeline
1343                 $lastid = PConfig::get($uid, 'statusnet', 'lasthometimelineid');
1344                 //$lastid = 1;
1345
1346                 $first_time = ($lastid == "");
1347
1348                 if ($lastid != "") {
1349                         $parameters["since_id"] = $lastid;
1350                 }
1351
1352                 $items = $connection->get('statuses/home_timeline', $parameters);
1353
1354                 if (!is_array($items)) {
1355                         if (is_object($items) && isset($items->error)) {
1356                                 $errormsg = $items->error;
1357                         } elseif (is_object($items)) {
1358                                 $errormsg = print_r($items, true);
1359                         } elseif (is_string($items) || is_float($items) || is_int($items)) {
1360                                 $errormsg = $items;
1361                         } else {
1362                                 $errormsg = "Unknown error";
1363                         }
1364
1365                         logger("statusnet_fetchhometimeline: Error fetching home timeline: " . $errormsg, LOGGER_DEBUG);
1366                         return;
1367                 }
1368
1369                 $posts = array_reverse($items);
1370
1371                 logger("statusnet_fetchhometimeline: Fetching timeline for user " . $uid . " " . sizeof($posts) . " items", LOGGER_DEBUG);
1372
1373                 if (count($posts)) {
1374                         foreach ($posts as $post) {
1375
1376                                 if ($post->id > $lastid) {
1377                                         $lastid = $post->id;
1378                                 }
1379
1380                                 if ($first_time) {
1381                                         continue;
1382                                 }
1383
1384                                 if (isset($post->statusnet_conversation_id)) {
1385                                         if (!isset($conversations[$post->statusnet_conversation_id])) {
1386                                                 statusnet_complete_conversation($a, $uid, $self, $create_user, $nick, $post->statusnet_conversation_id);
1387                                                 $conversations[$post->statusnet_conversation_id] = $post->statusnet_conversation_id;
1388                                         }
1389                                 } else {
1390                                         $postarray = statusnet_createpost($a, $uid, $post, $self, $create_user, true);
1391
1392                                         if (trim($postarray['body']) == "") {
1393                                                 continue;
1394                                         }
1395
1396                                         $item = Item::insert($postarray);
1397                                         $postarray["id"] = $item;
1398
1399                                         logger('statusnet_fetchhometimeline: User ' . $self["nick"] . ' posted home timeline item ' . $item);
1400
1401                                         if ($item && !function_exists("check_item_notification")) {
1402                                                 statusnet_checknotification($a, $uid, $nick, $item, $postarray);
1403                                         }
1404                                 }
1405                         }
1406                 }
1407                 PConfig::set($uid, 'statusnet', 'lasthometimelineid', $lastid);
1408         }
1409
1410         // Fetching mentions
1411         $lastid = PConfig::get($uid, 'statusnet', 'lastmentionid');
1412         $first_time = ($lastid == "");
1413
1414         if ($lastid != "") {
1415                 $parameters["since_id"] = $lastid;
1416         }
1417
1418         $items = $connection->get('statuses/mentions_timeline', $parameters);
1419
1420         if (!is_array($items)) {
1421                 logger("statusnet_fetchhometimeline: Error fetching mentions: " . print_r($items, true), LOGGER_DEBUG);
1422                 return;
1423         }
1424
1425         $posts = array_reverse($items);
1426
1427         logger("statusnet_fetchhometimeline: Fetching mentions for user " . $uid . " " . sizeof($posts) . " items", LOGGER_DEBUG);
1428
1429         if (count($posts)) {
1430                 foreach ($posts as $post) {
1431                         if ($post->id > $lastid) {
1432                                 $lastid = $post->id;
1433                         }
1434
1435                         if ($first_time) {
1436                                 continue;
1437                         }
1438
1439                         $postarray = statusnet_createpost($a, $uid, $post, $self, false, false);
1440
1441                         if (isset($post->statusnet_conversation_id)) {
1442                                 if (!isset($conversations[$post->statusnet_conversation_id])) {
1443                                         statusnet_complete_conversation($a, $uid, $self, $create_user, $nick, $post->statusnet_conversation_id);
1444                                         $conversations[$post->statusnet_conversation_id] = $post->statusnet_conversation_id;
1445                                 }
1446                         } else {
1447                                 if (trim($postarray['body']) == "") {
1448                                         continue;
1449                                 }
1450
1451                                 $item = Item::insert($postarray);
1452                                 $postarray["id"] = $item;
1453
1454                                 logger('statusnet_fetchhometimeline: User ' . $self["nick"] . ' posted mention timeline item ' . $item);
1455
1456                                 if ($item && function_exists("check_item_notification")) {
1457                                         check_item_notification($item, $uid, NOTIFY_TAGSELF);
1458                                 }
1459                         }
1460
1461                         $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1462                                 dbesc($postarray['uri']),
1463                                 intval($uid)
1464                         );
1465                         if (count($r)) {
1466                                 $item = $r[0]['id'];
1467                                 $parent_id = $r[0]['parent'];
1468                         }
1469
1470                         if (($item != 0) && !function_exists("check_item_notification")) {
1471                                 require_once 'include/enotify.php';
1472                                 notification([
1473                                         'type'         => NOTIFY_TAGSELF,
1474                                         'notify_flags' => $u[0]['notify-flags'],
1475                                         'language'     => $u[0]['language'],
1476                                         'to_name'      => $u[0]['username'],
1477                                         'to_email'     => $u[0]['email'],
1478                                         'uid'          => $u[0]['uid'],
1479                                         'item'         => $postarray,
1480                                         'link'         => $a->get_baseurl() . '/display/' . urlencode(Item::getGuidById($item)),
1481                                         'source_name'  => $postarray['author-name'],
1482                                         'source_link'  => $postarray['author-link'],
1483                                         'source_photo' => $postarray['author-avatar'],
1484                                         'verb'         => ACTIVITY_TAG,
1485                                         'otype'        => 'item',
1486                                         'parent'       => $parent_id,
1487                                 ]);
1488                         }
1489                 }
1490         }
1491
1492         PConfig::set($uid, 'statusnet', 'lastmentionid', $lastid);
1493 }
1494
1495 function statusnet_complete_conversation(App $a, $uid, $self, $create_user, $nick, $conversation)
1496 {
1497         $ckey    = PConfig::get($uid, 'statusnet', 'consumerkey');
1498         $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1499         $api     = PConfig::get($uid, 'statusnet', 'baseapi');
1500         $otoken  = PConfig::get($uid, 'statusnet', 'oauthtoken');
1501         $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1502         $own_url = PConfig::get($uid, 'statusnet', 'own_url');
1503
1504         $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1505
1506         $parameters["count"] = 200;
1507
1508         $items = $connection->get('statusnet/conversation/' . $conversation, $parameters);
1509         if (is_array($items)) {
1510                 $posts = array_reverse($items);
1511
1512                 foreach ($posts as $post) {
1513                         $postarray = statusnet_createpost($a, $uid, $post, $self, false, false);
1514
1515                         if (trim($postarray['body']) == "") {
1516                                 continue;
1517                         }
1518
1519                         $item = Item::insert($postarray);
1520                         $postarray["id"] = $item;
1521
1522                         logger('statusnet_complete_conversation: User ' . $self["nick"] . ' posted home timeline item ' . $item);
1523
1524                         if ($item && !function_exists("check_item_notification")) {
1525                                 statusnet_checknotification($a, $uid, $nick, $item, $postarray);
1526                         }
1527                 }
1528         }
1529 }
1530
1531 function statusnet_convertmsg(App $a, $body, $no_tags = false)
1532 {
1533         require_once "include/items.php";
1534
1535         $body = preg_replace("=\[url\=https?://([0-9]*).([0-9]*).([0-9]*).([0-9]*)/([0-9]*)\](.*?)\[\/url\]=ism", "$1.$2.$3.$4/$5", $body);
1536
1537         $URLSearchString = "^\[\]";
1538         $links = preg_match_all("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER);
1539
1540         $footer = "";
1541         $footerurl = "";
1542         $footerlink = "";
1543         $type = "";
1544
1545         if ($links) {
1546                 foreach ($matches AS $match) {
1547                         $search = "[url=" . $match[1] . "]" . $match[2] . "[/url]";
1548
1549                         logger("statusnet_convertmsg: expanding url " . $match[1], LOGGER_DEBUG);
1550
1551                         $expanded_url = Network::finalUrl($match[1]);
1552
1553                         logger("statusnet_convertmsg: fetching data for " . $expanded_url, LOGGER_DEBUG);
1554
1555                         $oembed_data = OEmbed::fetchURL($expanded_url, true);
1556
1557                         logger("statusnet_convertmsg: fetching data: done", LOGGER_DEBUG);
1558
1559                         if ($type == "") {
1560                                 $type = $oembed_data->type;
1561                         }
1562
1563                         if ($oembed_data->type == "video") {
1564                                 //$body = str_replace($search, "[video]".$expanded_url."[/video]", $body);
1565                                 $type = $oembed_data->type;
1566                                 $footerurl = $expanded_url;
1567                                 $footerlink = "[url=" . $expanded_url . "]" . $expanded_url . "[/url]";
1568
1569                                 $body = str_replace($search, $footerlink, $body);
1570                         } elseif (($oembed_data->type == "photo") && isset($oembed_data->url) && !$dontincludemedia) {
1571                                 $body = str_replace($search, "[url=" . $expanded_url . "][img]" . $oembed_data->url . "[/img][/url]", $body);
1572                         } elseif ($oembed_data->type != "link") {
1573                                 $body = str_replace($search, "[url=" . $expanded_url . "]" . $expanded_url . "[/url]", $body);
1574                         } else {
1575                                 $img_str = Network::fetchUrl($expanded_url, true, $redirects, 4);
1576
1577                                 $tempfile = tempnam(get_temppath(), "cache");
1578                                 file_put_contents($tempfile, $img_str);
1579                                 $mime = image_type_to_mime_type(exif_imagetype($tempfile));
1580                                 unlink($tempfile);
1581
1582                                 if (substr($mime, 0, 6) == "image/") {
1583                                         $type = "photo";
1584                                         $body = str_replace($search, "[img]" . $expanded_url . "[/img]", $body);
1585                                 } else {
1586                                         $type = $oembed_data->type;
1587                                         $footerurl = $expanded_url;
1588                                         $footerlink = "[url=" . $expanded_url . "]" . $expanded_url . "[/url]";
1589
1590                                         $body = str_replace($search, $footerlink, $body);
1591                                 }
1592                         }
1593                 }
1594
1595                 if ($footerurl != "") {
1596                         $footer = add_page_info($footerurl);
1597                 }
1598
1599                 if (($footerlink != "") && (trim($footer) != "")) {
1600                         $removedlink = trim(str_replace($footerlink, "", $body));
1601
1602                         if (($removedlink == "") || strstr($body, $removedlink)) {
1603                                 $body = $removedlink;
1604                         }
1605
1606                         $body .= $footer;
1607                 }
1608         }
1609
1610         if ($no_tags) {
1611                 return ["body" => $body, "tags" => ""];
1612         }
1613
1614         $str_tags = '';
1615
1616         $cnt = preg_match_all("/([!#@])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER);
1617         if ($cnt) {
1618                 foreach ($matches as $mtch) {
1619                         if (strlen($str_tags)) {
1620                                 $str_tags .= ',';
1621                         }
1622
1623                         if ($mtch[1] == "#") {
1624                                 // Replacing the hash tags that are directed to the GNU Social server with internal links
1625                                 $snhash = "#[url=" . $mtch[2] . "]" . $mtch[3] . "[/url]";
1626                                 $frdchash = '#[url=' . $a->get_baseurl() . '/search?tag=' . rawurlencode($mtch[3]) . ']' . $mtch[3] . '[/url]';
1627                                 $body = str_replace($snhash, $frdchash, $body);
1628
1629                                 $str_tags .= $frdchash;
1630                         } else {
1631                                 $str_tags .= "@[url=" . $mtch[2] . "]" . $mtch[3] . "[/url]";
1632                         }
1633                         // To-Do:
1634                         // There is a problem with links with to GNU Social groups, so these links are stored with "@" like friendica groups
1635                         //$str_tags .= $mtch[1]."[url=".$mtch[2]."]".$mtch[3]."[/url]";
1636                 }
1637         }
1638
1639         return ["body" => $body, "tags" => $str_tags];
1640 }
1641
1642 function statusnet_fetch_own_contact(App $a, $uid)
1643 {
1644         $ckey    = PConfig::get($uid, 'statusnet', 'consumerkey');
1645         $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1646         $api     = PConfig::get($uid, 'statusnet', 'baseapi');
1647         $otoken  = PConfig::get($uid, 'statusnet', 'oauthtoken');
1648         $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1649         $own_url = PConfig::get($uid, 'statusnet', 'own_url');
1650
1651         $contact_id = 0;
1652
1653         if ($own_url == "") {
1654                 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1655
1656                 // Fetching user data
1657                 $user = $connection->get('account/verify_credentials');
1658
1659                 PConfig::set($uid, 'statusnet', 'own_url', normalise_link($user->statusnet_profile_url));
1660
1661                 $contact_id = statusnet_fetch_contact($uid, $user, true);
1662         } else {
1663                 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1664                         intval($uid), dbesc($own_url));
1665                 if (count($r)) {
1666                         $contact_id = $r[0]["id"];
1667                 } else {
1668                         PConfig::delete($uid, 'statusnet', 'own_url');
1669                 }
1670         }
1671         return $contact_id;
1672 }
1673
1674 function statusnet_is_retweet(App $a, $uid, $body)
1675 {
1676         $body = trim($body);
1677
1678         // Skip if it isn't a pure repeated messages
1679         // Does it start with a share?
1680         if (strpos($body, "[share") > 0) {
1681                 return false;
1682         }
1683
1684         // Does it end with a share?
1685         if (strlen($body) > (strrpos($body, "[/share]") + 8)) {
1686                 return false;
1687         }
1688
1689         $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "$1", $body);
1690         // Skip if there is no shared message in there
1691         if ($body == $attributes) {
1692                 return false;
1693         }
1694
1695         $link = "";
1696         preg_match("/link='(.*?)'/ism", $attributes, $matches);
1697         if ($matches[1] != "") {
1698                 $link = $matches[1];
1699         }
1700
1701         preg_match('/link="(.*?)"/ism', $attributes, $matches);
1702         if ($matches[1] != "") {
1703                 $link = $matches[1];
1704         }
1705
1706         $ckey    = PConfig::get($uid, 'statusnet', 'consumerkey');
1707         $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1708         $api     = PConfig::get($uid, 'statusnet', 'baseapi');
1709         $otoken  = PConfig::get($uid, 'statusnet', 'oauthtoken');
1710         $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1711         $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $api);
1712
1713         $id = preg_replace("=https?://" . $hostname . "/notice/(.*)=ism", "$1", $link);
1714
1715         if ($id == $link) {
1716                 return false;
1717         }
1718
1719         logger('statusnet_is_retweet: Retweeting id ' . $id . ' for user ' . $uid, LOGGER_DEBUG);
1720
1721         $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1722
1723         $result = $connection->post('statuses/retweet/' . $id);
1724
1725         logger('statusnet_is_retweet: result ' . print_r($result, true), LOGGER_DEBUG);
1726
1727         return isset($result->id);
1728 }