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