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