]>
git.wh0rd.org - tt-rss.git/blob - plugins/instances/init.php
2 class Instances
extends Plugin
implements IHandler
{
5 private $status_codes = array (
6 0 => "Connection failed" ,
8 2 => "Invalid object received" ,
9 16 => "Access denied" );
13 "Support for linking tt-rss instances together and sharing popular feeds." ,
18 function init ( $host ) {
21 $host -> add_hook ( $host :: HOOK_PREFS_TABS
, $this );
22 $host -> add_handler ( "pref-instances" , "*" , $this );
23 $host -> add_handler ( "public" , "fbexport" , $this );
24 $host -> add_command ( "get-feeds" , "receive popular feeds from linked instances" , $this );
25 $host -> add_hook ( $host :: HOOK_UPDATE_TASK
, $this );
28 function hook_update_task ( $args ) {
29 _debug ( "Get linked feeds..." );
30 $this -> get_linked_feeds ();
34 // -1 - never connected
35 // 0 - no data received
36 // 1 - data received successfully
37 // 2 - did not receive valid data
38 // >10 - server error, code + 10 (e.g. 16 means server error 6)
40 function get_linked_feeds ( $instance_id = false ) {
42 $instance_qpart = "id = ' $instance_id ' AND " ;
46 if ( DB_TYPE
== "pgsql" ) {
47 $date_qpart = "last_connected < NOW() - INTERVAL '6 hours'" ;
49 $date_qpart = "last_connected < DATE_SUB(NOW(), INTERVAL 6 HOUR)" ;
52 $result = db_query ( "SELECT id, access_key, access_url FROM ttrss_linked_instances
53 WHERE $instance_qpart $date_qpart ORDER BY last_connected" );
55 while ( $line = db_fetch_assoc ( $result )) {
58 _debug ( "Updating: " . $line [ 'access_url' ] . " ( $id )" );
60 $fetch_url = $line [ 'access_url' ] . '/public.php?op=fbexport' ;
61 $post_query = 'key=' . $line [ 'access_key' ];
63 $feeds = fetch_file_contents ( $fetch_url , false , false , false , $post_query );
65 // try doing it the old way
67 $fetch_url = $line [ 'access_url' ] . '/backend.php?op=fbexport' ;
68 $feeds = fetch_file_contents ( $fetch_url , false , false , false , $post_query );
72 $feeds = json_decode ( $feeds , true );
75 if ( $feeds [ 'error' ]) {
76 $status = $feeds [ 'error' ][ 'code' ] +
10 ;
80 db_query ( "DELETE FROM ttrss_linked_feeds
81 WHERE instance_id = ' $id '" );
86 if ( count ( $feeds [ 'feeds' ]) > 0 ) {
88 db_query ( "DELETE FROM ttrss_linked_feeds
89 WHERE instance_id = ' $id '" );
91 foreach ( $feeds [ 'feeds' ] as $feed ) {
92 $feed_url = db_escape_string ( $feed [ 'feed_url' ]);
93 $title = db_escape_string ( $feed [ 'title' ]);
94 $subscribers = db_escape_string ( $feed [ 'subscribers' ]);
95 $site_url = db_escape_string ( $feed [ 'site_url' ]);
97 db_query ( "INSERT INTO ttrss_linked_feeds
98 (feed_url, site_url, title, subscribers, instance_id, created, updated)
100 (' $feed_url ', ' $site_url ', ' $title ', ' $subscribers ', ' $id ', NOW(), NOW())" );
103 // received 0 feeds, this might indicate that
104 // the instance on the other hand is rebuilding feedbrowser cache
105 // we will try again later
107 // TODO: maybe perform expiration based on updated here?
110 _debug ( "Processed " . count ( $feeds [ 'feeds' ]) . " feeds." );
120 _debug ( "Status: $status " );
122 db_query ( "UPDATE ttrss_linked_instances SET
123 last_status_out = ' $status ', last_connected = NOW() WHERE id = ' $id '" );
129 function get_feeds () {
130 $this -> get_linked_feeds ( false );
133 function get_prefs_js () {
134 return file_get_contents ( dirname ( __FILE__
) . "/instances.js" );
137 function hook_prefs_tabs ( $args ) {
138 if ( $_SESSION [ "access_level" ] >= 10 || SINGLE_USER_MODE
) {
139 ?
>< div id
= "instanceConfigTab" dojoType
= "dijit.layout.ContentPane"
140 href
= "backend.php?op=pref-instances"
141 title
= "<?php echo __('Linked') ?>" ></ div
>< ?php
145 function csrf_ignore ( $method ) {
146 $csrf_ignored = array ( "index" , "edit" );
148 return array_search ( $method , $csrf_ignored ) !== false ;
151 function before ( $method ) {
152 if ( $_SESSION [ "uid" ]) {
153 if ( $_SESSION [ "access_level" ] < 10 ) {
154 print __ ( "Your access level is insufficient to open this tab." );
167 $ids = db_escape_string ( $_REQUEST [ 'ids' ]);
169 db_query ( "DELETE FROM ttrss_linked_instances WHERE
174 $id = db_escape_string ( $_REQUEST [ "id" ]);
175 $access_url = db_escape_string ( $_REQUEST [ "access_url" ]);
176 $access_key = db_escape_string ( $_REQUEST [ "access_key" ]);
180 $result = db_query ( "SELECT id FROM ttrss_linked_instances
181 WHERE access_url = ' $access_url '" );
183 if ( db_num_rows ( $result ) == 0 ) {
184 db_query ( "INSERT INTO ttrss_linked_instances
185 (access_url, access_key, last_connected, last_status_in, last_status_out)
187 (' $access_url ', ' $access_key ', '1970-01-01', -1, -1)" );
195 $id = db_escape_string ( $_REQUEST [ "id" ]);
197 $result = db_query ( "SELECT * FROM ttrss_linked_instances WHERE
200 print "<input dojoType= \" dijit.form.TextBox \" style= \" display : none \" name= \" id \" value= \" $id\" >" ;
201 print "<input dojoType= \" dijit.form.TextBox \" style= \" display : none \" name= \" op \" value= \" pref-instances \" >" ;
202 print "<input dojoType= \" dijit.form.TextBox \" style= \" display : none \" name= \" method \" value= \" editSave \" >" ;
204 print "<div class= \" dlgSec \" >" . __ ( "Instance" ). "</div>" ;
206 print "<div class= \" dlgSecCont \" >" ;
210 $access_url = htmlspecialchars ( db_fetch_result ( $result , 0 , "access_url" ));
212 print __ ( "URL:" ) . " " ;
214 print "<input dojoType= \" dijit.form.ValidationTextBox \" required= \" 1 \"
215 placeHolder= \" " . __ ( "Instance URL" ). " \"
216 regExp='^(http|https)://.*'
217 style= \" font-size : 16px; width: 20em \" name= \" access_url \"
218 value= \" $access_url\" >" ;
222 $access_key = htmlspecialchars ( db_fetch_result ( $result , 0 , "access_key" ));
226 print __ ( "Access key:" ) . " " ;
228 print "<input dojoType= \" dijit.form.ValidationTextBox \" required= \" 1 \"
229 placeHolder= \" " . __ ( "Access key" ). " \" regExp='\w{40}'
230 style= \" width: 20em \" name= \" access_key \" id= \" instance_edit_key \"
231 value= \" $access_key\" >" ;
233 print "<p class='insensitive'>" . __ ( "Use one access key for both linked instances." );
237 print "<div class= \" dlgButtons \" >
238 <div style='float : left'>
239 <button dojoType= \" dijit.form.Button \"
240 onclick= \" return dijit.byId('instanceEditDlg').regenKey() \" >" .
241 __ ( 'Generate new key' ). "</button>
243 <button dojoType= \" dijit.form.Button \"
244 onclick= \" return dijit.byId('instanceEditDlg').execute() \" >" .
245 __ ( 'Save' ). "</button>
246 <button dojoType= \" dijit.form.Button \"
247 onclick= \" return dijit.byId('instanceEditDlg').hide() \"\" >" .
248 __ ( 'Cancel' ). "</button></div>" ;
252 function editSave () {
253 $id = db_escape_string ( $_REQUEST [ "id" ]);
254 $access_url = db_escape_string ( $_REQUEST [ "access_url" ]);
255 $access_key = db_escape_string ( $_REQUEST [ "access_key" ]);
257 db_query ( "UPDATE ttrss_linked_instances SET
258 access_key = ' $access_key ', access_url = ' $access_url ',
259 last_connected = '1970-01-01'
266 if (! function_exists ( 'curl_init' )) {
267 print "<div style='padding : 1em'>" ;
268 print_error ( "This functionality requires CURL functions. Please enable CURL in your PHP configuration (you might also want to disable open_basedir in php.ini) and reload this page." );
272 print "<div id= \" pref-instance-wrap \" dojoType= \" dijit.layout.BorderContainer \" gutters= \" false \" >" ;
273 print "<div id= \" pref-instance-header \" dojoType= \" dijit.layout.ContentPane \" region= \" top \" >" ;
275 print "<div id= \" pref-instance-toolbar \" dojoType= \" dijit.Toolbar \" >" ;
277 $sort = db_escape_string ( $_REQUEST [ "sort" ]);
279 if (! $sort ||
$sort == "undefined" ) {
280 $sort = "access_url" ;
283 print "<div dojoType= \" dijit.form.DropDownButton \" >" .
284 "<span>" . __ ( 'Select' ). "</span>" ;
285 print "<div dojoType= \" dijit.Menu \" style= \" display: none; \" >" ;
286 print "<div onclick= \" selectTableRows('prefInstanceList', 'all') \"
287 dojoType= \" dijit.MenuItem \" >" . __ ( 'All' ). "</div>" ;
288 print "<div onclick= \" selectTableRows('prefInstanceList', 'none') \"
289 dojoType= \" dijit.MenuItem \" >" . __ ( 'None' ). "</div>" ;
290 print "</div></div>" ;
292 print "<button dojoType= \" dijit.form.Button \" onclick= \" addInstance() \" >" . __ ( 'Link instance' ). "</button>" ;
293 print "<button dojoType= \" dijit.form.Button \" onclick= \" editSelectedInstance() \" >" . __ ( 'Edit' ). "</button>" ;
294 print "<button dojoType= \" dijit.form.Button \" onclick= \" removeSelectedInstances() \" >" . __ ( 'Remove' ). "</button>" ;
296 print "</div>" ; #toolbar
298 $result = db_query ( "SELECT *,
299 (SELECT COUNT(*) FROM ttrss_linked_feeds
300 WHERE instance_id = ttrss_linked_instances.id) AS num_feeds
301 FROM ttrss_linked_instances
304 print "<p class= \" insensitive \" style='margin-left : 1em;'>" . __ ( "You can connect other instances of Tiny Tiny RSS to this one to share Popular feeds. Link to this instance of Tiny Tiny RSS by using this URL:" );
306 print " <a href= \" # \" onclick= \" alert('" . htmlspecialchars ( get_self_url_prefix ()). "') \" >(display url)</a>" ;
308 print "<p><table width='100%' id='prefInstanceList' class='prefInstanceList' cellspacing='0'>" ;
310 print "<tr class= \" title \" >
311 <td align='center' width= \" 5% \" > </td>
312 <td width=''><a href= \" # \" onclick= \" updateInstanceList('access_url') \" >" . __ ( 'Instance URL' ). "</a></td>
313 <td width='20%'><a href= \" # \" onclick= \" updateInstanceList('access_key') \" >" . __ ( 'Access key' ). "</a></td>
314 <td width='10%'><a href= \" # \" onclick= \" updateUsersList('last_connected') \" >" . __ ( 'Last connected' ). "</a></td>
315 <td width='10%'><a href= \" # \" onclick= \" updateUsersList('last_status_out') \" >" . __ ( 'Status' ). "</a></td>
316 <td width='10%'><a href= \" # \" onclick= \" updateUsersList('num_feeds') \" >" . __ ( 'Stored feeds' ). "</a></td>
321 while ( $line = db_fetch_assoc ( $result )) {
322 $class = ( $lnum %
2 ) ?
"even" : "odd" ;
325 $this_row_id = "id= \" LIRR- $id\" " ;
327 $line [ "last_connected" ] = make_local_datetime ( $line [ "last_connected" ], false );
329 print "<tr class= \" $class\" $this_row_id >" ;
331 print "<td align='center'><input onclick='toggleSelectRow(this);'
332 type= \" checkbox \" id= \" LICHK- $id\" ></td>" ;
334 $onclick = "onclick='editInstance( $id , event)' title='" . __ ( 'Click to edit' ). "'" ;
336 $access_key = mb_substr ( $line [ 'access_key' ], 0 , 4 ) . '...' .
337 mb_substr ( $line [ 'access_key' ], - 4 );
339 print "<td $onclick >" . htmlspecialchars ( $line [ 'access_url' ]) . "</td>" ;
340 print "<td $onclick >" . htmlspecialchars ( $access_key ) . "</td>" ;
341 print "<td $onclick >" . htmlspecialchars ( $line [ 'last_connected' ]) . "</td>" ;
342 print "<td $onclick >" . $this -> status_codes
[ $line [ 'last_status_out' ]] . "</td>" ;
343 print "<td $onclick >" . htmlspecialchars ( $line [ 'num_feeds' ]) . "</td>" ;
352 print "</div>" ; #pane
354 PluginHost
:: getInstance ()-> run_hooks ( PluginHost
:: HOOK_PREFS_TAB
,
355 "hook_prefs_tab" , "prefInstances" );
357 print "</div>" ; #container
361 function fbexport () {
363 $access_key = db_escape_string ( $_POST [ "key" ]);
365 // TODO: rate limit checking using last_connected
366 $result = db_query ( "SELECT id FROM ttrss_linked_instances
367 WHERE access_key = ' $access_key '" );
369 if ( db_num_rows ( $result ) == 1 ) {
371 $instance_id = db_fetch_result ( $result , 0 , "id" );
373 $result = db_query ( "SELECT feed_url, site_url, title, subscribers
374 FROM ttrss_feedbrowser_cache ORDER BY subscribers DESC LIMIT 100" );
378 while ( $line = db_fetch_assoc ( $result )) {
379 array_push ( $feeds , $line );
382 db_query ( "UPDATE ttrss_linked_instances SET
383 last_status_in = 1 WHERE id = ' $instance_id '" );
385 print json_encode ( array ( "feeds" => $feeds ));
387 print json_encode ( array ( "error" => array ( "code" => 6 )));
391 function addInstance () {
392 print "<input dojoType= \" dijit.form.TextBox \" style= \" display : none \" name= \" op \" value= \" pref-instances \" >" ;
393 print "<input dojoType= \" dijit.form.TextBox \" style= \" display : none \" name= \" method \" value= \" add \" >" ;
395 print "<div class= \" dlgSec \" >" . __ ( "Instance" ). "</div>" ;
397 print "<div class= \" dlgSecCont \" >" ;
401 print __ ( "URL:" ) . " " ;
403 print "<input dojoType= \" dijit.form.ValidationTextBox \" required= \" 1 \"
404 placeHolder= \" " . __ ( "Instance URL" ). " \"
405 regExp='^(http|https)://.*'
406 style= \" font-size : 16px; width: 20em \" name= \" access_url \" >" ;
410 $access_key = sha1 ( uniqid ( rand (), true ));
414 print __ ( "Access key:" ) . " " ;
416 print "<input dojoType= \" dijit.form.ValidationTextBox \" required= \" 1 \"
417 placeHolder= \" " . __ ( "Access key" ). " \" regExp='\w{40}'
418 style= \" width: 20em \" name= \" access_key \" id= \" instance_add_key \"
419 value= \" $access_key\" >" ;
421 print "<p class='insensitive'>" . __ ( "Use one access key for both linked instances." );
425 print "<div class= \" dlgButtons \" >
426 <div style='float : left'>
427 <button dojoType= \" dijit.form.Button \"
428 onclick= \" return dijit.byId('instanceAddDlg').regenKey() \" >" .
429 __ ( 'Generate new key' ). "</button>
431 <button dojoType= \" dijit.form.Button \"
432 onclick= \" return dijit.byId('instanceAddDlg').execute() \" >" .
433 __ ( 'Create link' ). "</button>
434 <button dojoType= \" dijit.form.Button \"
435 onclick= \" return dijit.byId('instanceAddDlg').hide() \"\" >" .
436 __ ( 'Cancel' ). "</button></div>" ;
442 $hash = sha1 ( uniqid ( rand (), true ));
444 print json_encode ( array ( "hash" => $hash ));
447 function api_version () {