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