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