]> git.mxchange.org Git - friendica-addons.git/blob - statusnet/statusnet.php
user selects preconfigured SN, put app name in pconfig
[friendica-addons.git] / statusnet / statusnet.php
1 <?php
2 /**
3  * Name: StatusNet Connector
4  * Description: Relay public postings to a connected StatusNet account
5  * Version: 1.0.5
6  * Author: Tobias Diekershoff <http://diekershoff.homeunix.net/friendika/profile/tobias>
7  */
8  
9 /*   StatusNet Plugin for Friendica
10  *
11  *   Author: Tobias Diekershoff
12  *           tobias.diekershoff@gmx.net
13  *
14  *   License:3-clause BSD license
15  *
16  *   Configuration:
17  *     To activate the plugin itself add it to the $a->config['system']['addon']
18  *     setting. After this, your user can configure their Twitter account settings
19  *     from "Settings -> Plugin Settings".
20  *
21  *     Requirements: PHP5, curl [Slinky library]
22  *
23  *     Documentation: http://diekershoff.homeunix.net/redmine/wiki/friendikaplugin/StatusNet_Plugin
24  */
25
26 /***
27  * We have to alter the TwitterOAuth class a little bit to work with any StatusNet
28  * installation abroad. Basically it's only make the API path variable and be happy.
29  *
30  * Thank you guys for the Twitter compatible API!
31  */
32
33 define('STATUSNET_DEFAULT_POLL_INTERVAL', 5); // given in minutes
34
35 require_once('library/twitteroauth.php');
36
37 class StatusNetOAuth extends TwitterOAuth {
38     function get_maxlength() {
39         $config = $this->get($this->host . 'statusnet/config.json');
40         return $config->site->textlimit;
41     }
42     function accessTokenURL()  { return $this->host.'oauth/access_token'; }
43     function authenticateURL() { return $this->host.'oauth/authenticate'; } 
44     function authorizeURL() { return $this->host.'oauth/authorize'; }
45     function requestTokenURL() { return $this->host.'oauth/request_token'; }
46     function __construct($apipath, $consumer_key, $consumer_secret, $oauth_token = NULL, $oauth_token_secret = NULL) {
47         parent::__construct($consumer_key, $consumer_secret, $oauth_token, $oauth_token_secret);
48         $this->host = $apipath;
49     }
50   /**
51    * Make an HTTP request
52    *
53    * @return API results
54    *
55    * Copied here from the twitteroauth library and complemented by applying the proxy settings of friendica
56    */
57   function http($url, $method, $postfields = NULL) {
58     $this->http_info = array();
59     $ci = curl_init();
60     /* Curl settings */
61     $prx = get_config('system','proxy');
62     if(strlen($prx)) {
63         curl_setopt($ci, CURLOPT_HTTPPROXYTUNNEL, 1);
64         curl_setopt($ci, CURLOPT_PROXY, $prx);
65         $prxusr = get_config('system','proxyuser');
66         if(strlen($prxusr))
67             curl_setopt($ci, CURLOPT_PROXYUSERPWD, $prxusr);
68     }
69     curl_setopt($ci, CURLOPT_USERAGENT, $this->useragent);
70     curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout);
71     curl_setopt($ci, CURLOPT_TIMEOUT, $this->timeout);
72     curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE);
73     curl_setopt($ci, CURLOPT_HTTPHEADER, array('Expect:'));
74     curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer);
75     curl_setopt($ci, CURLOPT_HEADERFUNCTION, array($this, 'getHeader'));
76     curl_setopt($ci, CURLOPT_HEADER, FALSE);
77
78     switch ($method) {
79       case 'POST':
80         curl_setopt($ci, CURLOPT_POST, TRUE);
81         if (!empty($postfields)) {
82           curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields);
83         }
84         break;
85       case 'DELETE':
86         curl_setopt($ci, CURLOPT_CUSTOMREQUEST, 'DELETE');
87         if (!empty($postfields)) {
88           $url = "{$url}?{$postfields}";
89         }
90     }
91
92     curl_setopt($ci, CURLOPT_URL, $url);
93     $response = curl_exec($ci);
94     $this->http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE);
95     $this->http_info = array_merge($this->http_info, curl_getinfo($ci));
96     $this->url = $url;
97     curl_close ($ci);
98     return $response;
99   }
100 }
101
102 function statusnet_install() {
103         //  we need some hooks, for the configuration and for sending tweets
104         register_hook('connector_settings', 'addon/statusnet/statusnet.php', 'statusnet_settings'); 
105         register_hook('connector_settings_post', 'addon/statusnet/statusnet.php', 'statusnet_settings_post');
106         register_hook('notifier_normal', 'addon/statusnet/statusnet.php', 'statusnet_post_hook');
107         register_hook('post_local', 'addon/statusnet/statusnet.php', 'statusnet_post_local');
108         register_hook('jot_networks',    'addon/statusnet/statusnet.php', 'statusnet_jot_nets');
109         register_hook('cron', 'addon/statusnet/statusnet.php', 'statusnet_cron');
110         logger("installed statusnet");
111 }
112
113
114 function statusnet_uninstall() {
115         unregister_hook('connector_settings', 'addon/statusnet/statusnet.php', 'statusnet_settings'); 
116         unregister_hook('connector_settings_post', 'addon/statusnet/statusnet.php', 'statusnet_settings_post');
117         unregister_hook('notifier_normal', 'addon/statusnet/statusnet.php', 'statusnet_post_hook');
118         unregister_hook('post_local', 'addon/statusnet/statusnet.php', 'statusnet_post_local');
119         unregister_hook('jot_networks',    'addon/statusnet/statusnet.php', 'statusnet_jot_nets');
120         unregister_hook('cron', 'addon/statusnet/statusnet.php', 'statusnet_cron');
121
122         // old setting - remove only
123         unregister_hook('post_local_end', 'addon/statusnet/statusnet.php', 'statusnet_post_hook');
124         unregister_hook('plugin_settings', 'addon/statusnet/statusnet.php', 'statusnet_settings'); 
125         unregister_hook('plugin_settings_post', 'addon/statusnet/statusnet.php', 'statusnet_settings_post');
126
127 }
128
129 function statusnet_jot_nets(&$a,&$b) {
130         if(! local_user())
131                 return;
132
133         $statusnet_post = get_pconfig(local_user(),'statusnet','post');
134         if(intval($statusnet_post) == 1) {
135                 $statusnet_defpost = get_pconfig(local_user(),'statusnet','post_by_default');
136                 $selected = ((intval($statusnet_defpost) == 1) ? ' checked="checked" ' : '');
137                 $b .= '<div class="profile-jot-net"><input type="checkbox" name="statusnet_enable"' . $selected . ' value="1" /> ' 
138                         . t('Post to StatusNet') . '</div>';
139         }
140 }
141
142 function statusnet_settings_post ($a,$post) {
143         if(! local_user())
144             return;
145         // don't check statusnet settings if statusnet submit button is not clicked
146         if (!x($_POST,'statusnet-submit')) return;
147         
148         if (isset($_POST['statusnet-disconnect'])) {
149             /***
150              * if the statusnet-disconnect checkbox is set, clear the statusnet configuration
151              */
152             del_pconfig(local_user(), 'statusnet', 'consumerkey');
153             del_pconfig(local_user(), 'statusnet', 'consumersecret');
154             del_pconfig(local_user(), 'statusnet', 'post');
155             del_pconfig(local_user(), 'statusnet', 'post_by_default');
156             del_pconfig(local_user(), 'statusnet', 'oauthtoken');
157             del_pconfig(local_user(), 'statusnet', 'oauthsecret');
158             del_pconfig(local_user(), 'statusnet', 'baseapi');
159             del_pconfig(local_user(), 'statusnet', 'post_taglinks');
160             del_pconfig(local_user(), 'statusnet', 'lastid');
161             del_pconfig(local_user(), 'statusnet', 'mirror_posts');
162             del_pconfig(local_user(), 'statusnet', 'intelligent_shortening');
163         } else {
164             if (isset($_POST['statusnet-preconf-apiurl'])) {
165                 /***
166                  * If the user used one of the preconfigured StatusNet server credentials
167                  * use them. All the data are available in the global config.
168                  * Check the API Url never the less and blame the admin if it's not working ^^
169                  */
170                 $globalsn = get_config('statusnet', 'sites');
171                 foreach ( $globalsn as $asn) {
172                     if ($asn['apiurl'] == $_POST['statusnet-preconf-apiurl'] ) {
173                         $apibase = $asn['apiurl'];
174                         $c = fetch_url( $apibase . 'statusnet/version.xml' );
175                         if (strlen($c) > 0) {
176                             set_pconfig(local_user(), 'statusnet', 'consumerkey', $asn['consumerkey'] );
177                             set_pconfig(local_user(), 'statusnet', 'consumersecret', $asn['consumersecret'] );
178                             set_pconfig(local_user(), 'statusnet', 'baseapi', $asn['apiurl'] );
179                             set_pconfig(local_user(), 'statusnet', 'application_name', $asn['applicationname'] );
180                         } else {
181                             notice( t('Please contact your site administrator.<br />The provided API URL is not valid.').EOL.$asn['apiurl'].EOL );
182                         }
183                     }
184                 }
185                 goaway($a->get_baseurl().'/settings/connectors');
186             } else {
187             if (isset($_POST['statusnet-consumersecret'])) {
188                 //  check if we can reach the API of the StatusNet server
189                 //  we'll check the API Version for that, if we don't get one we'll try to fix the path but will
190                 //  resign quickly after this one try to fix the path ;-)
191                 $apibase = $_POST['statusnet-baseapi'];
192                 $c = fetch_url( $apibase . 'statusnet/version.xml' );
193                 if (strlen($c) > 0) {
194                     //  ok the API path is correct, let's save the settings
195                     set_pconfig(local_user(), 'statusnet', 'consumerkey', $_POST['statusnet-consumerkey']);
196                     set_pconfig(local_user(), 'statusnet', 'consumersecret', $_POST['statusnet-consumersecret']);
197                     set_pconfig(local_user(), 'statusnet', 'baseapi', $apibase );
198                 } else {
199                     //  the API path is not correct, maybe missing trailing / ?
200                     $apibase = $apibase . '/';
201                     $c = fetch_url( $apibase . 'statusnet/version.xml' );
202                     if (strlen($c) > 0) {
203                         //  ok the API path is now correct, let's save the settings
204                         set_pconfig(local_user(), 'statusnet', 'consumerkey', $_POST['statusnet-consumerkey']);
205                         set_pconfig(local_user(), 'statusnet', 'consumersecret', $_POST['statusnet-consumersecret']);
206                         set_pconfig(local_user(), 'statusnet', 'baseapi', $apibase );
207                     } else {
208                         //  still not the correct API base, let's do noting
209                         notice( t('We could not contact the StatusNet API with the Path you entered.').EOL );
210                     }
211                 }
212                 goaway($a->get_baseurl().'/settings/connectors');
213             } else {
214                 if (isset($_POST['statusnet-pin'])) {
215                         //  if the user supplied us with a PIN from StatusNet, let the magic of OAuth happen
216                     $api     = get_pconfig(local_user(), 'statusnet', 'baseapi');
217                                         $ckey    = get_pconfig(local_user(), 'statusnet', 'consumerkey'  );
218                                         $csecret = get_pconfig(local_user(), 'statusnet', 'consumersecret' );
219                                         //  the token and secret for which the PIN was generated were hidden in the settings
220                                         //  form as token and token2, we need a new connection to Twitter using these token
221                                         //  and secret to request a Access Token with the PIN
222                                         $connection = new StatusNetOAuth($api, $ckey, $csecret, $_POST['statusnet-token'], $_POST['statusnet-token2']);
223                                         $token   = $connection->getAccessToken( $_POST['statusnet-pin'] );
224                                         //  ok, now that we have the Access Token, save them in the user config
225                                         set_pconfig(local_user(),'statusnet', 'oauthtoken',  $token['oauth_token']);
226                                         set_pconfig(local_user(),'statusnet', 'oauthsecret', $token['oauth_token_secret']);
227                                         set_pconfig(local_user(),'statusnet', 'post', 1);
228                                         set_pconfig(local_user(),'statusnet', 'post_taglinks', 1);
229                     //  reload the Addon Settings page, if we don't do it see Bug #42
230                     goaway($a->get_baseurl().'/settings/connectors');
231                                 } else {
232                                         //  if no PIN is supplied in the POST variables, the user has changed the setting
233                                         //  to post a dent for every new __public__ posting to the wall
234                                         set_pconfig(local_user(),'statusnet','post',intval($_POST['statusnet-enable']));
235                                         set_pconfig(local_user(),'statusnet','post_by_default',intval($_POST['statusnet-default']));
236                                         set_pconfig(local_user(),'statusnet','post_taglinks',intval($_POST['statusnet-sendtaglinks']));
237                                         set_pconfig(local_user(), 'statusnet', 'mirror_posts', intval($_POST['statusnet-mirror']));
238                                         set_pconfig(local_user(), 'statusnet', 'intelligent_shortening', intval($_POST['statusnet-shortening']));
239                                         info( t('StatusNet settings updated.') . EOL);
240                 }}}}
241 }
242 function statusnet_settings(&$a,&$s) {
243         if(! local_user())
244                 return;
245         $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/statusnet/statusnet.css' . '" media="all" />' . "\r\n";
246         /***
247          * 1) Check that we have a base api url and a consumer key & secret
248          * 2) If no OAuthtoken & stuff is present, generate button to get some
249          *    allow the user to cancel the connection process at this step
250          * 3) Checkbox for "Send public notices (respect size limitation)
251          */
252         $api     = get_pconfig(local_user(), 'statusnet', 'baseapi');
253         $ckey    = get_pconfig(local_user(), 'statusnet', 'consumerkey' );
254         $csecret = get_pconfig(local_user(), 'statusnet', 'consumersecret' );
255         $otoken  = get_pconfig(local_user(), 'statusnet', 'oauthtoken'  );
256         $osecret = get_pconfig(local_user(), 'statusnet', 'oauthsecret' );
257         $enabled = get_pconfig(local_user(), 'statusnet', 'post');
258         $checked = (($enabled) ? ' checked="checked" ' : '');
259         $defenabled = get_pconfig(local_user(),'statusnet','post_by_default');
260         $defchecked = (($defenabled) ? ' checked="checked" ' : '');
261         $linksenabled = get_pconfig(local_user(),'statusnet','post_taglinks');
262         $linkschecked = (($linksenabled) ? ' checked="checked" ' : '');
263
264         $mirrorenabled = get_pconfig(local_user(),'statusnet','mirror_posts');
265         $mirrorchecked = (($mirrorenabled) ? ' checked="checked" ' : '');
266         $shorteningenabled = get_pconfig(local_user(),'statusnet','intelligent_shortening');
267         $shorteningchecked = (($shorteningenabled) ? ' checked="checked" ' : '');
268
269         $s .= '<div class="settings-block">';
270         $s .= '<h3>'. t('StatusNet Posting Settings').'</h3>';
271
272         if ( (!$ckey) && (!$csecret) ) {
273                 /***
274                  * no consumer keys
275                  */
276             $globalsn = get_config('statusnet', 'sites');
277             /***
278              * lets check if we have one or more globally configured StatusNet
279              * server OAuth credentials in the configuration. If so offer them
280              * with a little explanation to the user as choice - otherwise
281              * ignore this option entirely.
282              */
283             if (! $globalsn == null) {
284                 $s .= '<h4>' . t('Globally Available StatusNet OAuthKeys') . '</h4>';
285                 $s .= '<p>'. t("There are preconfigured OAuth key pairs for some StatusNet servers available. If you are useing one of them, please use these credentials. If not feel free to connect to any other StatusNet instance \x28see below\x29.") .'</p>';
286                 $s .= '<div id="statusnet-preconf-wrapper">';
287                 foreach ($globalsn as $asn) {
288                     $s .= '<input type="radio" name="statusnet-preconf-apiurl" value="'. $asn['apiurl'] .'">'. $asn['sitename'] .'<br />';
289                 }
290                 $s .= '<p></p><div class="clear"></div></div>';
291                 $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . t('Submit') . '" /></div>';
292             }
293             $s .= '<h4>' . t('Provide your own OAuth Credentials') . '</h4>';
294             $s .= '<p>'. t('No consumer key pair for StatusNet found. Register your Friendica Account as an desktop client on your StatusNet 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 StatusNet installation.') .'</p>';
295             $s .= '<div id="statusnet-consumer-wrapper">';
296             $s .= '<label id="statusnet-consumerkey-label" for="statusnet-consumerkey">'. t('OAuth Consumer Key') .'</label>';
297             $s .= '<input id="statusnet-consumerkey" type="text" name="statusnet-consumerkey" size="35" /><br />';
298             $s .= '<div class="clear"></div>';
299             $s .= '<label id="statusnet-consumersecret-label" for="statusnet-consumersecret">'. t('OAuth Consumer Secret') .'</label>';
300             $s .= '<input id="statusnet-consumersecret" type="text" name="statusnet-consumersecret" size="35" /><br />';
301             $s .= '<div class="clear"></div>';
302             $s .= '<label id="statusnet-baseapi-label" for="statusnet-baseapi">'. t("Base API Path \x28remember the trailing /\x29") .'</label>';
303             $s .= '<input id="statusnet-baseapi" type="text" name="statusnet-baseapi" size="35" /><br />';
304             $s .= '<p></p><div class="clear"></div></div>';
305             $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . t('Submit') . '" /></div>';
306         } else {
307                 /***
308                  * ok we have a consumer key pair now look into the OAuth stuff
309                  */
310                 if ( (!$otoken) && (!$osecret) ) {
311                         /***
312                          * the user has not yet connected the account to statusnet
313                          * get a temporary OAuth key/secret pair and display a button with
314                          * which the user can request a PIN to connect the account to a
315                          * account at statusnet
316                          */
317                         $connection = new StatusNetOAuth($api, $ckey, $csecret);
318                         $request_token = $connection->getRequestToken('oob');
319                         $token = $request_token['oauth_token'];
320                         /***
321                          *  make some nice form
322                          */
323                         $s .= '<p>'. t('To connect to your StatusNet account click the button below to get a security code from StatusNet which you have to copy into the input box below and submit the form. Only your <strong>public</strong> posts will be posted to StatusNet.') .'</p>';
324                         $s .= '<a href="'.$connection->getAuthorizeURL($token,False).'" target="_statusnet"><img src="addon/statusnet/signinwithstatusnet.png" alt="'. t('Log in with StatusNet') .'"></a>';
325                         $s .= '<div id="statusnet-pin-wrapper">';
326                         $s .= '<label id="statusnet-pin-label" for="statusnet-pin">'. t('Copy the security code from StatusNet here') .'</label>';
327                         $s .= '<input id="statusnet-pin" type="text" name="statusnet-pin" />';
328                         $s .= '<input id="statusnet-token" type="hidden" name="statusnet-token" value="'.$token.'" />';
329                         $s .= '<input id="statusnet-token2" type="hidden" name="statusnet-token2" value="'.$request_token['oauth_token_secret'].'" />';
330                         $s .= '</div><div class="clear"></div>';
331                         $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . t('Submit') . '" /></div>';
332                         $s .= '<h4>'.t('Cancel Connection Process').'</h4>';
333                         $s .= '<div id="statusnet-cancel-wrapper">';
334                         $s .= '<p>'.t('Current StatusNet API is').': '.$api.'</p>';
335                         $s .= '<label id="statusnet-cancel-label" for="statusnet-cancel">'. t('Cancel StatusNet Connection') . '</label>';
336                         $s .= '<input id="statusnet-cancel" type="checkbox" name="statusnet-disconnect" value="1" />';
337                         $s .= '</div><div class="clear"></div>';
338                         $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . t('Submit') . '" /></div>';
339                 } else {
340                         /***
341                          *  we have an OAuth key / secret pair for the user
342                          *  so let's give a chance to disable the postings to statusnet
343                          */
344                         $connection = new StatusNetOAuth($api,$ckey,$csecret,$otoken,$osecret);
345                         $details = $connection->get('account/verify_credentials');
346                         $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>';
347                         $s .= '<p>'. t('If enabled all your <strong>public</strong> postings can be posted to the associated StatusNet account. You can choose to do so by default (here) or for every posting separately in the posting options when writing the entry.') .'</p>';
348                         if ($a->user['hidewall']) {
349                             $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 StatusNet will lead the visitor to a blank page informing the visitor that the access to your profile has been restricted.') .'</p>';
350                         }
351                         $s .= '<div id="statusnet-enable-wrapper">';
352                         $s .= '<label id="statusnet-enable-label" for="statusnet-checkbox">'. t('Allow posting to StatusNet') .'</label>';
353                         $s .= '<input id="statusnet-checkbox" type="checkbox" name="statusnet-enable" value="1" ' . $checked . '/>';
354                         $s .= '<div class="clear"></div>';
355                         $s .= '<label id="statusnet-default-label" for="statusnet-default">'. t('Send public postings to StatusNet by default') .'</label>';
356                         $s .= '<input id="statusnet-default" type="checkbox" name="statusnet-default" value="1" ' . $defchecked . '/>';
357                         $s .= '<div class="clear"></div>';
358
359                         $s .= '<label id="statusnet-mirror-label" for="statusnet-mirror">'.t('Mirror all posts from statusnet that are no replies or repeated messages').'</label>';
360                         $s .= '<input id="statusnet-mirror" type="checkbox" name="statusnet-mirror" value="1" '. $mirrorchecked . '/>';
361                         $s .= '<div class="clear"></div>';
362
363                         $s .= '<label id="statusnet-shortening-label" for="statusnet-shortening">'.t('Shortening method that optimizes the post').'</label>';
364                         $s .= '<input id="statusnet-shortening" type="checkbox" name="statusnet-shortening" value="1" '. $shorteningchecked . '/>';
365                         $s .= '<div class="clear"></div>';
366
367                         $s .= '<label id="statusnet-sendtaglinks-label" for="statusnet-sendtaglinks">'.t('Send linked #-tags and @-names to StatusNet').'</label>';
368                         $s .= '<input id="statusnet-sendtaglinks" type="checkbox" name="statusnet-sendtaglinks" value="1" '. $linkschecked . '/>';
369                         $s .= '</div><div class="clear"></div>';
370
371                         $s .= '<div id="statusnet-disconnect-wrapper">';
372                         $s .= '<label id="statusnet-disconnect-label" for="statusnet-disconnect">'. t('Clear OAuth configuration') .'</label>';
373                         $s .= '<input id="statusnet-disconnect" type="checkbox" name="statusnet-disconnect" value="1" />';
374                         $s .= '</div><div class="clear"></div>';
375                         $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . t('Submit') . '" /></div>'; 
376                 }
377         }
378         $s .= '</div><div class="clear"></div></div>';
379 }
380
381
382 function statusnet_post_local(&$a,&$b) {
383         if($b['edit'])
384                 return;
385
386         if((local_user()) && (local_user() == $b['uid']) && (! $b['private'])) {
387
388                 $statusnet_post = get_pconfig(local_user(),'statusnet','post');
389                 $statusnet_enable = (($statusnet_post && x($_REQUEST,'statusnet_enable')) ? intval($_REQUEST['statusnet_enable']) : 0);
390
391                 // if API is used, default to the chosen settings
392                 if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'statusnet','post_by_default')))
393                         $statusnet_enable = 1;
394
395        if(! $statusnet_enable)
396             return;
397
398        if(strlen($b['postopts']))
399            $b['postopts'] .= ',';
400        $b['postopts'] .= 'statusnet';
401     }
402 }
403
404 if (! function_exists( 'short_link' )) {
405 function short_link($url) {
406     require_once('library/slinky.php');
407     $slinky = new Slinky( $url );
408     $yourls_url = get_config('yourls','url1');
409     if ($yourls_url) {
410             $yourls_username = get_config('yourls','username1');
411             $yourls_password = get_config('yourls', 'password1');
412             $yourls_ssl = get_config('yourls', 'ssl1');
413             $yourls = new Slinky_YourLS();
414             $yourls->set( 'username', $yourls_username );
415             $yourls->set( 'password', $yourls_password );
416             $yourls->set( 'ssl', $yourls_ssl );
417             $yourls->set( 'yourls-url', $yourls_url );
418             $slinky->set_cascade( array( $yourls, new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) );
419     }
420     else {
421             // setup a cascade of shortening services
422             // try to get a short link from these services
423             // in the order ur1.ca, trim, id.gd, tinyurl
424             $slinky->set_cascade( array( new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) );
425     }
426     return $slinky->short();
427 } };
428
429 function statusnet_shortenmsg($b, $max_char) {
430         require_once("include/bbcode.php");
431         require_once("include/html2plain.php");
432
433         // Looking for the first image
434         $image = '';
435         if(preg_match("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/is",$b['body'],$matches))
436                 $image = $matches[3];
437
438         if ($image == '')
439                 if(preg_match("/\[img\](.*?)\[\/img\]/is",$b['body'],$matches))
440                         $image = $matches[1];
441
442         $multipleimages = (strpos($b['body'], "[img") != strrpos($b['body'], "[img"));
443
444         // When saved into the database the content is sent through htmlspecialchars
445         // That means that we have to decode all image-urls
446         $image = htmlspecialchars_decode($image);
447
448         $body = $b["body"];
449         if ($b["title"] != "")
450                 $body = $b["title"]."\n\n".$body;
451
452         if (strpos($body, "[bookmark") !== false) {
453                 // splitting the text in two parts:
454                 // before and after the bookmark
455                 $pos = strpos($body, "[bookmark");
456                 $body1 = substr($body, 0, $pos);
457                 $body2 = substr($body, $pos);
458
459                 // Removing all quotes after the bookmark
460                 // they are mostly only the content after the bookmark.
461                 $body2 = preg_replace("/\[quote\=([^\]]*)\](.*?)\[\/quote\]/ism",'',$body2);
462                 $body2 = preg_replace("/\[quote\](.*?)\[\/quote\]/ism",'',$body2);
463                 $body = $body1.$body2;
464         }
465
466         // Add some newlines so that the message could be cut better
467         $body = str_replace(array("[quote", "[bookmark", "[/bookmark]", "[/quote]"),
468                                 array("\n[quote", "\n[bookmark", "[/bookmark]\n", "[/quote]\n"), $body);
469
470         // remove the recycle signs and the names since they aren't helpful on twitter
471         // recycle 1
472         $recycle = html_entity_decode("&#x2672; ", ENT_QUOTES, 'UTF-8');
473         $body = preg_replace( '/'.$recycle.'\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', "\n", $body);
474         // recycle 2 (Test)
475         $recycle = html_entity_decode("&#x25CC; ", ENT_QUOTES, 'UTF-8');
476         $body = preg_replace( '/'.$recycle.'\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', "\n", $body);
477
478         // remove the share element
479         $body = preg_replace("/\[share(.*?)\](.*?)\[\/share\]/ism","\n\n$2\n\n",$body);
480
481         // At first convert the text to html
482         $html = bbcode($body, false, false);
483
484         // Then convert it to plain text
485         //$msg = trim($b['title']." \n\n".html2plain($html, 0, true));
486         $msg = trim(html2plain($html, 0, true));
487         $msg = html_entity_decode($msg,ENT_QUOTES,'UTF-8');
488
489         // Removing multiple newlines
490         while (strpos($msg, "\n\n\n") !== false)
491                 $msg = str_replace("\n\n\n", "\n\n", $msg);
492
493         // Removing multiple spaces
494         while (strpos($msg, "  ") !== false)
495                 $msg = str_replace("  ", " ", $msg);
496
497         // Removing URLs
498         $msg = preg_replace('/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/i', "", $msg);
499
500         $msg = trim($msg);
501
502         $link = '';
503         // look for bookmark-bbcode and handle it with priority
504         if(preg_match("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/is",$b['body'],$matches))
505                 $link = $matches[1];
506
507         $multiplelinks = (strpos($b['body'], "[bookmark") != strrpos($b['body'], "[bookmark"));
508
509         // If there is no bookmark element then take the first link
510         if ($link == '') {
511                 $links = collecturls($html);
512                 if (sizeof($links) > 0) {
513                         reset($links);
514                         $link = current($links);
515                 }
516                 $multiplelinks = (sizeof($links) > 1);
517         }
518
519         $msglink = "";
520         if ($multiplelinks)
521                 $msglink = $b["plink"];
522         else if ($link != "")
523                 $msglink = $link;
524         else if ($multipleimages)
525                 $msglink = $b["plink"];
526         else if ($image != "")
527                 $msglink = $image;
528
529         if (($msglink == "") and strlen($msg) > $max_char)
530                 $msglink = $b["plink"];
531
532         if (strlen($msglink) > 20)
533                 $msglink = short_link($msglink);
534
535         if (strlen(trim($msg." ".$msglink)) > $max_char) {
536                 $msg = substr($msg, 0, $max_char - (strlen($msglink)));
537                 $lastchar = substr($msg, -1);
538                 $msg = substr($msg, 0, -1);
539                 $pos = strrpos($msg, "\n");
540                 if ($pos > 0)
541                         $msg = substr($msg, 0, $pos);
542                 else if ($lastchar != "\n")
543                         $msg = substr($msg, 0, -3)."...";
544         }
545         $msg = str_replace("\n", " ", $msg);
546
547         // Removing multiple spaces - again
548         while (strpos($msg, "  ") !== false)
549                 $msg = str_replace("  ", " ", $msg);
550
551         return(array("msg"=>trim($msg." ".$msglink), "image"=>$image));
552 }
553
554 function statusnet_post_hook(&$a,&$b) {
555
556         /**
557          * Post to statusnet
558          */
559
560         if($b['deleted'] || $b['private'] || ($b['created'] !== $b['edited']))
561                 return;
562
563         if(! strstr($b['postopts'],'statusnet'))
564                 return;
565
566         // if posts comes from statusnet don't send it back
567         if($b['app'] == "StatusNet")
568                 return;
569
570         logger('statusnet post invoked');
571
572         load_pconfig($b['uid'], 'statusnet');
573
574         $api     = get_pconfig($b['uid'], 'statusnet', 'baseapi');
575         $ckey    = get_pconfig($b['uid'], 'statusnet', 'consumerkey');
576         $csecret = get_pconfig($b['uid'], 'statusnet', 'consumersecret');
577         $otoken  = get_pconfig($b['uid'], 'statusnet', 'oauthtoken');
578         $osecret = get_pconfig($b['uid'], 'statusnet', 'oauthsecret');
579         $intelligent_shortening = get_pconfig($b['uid'], 'statusnet', 'intelligent_shortening');
580
581         // Global setting overrides this
582         if (get_config('statusnet','intelligent_shortening'))
583                 $intelligent_shortening = get_config('statusnet','intelligent_shortening');
584
585         if($ckey && $csecret && $otoken && $osecret) {
586
587                 require_once('include/bbcode.php');
588                 $dent = new StatusNetOAuth($api,$ckey,$csecret,$otoken,$osecret);
589                 $max_char = $dent->get_maxlength(); // max. length for a dent
590                 // we will only work with up to two times the length of the dent
591                 // we can later send to StatusNet. This way we can "gain" some
592                 // information during shortening of potential links but do not
593                 // shorten all the links in a 200000 character long essay.
594
595                 $tempfile = "";
596                 $intelligent_shortening = get_config('statusnet','intelligent_shortening');
597                 if (!$intelligent_shortening) {
598                         if (! $b['title']=='') {
599                                 $tmp = $b['title'].": \n".$b['body'];
600         //                    $tmp = substr($tmp, 0, 4*$max_char);
601                         } else {
602                             $tmp = $b['body']; // substr($b['body'], 0, 3*$max_char);
603                         }
604                         // if [url=bla][img]blub.png[/img][/url] get blub.png
605                         $tmp = preg_replace( '/\[url\=(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)\]\[img\](\\w+.*?)\\[\\/img\]\\[\\/url\]/i', '$2', $tmp);
606                         // preserve links to images, videos and audios
607                         $tmp = preg_replace( '/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism', '$3', $tmp);
608                         $tmp = preg_replace( '/\[\\/?img(\\s+.*?\]|\])/i', '', $tmp);
609                         $tmp = preg_replace( '/\[\\/?video(\\s+.*?\]|\])/i', '', $tmp);
610                         $tmp = preg_replace( '/\[\\/?youtube(\\s+.*?\]|\])/i', '', $tmp);
611                         $tmp = preg_replace( '/\[\\/?vimeo(\\s+.*?\]|\])/i', '', $tmp);
612                         $tmp = preg_replace( '/\[\\/?audio(\\s+.*?\]|\])/i', '', $tmp);
613                         $linksenabled = get_pconfig($b['uid'],'statusnet','post_taglinks');
614                         // if a #tag is linked, don't send the [url] over to SN
615                         // that is, don't send if the option is not set in the 
616                         // connector settings
617                         if ($linksenabled=='0') {
618                                 // #-tags
619                                 $tmp = preg_replace( '/#\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', '#$2', $tmp);
620                                 // @-mentions
621                                 $tmp = preg_replace( '/@\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', '@$2', $tmp);
622                                 // recycle 1
623                                 $recycle = html_entity_decode("&#x2672; ", ENT_QUOTES, 'UTF-8');
624                                 $tmp = preg_replace( '/'.$recycle.'\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', $recycle.'$2', $tmp);
625                                 // recycle 2 (test)
626                                 $recycle = html_entity_decode("&#x25CC; ", ENT_QUOTES, 'UTF-8');
627                                 $tmp = preg_replace( '/'.$recycle.'\[url\=(\w+.*?)\](\w+.*?)\[\/url\]/i', $recycle.'$2', $tmp);
628                         }
629                         // preserve links to webpages
630                         $tmp = preg_replace( '/\[url\=(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)\](\w+.*?)\[\/url\]/i', '$2 $1', $tmp);
631                         $tmp = preg_replace( '/\[bookmark\=(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)\](\w+.*?)\[\/bookmark\]/i', '$2 $1', $tmp);
632                         // find all http or https links in the body of the entry and 
633                         // apply the shortener if the link is longer then 20 characters 
634                         if (( strlen($tmp)>$max_char ) && ( $max_char > 0 )) {
635                             preg_match_all ( '/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/i', $tmp, $allurls  );
636                             foreach ($allurls as $url) {
637                                 foreach ($url as $u) {
638                                     if (strlen($u)>20) {
639                                         $sl = short_link($u);
640                                         $tmp = str_replace( $u, $sl, $tmp );
641                                     }
642                                 }
643                             }
644                         }
645                         // ok, all the links we want to send out are save, now strip 
646                         // away the remaining bbcode
647                         //$msg = strip_tags(bbcode($tmp, false, false));
648                         $msg = bbcode($tmp, false, false);
649                         $msg = str_replace(array('<br>','<br />'),"\n",$msg);
650                         $msg = strip_tags($msg);
651
652                         // quotes not working - let's try this
653                         $msg = html_entity_decode($msg);
654
655                         if (( strlen($msg) > $max_char) && $max_char > 0) {
656                                 $shortlink = short_link( $b['plink'] );
657                                 // the new message will be shortened such that "... $shortlink"
658                                 // will fit into the character limit
659                                 $msg = nl2br(substr($msg, 0, $max_char-strlen($shortlink)-4));
660                                 $msg = str_replace(array('<br>','<br />'),' ',$msg);
661                                 $e = explode(' ', $msg);
662                                 //  remove the last word from the cut down message to 
663                                 //  avoid sending cut words to the MicroBlog
664                                 array_pop($e);
665                                 $msg = implode(' ', $e);
666                                 $msg .= '... ' . $shortlink;
667                         }
668
669                         $msg = trim($msg);
670                         $postdata = array('status' => $msg);
671                 } else {
672                         $msgarr = statusnet_shortenmsg($b, $max_char);
673                         $msg = $msgarr["msg"];
674                         $image = $msgarr["image"];
675                         if ($image != "") {
676                                 $imagedata = file_get_contents($image);
677                                 $tempfile = tempnam(get_config("system","temppath"), "upload");
678                                 file_put_contents($tempfile, $imagedata);
679                                 $postdata = array("status"=>$msg, "media"=>"@".$tempfile);
680                         } else
681                                 $postdata = array("status"=>$msg);
682                 }
683
684                 // and now dent it :-)
685                 if(strlen($msg)) {
686                     //$result = $dent->post('statuses/update', array('status' => $msg));
687                     $result = $dent->post('statuses/update', $postdata);
688                     logger('statusnet_post send, result: ' . print_r($result, true).
689                            "\nmessage: ".$msg, LOGGER_DEBUG."\nOriginal post: ".print_r($b, true)."\nPost Data: ".print_r($postdata, true));
690                     if ($result->error) {
691                         logger('Send to StatusNet failed: "' . $result->error . '"');
692                     }
693                 }
694                 if ($tempfile != "")
695                         unlink($tempfile);
696         }
697 }
698
699 function statusnet_plugin_admin_post(&$a){
700         
701         $sites = array();
702         
703         foreach($_POST['sitename'] as $id=>$sitename){
704                 $sitename=trim($sitename);
705                 $apiurl=trim($_POST['apiurl'][$id]);
706                 $secret=trim($_POST['secret'][$id]);
707                 $key=trim($_POST['key'][$id]);
708                 $applicationname = ((x($_POST, 'applicationname')) ? notags(trim($_POST['applicationname'][$id])):'');
709                 if ($sitename!="" &&
710                         $apiurl!="" &&
711                         $secret!="" &&
712                         $key!="" &&
713                         !x($_POST['delete'][$id])){
714                                 
715                                 $sites[] = Array(
716                                         'sitename' => $sitename,
717                                         'apiurl' => $apiurl,
718                                         'consumersecret' => $secret,
719                                         'consumerkey' => $key,
720                                         'applicationname' => $applicationname
721                                 );
722                 }
723         }
724         
725         $sites = set_config('statusnet','sites', $sites);
726         
727 }
728
729 function statusnet_plugin_admin(&$a, &$o){
730
731         $sites = get_config('statusnet','sites');
732         $sitesform=array();
733         if (is_array($sites)){
734                 foreach($sites as $id=>$s){
735                         $sitesform[] = Array(
736                                 'sitename' => Array("sitename[$id]", "Site name", $s['sitename'], ""),
737                                 'apiurl' => Array("apiurl[$id]", "Api url", $s['apiurl'], ""),
738                                 'secret' => Array("secret[$id]", "Secret", $s['consumersecret'], ""),
739                                 'key' => Array("key[$id]", "Key", $s['consumerkey'], ""),
740                                 'applicationname' => Array("applicationname[$id]", "Application name", $s['applicationname'], ""),
741                                 'delete' => Array("delete[$id]", "Delete", False , "Check to delete this preset"),
742                         );
743                 }
744         }
745         /* empty form to add new site */
746         $id++;
747         $sitesform[] = Array(
748                 'sitename' => Array("sitename[$id]", t("Site name"), "", ""),
749                 'apiurl' => Array("apiurl[$id]", t("API URL"), "", ""),
750                 'secret' => Array("secret[$id]", t("Consumer Secret"), "", ""),
751                 'key' => Array("key[$id]", t("Consumer Key"), "", ""),
752                 'applicationname' => Array("applicationname[$id]", t("Application name"), "", ""),
753         );
754
755         $t = get_markup_template( "admin.tpl", "addon/statusnet/" );
756         $o = replace_macros($t, array(
757                 '$submit' => t('Submit'),
758                 '$sites' => $sitesform,
759         ));
760 }
761
762 function statusnet_cron($a,$b) {
763         $last = get_config('statusnet','last_poll');
764
765         $poll_interval = intval(get_config('statusnet','poll_interval'));
766         if(! $poll_interval)
767                 $poll_interval = STATUSNET_DEFAULT_POLL_INTERVAL;
768
769         if($last) {
770                 $next = $last + ($poll_interval * 60);
771                 if($next > time()) {
772                         logger('statusnet: poll intervall not reached');
773                         return;
774                 }
775         }
776         logger('statusnet: cron_start');
777
778         $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'statusnet' AND `k` = 'mirror_posts' AND `v` = '1' ORDER BY RAND() ");
779         if(count($r)) {
780                 foreach($r as $rr) {
781                         logger('statusnet: fetching for user '.$rr['uid']);
782                         statusnet_fetchtimeline($a, $rr['uid']);
783                 }
784         }
785
786         logger('statusnet: cron_end');
787
788         set_config('statusnet','last_poll', time());
789 }
790
791 function statusnet_fetchtimeline($a, $uid) {
792         $ckey    = get_pconfig($uid, 'statusnet', 'consumerkey');
793         $csecret = get_pconfig($uid, 'statusnet', 'consumersecret');
794         $api     = get_pconfig($uid, 'statusnet', 'baseapi');
795         $otoken  = get_pconfig($uid, 'statusnet', 'oauthtoken');
796         $osecret = get_pconfig($uid, 'statusnet', 'oauthsecret');
797         $lastid  = get_pconfig($uid, 'statusnet', 'lastid');
798
799         //  get the application name for the SN app
800         //  1st try personal config, then system config and fallback to the 
801         //  hostname of the node if neither one is set. 
802         $application_name  = get_pconfig( $uid, 'statusnet', 'application_name');
803         if ($application_name == "")
804                 $application_name  = get_config('statusnet', 'application_name');
805         if ($application_name == "")
806                 $application_name = $a->get_hostname();
807
808         $connection = new StatusNetOAuth($api, $ckey,$csecret,$otoken,$osecret);
809
810         $parameters = array("exclude_replies" => true, "trim_user" => true, "contributor_details" => false, "include_rts" => false);
811
812         $first_time = ($lastid == "");
813
814         if ($lastid <> "")
815                 $parameters["since_id"] = $lastid;
816
817         $items = $connection->get('statuses/user_timeline', $parameters);
818         $posts = array_reverse($items);
819
820         if (count($posts)) {
821             foreach ($posts as $post) {
822                 if ($post->id > $lastid)
823                         $lastid = $post->id;
824
825                 if ($first_time)
826                         continue;
827
828                 if (is_object($post->retweeted_status))
829                         continue;
830
831                 if ($post->in_reply_to_status_id != "")
832                         continue;
833
834                 if (!strpos($post->source, $application_name)) {
835                         $_SESSION["authenticated"] = true;
836                         $_SESSION["uid"] = $uid;
837
838                         $_REQUEST["type"] = "wall";
839                         $_REQUEST["api_source"] = true;
840                         $_REQUEST["profile_uid"] = $uid;
841                         $_REQUEST["source"] = "StatusNet";
842
843                         //$_REQUEST["date"] = $post->created_at;
844
845                         $_REQUEST["body"] = $post->text;
846                         if (is_string($post->place->name))
847                                 $_REQUEST["location"] = $post->place->name;
848
849                         if (is_string($post->place->full_name))
850                                 $_REQUEST["location"] = $post->place->full_name;
851
852                         if (is_array($post->geo->coordinates))
853                                 $_REQUEST["coord"] = $post->geo->coordinates[0]." ".$post->geo->coordinates[1];
854
855                         if (is_array($post->coordinates->coordinates))
856                                 $_REQUEST["coord"] = $post->coordinates->coordinates[1]." ".$post->coordinates->coordinates[0];
857
858                         //print_r($_REQUEST);
859                         if ($_REQUEST["body"] != "") {
860                                 logger('statusnet: posting for user '.$uid);
861
862                                 require_once('mod/item.php');
863                                 item_post($a);
864                         }
865                 }
866             }
867         }
868         set_pconfig($uid, 'statusnet', 'lastid', $lastid);
869 }
870