diff --git a/backend.php b/backend.php index 308ade0a7..c4edd92ea 100644 --- a/backend.php +++ b/backend.php @@ -60,7 +60,7 @@ if (!($_SESSION["uid"] && validate_session($link)) && $op != "globalUpdateFeeds" && $op != "rss" && $op != "getUnread" && $op != "getProfiles" && - $op != "logout" && $op != "pubsub") { + $op != "fbexport" && $op != "logout" && $op != "pubsub") { header("Content-Type: text/plain"); print json_encode(array("error" => array("code" => 6))); @@ -121,6 +121,7 @@ require_once "modules/pref-filters.php"; require_once "modules/pref-labels.php"; require_once "modules/pref-users.php"; + require_once "modules/pref-instances.php"; $error = sanity_check($link); @@ -435,6 +436,10 @@ module_pref_feed_browser($link); break; // pref-feed-browser + case "pref-instances": + module_pref_instances($link); + break; // pref-instances + case "rss": $feed = db_escape_string($_REQUEST["id"]); $key = db_escape_string($_REQUEST["key"]); @@ -504,7 +509,7 @@ header("Content-type: text/html"); print __("Loading, please wait...") . " " . ""; - break; + break; // loading case "getProfiles": $login = db_escape_string($_REQUEST["login"]); @@ -529,7 +534,7 @@ $_SESSION = array(); } - break; + break; // getprofiles case "pubsub": $mode = db_escape_string($_REQUEST['hub_mode']); @@ -571,17 +576,48 @@ header('HTTP/1.0 404 Not Found'); } - break; + break; // pubsub case "logout": logout_user(); header("Location: tt-rss.php"); - break; + break; // logout + + case "fbexport": + + // TODO: change to _POST + $access_key = db_escape_string($_REQUEST["key"]); + + // TODO: rate limit checking using last_connected + $result = db_query($link, "SELECT id FROM ttrss_linked_instances + WHERE access_key = '$access_key'"); + + if (db_num_rows($result) == 1) { + + $instance_id = db_fetch_result($result, 0, "id"); + + $result = db_query($link, "SELECT feed_url, title, subscribers + FROM ttrss_feedbrowser_cache ORDER BY subscribers DESC LIMIT 100"); + + $feeds = array(); + + while ($line = db_fetch_assoc($result)) { + array_push($feeds, $line); + } + + db_query($link, "UPDATE ttrss_linked_instances SET last_connected = NOW(), + last_status_in = 1 WHERE id = '$instance_id'"); + + print json_encode(array("feeds" => $feeds)); + } else { + print json_encode(array("error" => array("code" => 6))); + } + break; // fbexport default: header("Content-Type: text/plain"); print json_encode(array("error" => array("code" => 7))); - break; + break; // fallback } // Select action according to $op value. // We close the connection to database. diff --git a/functions.php b/functions.php index 0da69094a..7fd9df780 100644 --- a/functions.php +++ b/functions.php @@ -358,7 +358,7 @@ } } - function fetch_file_contents($url, $type = false, $login = false, $pass = false) { + function fetch_file_contents($url, $type = false, $login = false, $pass = false, $post_query = false) { $login = urlencode($login); $pass = urlencode($pass); @@ -374,6 +374,11 @@ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + if ($post_query) { + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $post_query); + } + if ($login && $pass) curl_setopt($ch, CURLOPT_USERPWD, "$login:$pass"); @@ -7351,4 +7356,87 @@ return $query; } + + // Status codes: + // -1 - never connected + // 0 - no data received + // 1 - data received successfully + // 2 - did not receive valid data + // >10 - server error, code + 10 (e.g. 16 means server error 6) + + function get_linked_feeds($link, $instance_id = false) { + if ($instance_id) + $instance_qpart = "id = '$instance_id' AND "; + else + $instance_qpart = ""; + + if (DB_TYPE == "pgsql") { + $date_qpart = "last_connected < NOW() - INTERVAL '6 hours'"; + } else { + $date_qpart = "last_connected < DATE_SUB(NOW(), INTERVAL 6 HOUR)"; + } + + $result = db_query($link, "SELECT id, access_key, access_url FROM ttrss_linked_instances + WHERE $instance_qpart $date_qpart ORDER BY last_connected"); + + while ($line = db_fetch_assoc($result)) { + $id = $line['id']; + + _debug("Updating: " . $line['access_url'] . " ($id)"); + + $fetch_url = $line['access_url'] . '/backend.php?op=fbexport'; + $post_query = 'key=' . $line['access_key']; + + $feeds = fetch_file_contents($fetch_url, false, false, false, $post_query); + + if ($feeds) { + $feeds = json_decode($feeds, true); + + if ($feeds) { + if ($feeds['error']) { + $status = $feeds['error']['code'] + 10; + } else { + $status = 1; + + if (count($feeds['feeds']) > 0) { + + db_query($link, "DELETE FROM ttrss_linked_feeds + WHERE instance_id = '$id'"); + + foreach ($feeds['feeds'] as $feed) { + $feed_url = db_escape_string($feed['feed_url']); + $title = db_escape_string($feed['title']); + $subscribers = db_escape_string($feed['subscribers']); + + db_query($link, "INSERT INTO ttrss_linked_feeds + (feed_url, title, subscribers, instance_id, created, updated) + VALUES + ('$feed_url', '$title', '$subscribers', '$id', NOW(), NOW())"); + } + } else { + // received 0 feeds, this might indicate that + // the instance on the other hand is rebuilding feedbrowser cache + // we will try again later + + // TODO: maybe perform expiration based on updated here? + } + + _debug("Processed " . count($feeds['feeds']) . " feeds."); + } + } else { + $status = 2; + } + + } else { + $status = 0; + } + + _debug("Status: $status"); + + db_query($link, "UPDATE ttrss_linked_instances SET + last_status_out = '$status', last_connected = NOW() WHERE id = '$id'"); + + } + + } ?> diff --git a/modules/backend-rpc.php b/modules/backend-rpc.php index f21ca5814..75ce6886a 100644 --- a/modules/backend-rpc.php +++ b/modules/backend-rpc.php @@ -834,6 +834,13 @@ return; } + if ($subop == "genHash") { + $hash = sha1(uniqid(rand(), true)); + + print json_encode(array("hash" => $hash)); + return; + } + print json_encode(array("error" => array("code" => 7, "message" => "Unknown method: $subop"))); } diff --git a/modules/popup-dialog.php b/modules/popup-dialog.php index 2eb63af9d..6cb60eef4 100644 --- a/modules/popup-dialog.php +++ b/modules/popup-dialog.php @@ -990,6 +990,61 @@ type=\"submit\">". __('Close this window').""; print ""; + } + + if ($id == "addInstance") { + + print ""; + print ""; + + print "
".__("Instance")."
"; + + print "
"; + + /* URL */ + + print __("URL:") . " "; + + print ""; + + print "
"; + + $access_key = sha1(uniqid(rand(), true)); + + /* Access key */ + + print __("Access key:") . " "; + + print ""; + + print "

" . __("Use one access key for both linked instances."); + + print "

"; + + print "
+
+ +
+ +
"; + + return; + + + + } print ""; diff --git a/modules/pref-feeds.php b/modules/pref-feeds.php index f496c782f..4baba572a 100644 --- a/modules/pref-feeds.php +++ b/modules/pref-feeds.php @@ -1539,11 +1539,21 @@ } if ($mode == 1) { - $result = db_query($link, "SELECT feed_url, subscribers FROM + /* $result = db_query($link, "SELECT feed_url, subscribers FROM ttrss_feedbrowser_cache WHERE (SELECT COUNT(id) = 0 FROM ttrss_feeds AS tf WHERE tf.feed_url = ttrss_feedbrowser_cache.feed_url AND owner_uid = '$owner_uid') $search_qpart - ORDER BY subscribers DESC LIMIT $limit"); + ORDER BY subscribers DESC LIMIT $limit"); */ + + $result = db_query($link, "SELECT feed_url, title, SUM(subscribers) AS subscribers FROM + (SELECT feed_url, title, subscribers FROM ttrss_feedbrowser_cache UNION ALL + SELECT feed_url, title, subscribers FROM ttrss_linked_feeds) AS qqq + WHERE + (SELECT COUNT(id) = 0 FROM ttrss_feeds AS tf + WHERE tf.feed_url = qqq.feed_url + AND owner_uid = '$owner_uid') $search_qpart + GROUP BY feed_url, title ORDER BY subscribers DESC LIMIT $limit"); + } else if ($mode == 2) { $result = db_query($link, "SELECT *, (SELECT COUNT(*) FROM ttrss_user_entries WHERE @@ -1603,7 +1613,7 @@ $rv .= "
  • $check_box". - "$feed_icon $feed_url " . htmlspecialchars($details["title"]) . + "$feed_icon $feed_url " . htmlspecialchars($line["title"]) . " ($subscribers) $site_url
  • "; diff --git a/modules/pref-instances.php b/modules/pref-instances.php new file mode 100644 index 000000000..d3510c287 --- /dev/null +++ b/modules/pref-instances.php @@ -0,0 +1,193 @@ +"; + print ""; + print ""; + + print "
    ".__("Instance")."
    "; + + print "
    "; + + /* URL */ + + $access_url = htmlspecialchars(db_fetch_result($result, 0, "access_url")); + + print __("URL:") . " "; + + print ""; + + print "
    "; + + $access_key = htmlspecialchars(db_fetch_result($result, 0, "access_key")); + + /* Access key */ + + print __("Access key:") . " "; + + print ""; + + print "

    " . __("Use one access key for both linked instances."); + + print "

    "; + + print "
    +
    + +
    + +
    "; + + return; + } + + if ($subop == "editSave") { + $id = db_escape_string($_REQUEST["id"]); + $access_url = db_escape_string($_REQUEST["access_url"]); + $access_key = db_escape_string($_REQUEST["access_key"]); + + db_query($link, "UPDATE ttrss_linked_instances SET + access_key = '$access_key', access_url = '$access_url', + last_connected = '1970-01-01' + WHERE id = '$id'"); + + return; + } + + print "
    "; + print "
    "; + + print "
    "; + + $sort = db_escape_string($_REQUEST["sort"]); + + if (!$sort || $sort == "undefined") { + $sort = "access_url"; + } + + print "
    ". + "" . __('Select').""; + print "
    "; + print "
    ".__('All')."
    "; + print "
    ".__('None')."
    "; + print "
    "; + + print ""; + print ""; + print ""; + + print "
    "; #toolbar + + $result = db_query($link, "SELECT *, + (SELECT COUNT(*) FROM ttrss_linked_feeds + WHERE instance_id = ttrss_linked_instances.id) AS num_feeds + FROM ttrss_linked_instances + ORDER BY $sort"); + + print "

    " . __("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:"); + + print " (display url)"; + + print "

    "; + + print " + + + + + + "; + + $lnum = 0; + + while ($line = db_fetch_assoc($result)) { + $class = ($lnum % 2) ? "even" : "odd"; + + $id = $line['id']; + $this_row_id = "id=\"LIRR-$id\""; + + $line["last_connected"] = make_local_datetime($link, $line["last_connected"], false); + + print ""; + + print ""; + + $onclick = "onclick='editInstance($id, event)' title='".__('Click to edit')."'"; + + $access_key = mb_substr($line['access_key'], 0, 4) . '...' . + mb_substr($line['access_key'], -4); + + print ""; + print ""; + print ""; + print ""; + + print ""; + + ++$lnum; + } + + print "
     ".__('Instance URL')."".__('Access key')."".__('Last connected')."".__('Stored feeds')."
    " . htmlspecialchars($line['access_url']) . "" . htmlspecialchars($access_key) . "" . htmlspecialchars($line['last_connected']) . "" . htmlspecialchars($line['num_feeds']) . "
    "; + + print "

    "; #pane + print "
    "; #container + + } +?> diff --git a/prefs.js b/prefs.js index 5d8cca381..1cf294329 100644 --- a/prefs.js +++ b/prefs.js @@ -5,6 +5,16 @@ var hotkey_prefix_pressed = false; var seq = ""; +function instancelist_callback2(transport) { + try { + dijit.byId('instanceConfigTab').attr('content', transport.responseText); + selectTab("instanceConfig", true); + notify(""); + } catch (e) { + exception_error("instancelist_callback2", e); + } +} + function feedlist_callback2(transport) { try { dijit.byId('feedConfigTab').attr('content', transport.responseText); @@ -66,6 +76,14 @@ function updateFeedList(sort_key) { } }); } +function updateInstanceList(sort_key) { + new Ajax.Request("backend.php", { + parameters: "?op=pref-instances&sort=" + param_escape(sort_key), + onComplete: function(transport) { + instancelist_callback2(transport); + } }); +} + function updateUsersList(sort_key) { try { @@ -1760,3 +1778,160 @@ function insertSSLserial(value) { exception_error("insertSSLcerial", e); } } + +function getSelectedInstances() { + return getSelectedTableRowIds("prefInstanceList"); +} + +function addInstance() { + try { + var query = "backend.php?op=dlg&id=addInstance"; + + if (dijit.byId("instanceAddDlg")) + dijit.byId("instanceAddDlg").destroyRecursive(); + + dialog = new dijit.Dialog({ + id: "instanceAddDlg", + title: __("Link Instance"), + style: "width: 600px", + regenKey: function() { + new Ajax.Request("backend.php", { + parameters: "?op=rpc&subop=genHash", + onComplete: function(transport) { + var reply = JSON.parse(transport.responseText); + if (reply) + dijit.byId('instance_add_key').attr('value', reply.hash); + + } }); + }, + execute: function() { + if (this.validate()) { + console.warn(dojo.objectToQuery(this.attr('value'))); + + notify_progress('Saving data...', true); + new Ajax.Request("backend.php", { + parameters: dojo.objectToQuery(this.attr('value')), + onComplete: function(transport) { + dialog.hide(); + notify(''); + updateInstanceList(); + } }); + } + }, + href: query, + }); + + dialog.show(); + + } catch (e) { + exception_error("addInstance", e); + } +} + +function editInstance(id, event) { + try { + if (!event || !event.ctrlKey) { + + selectTableRows('prefInstanceList', 'none'); + selectTableRowById('LIRR-'+id, 'LICHK-'+id, true); + + var query = "backend.php?op=pref-instances&subop=edit&id=" + + param_escape(id); + + if (dijit.byId("instanceEditDlg")) + dijit.byId("instanceEditDlg").destroyRecursive(); + + dialog = new dijit.Dialog({ + id: "instanceEditDlg", + title: __("Edit Instance"), + style: "width: 600px", + regenKey: function() { + new Ajax.Request("backend.php", { + parameters: "?op=rpc&subop=genHash", + onComplete: function(transport) { + var reply = JSON.parse(transport.responseText); + if (reply) + dijit.byId('instance_edit_key').attr('value', reply.hash); + + } }); + }, + execute: function() { + if (this.validate()) { +// console.warn(dojo.objectToQuery(this.attr('value'))); + + notify_progress('Saving data...', true); + new Ajax.Request("backend.php", { + parameters: dojo.objectToQuery(this.attr('value')), + onComplete: function(transport) { + dialog.hide(); + notify(''); + updateInstanceList(); + } }); + } + }, + href: query, + }); + + dialog.show(); + + } else if (event.ctrlKey) { + var cb = $('LICHK-' + id); + cb.checked = !cb.checked; + toggleSelectRow(cb); + } + + + } catch (e) { + exception_error("editInstance", e); + } +} + +function removeSelectedInstances() { + try { + var sel_rows = getSelectedInstances(); + + if (sel_rows.length > 0) { + + var ok = confirm(__("Remove selected instances?")); + + if (ok) { + notify_progress("Removing selected instances..."); + + var query = "?op=pref-instances&subop=remove&ids="+ + param_escape(sel_rows.toString()); + + new Ajax.Request("backend.php", { + parameters: query, + onComplete: function(transport) { + notify(''); + updateInstanceList(); + } }); + } + + } else { + alert(__("No instances are selected.")); + } + + } catch (e) { + exception_error("removeInstance", e); + } +} + +function editSelectedInstance() { + var rows = getSelectedInstances(); + + if (rows.length == 0) { + alert(__("No instances are selected.")); + return; + } + + if (rows.length > 1) { + alert(__("Please select only one instance.")); + return; + } + + notify(""); + + editInstance(rows[0]); +} + diff --git a/prefs.php b/prefs.php index 5b70b2cad..11dab3977 100644 --- a/prefs.php +++ b/prefs.php @@ -103,7 +103,11 @@
    +
    +