diff options
author | Andrew Dolgov <[email protected]> | 2011-12-27 21:09:22 +0400 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2011-12-27 21:09:22 +0400 |
commit | 566faa14760112f81eea78fe441d5adc1b872da1 (patch) | |
tree | e3e9d12ef58f52324e503421fc28a3c5bed379a9 | |
parent | b4a9e560aabfb26e8a55e742853785e074feb672 (diff) |
implement neutral-format personal data export
-rwxr-xr-x | cache/export/.empty | 0 | ||||
-rw-r--r-- | classes/dlg.php | 18 | ||||
-rw-r--r-- | classes/pref_feeds.php | 19 | ||||
-rw-r--r-- | classes/rpc.php | 79 | ||||
-rw-r--r-- | include/sanity_check.php | 8 | ||||
-rw-r--r-- | js/prefs.js | 78 |
6 files changed, 195 insertions, 7 deletions
diff --git a/cache/export/.empty b/cache/export/.empty new file mode 100755 index 000000000..e69de29bb --- /dev/null +++ b/cache/export/.empty diff --git a/classes/dlg.php b/classes/dlg.php index bd18f54dc..2d1cb9cc5 100644 --- a/classes/dlg.php +++ b/classes/dlg.php @@ -16,6 +16,24 @@ class Dlg extends Protected_Handler { print "</dlg>"; } + function exportData() { + + print "<p style='text-align : center' id='export_status_message'>You need to prepare exported data first by clicking the button below.</p>"; + + print "<div align='center'>"; + print "<button dojoType=\"dijit.form.Button\" + onclick=\"dijit.byId('dataExportDlg').prepare()\">". + __('Prepare data')."</button>"; + + print "<button dojoType=\"dijit.form.Button\" + onclick=\"dijit.byId('dataExportDlg').hide()\">". + __('Close this window')."</button>"; + + print "</div>"; + + + } + function importOpml() { header("Content-Type: text/html"); # required for iframe diff --git a/classes/pref_feeds.php b/classes/pref_feeds.php index ed3783d67..f9ed1b554 100644 --- a/classes/pref_feeds.php +++ b/classes/pref_feeds.php @@ -1398,15 +1398,15 @@ class Pref_Feeds extends Protected_Handler { print "</div>"; # feeds pane - print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('OPML')."\">"; + print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Import and export')."\">"; - print "<p>" . __("Using OPML you can export and import your feeds, filters, labels and Tiny Tiny RSS settings.") . " "; + print "<h2>" . __("OPML") . "</h2>"; - print "<span class=\"insensitive\">" . __("Note: Only main settings profile can be migrated using OPML.") . "</span>"; + print "<h3>" . __("Import") . "</h3>"; - print "</p>"; + print "<p>" . __("Using OPML you can export and import your feeds, filters, labels and Tiny Tiny RSS settings.") . " "; - print "<h3>" . __("Import") . "</h3>"; + print __("Only main settings profile can be migrated using OPML.") . "</p>"; print "<br/><iframe id=\"upload_iframe\" name=\"upload_iframe\" onload=\"opmlImportComplete(this)\" @@ -1435,12 +1435,19 @@ class Pref_Feeds extends Protected_Handler { print "<p>".__('Your OPML can be published publicly and can be subscribed by anyone who knows the URL below.') . " "; - print "<span class=\"insensitive\">" . __("Note: Published OPML does not include your Tiny Tiny RSS settings, feeds that require authentication or feeds hidden from Popular feeds.") . "</span>" . "</p>"; + print __("Published OPML does not include your Tiny Tiny RSS settings, feeds that require authentication or feeds hidden from Popular feeds.") . "</p>"; print "<button dojoType=\"dijit.form.Button\" onclick=\"return displayDlg('pubOPMLUrl')\">". __('Display URL')."</button> "; + print "<h2>" . __("Data Export") . "</h2>"; + + print "<p>" . __("You can export your Starred and Archived articles using database-neutral format for safekeeping.") . "</p>"; + + print "<button dojoType=\"dijit.form.Button\" onclick=\"return exportData()\">". + __('Export my data')."</button> "; + print "</div>"; # pane if (strpos($_SERVER['HTTP_USER_AGENT'], "Firefox") !== false) { diff --git a/classes/rpc.php b/classes/rpc.php index 4cdaef935..497b1a55d 100644 --- a/classes/rpc.php +++ b/classes/rpc.php @@ -2,7 +2,7 @@ class RPC extends Protected_Handler { function csrf_ignore($method) { - $csrf_ignored = array("sanitycheck", "buttonplugin"); + $csrf_ignored = array("sanitycheck", "buttonplugin", "exportget"); return array_search($method, $csrf_ignored) !== false; } @@ -14,6 +14,83 @@ class RPC extends Protected_Handler { $_SESSION["prefs_cache"] = array(); } + function exportget() { + $exportname = CACHE_DIR . "/export/" . + sha1($_SESSION['uid'] . $_SESSION['login']) . ".xml"; + + if (file_exists($exportname)) { + header("Content-type: text/xml"); + header("Content-Disposition: attachment; filename=TinyTinyRSS_exported.xml"); + + echo file_get_contents($exportname); + } else { + echo "File not found."; + } + } + + function exportrun() { + $offset = (int) db_escape_string($_REQUEST['offset']); + $exported = 0; + $limit = 250; + + if ($offset < 10000 && is_writable(CACHE_DIR . "/export")) { + $result = db_query($this->link, "SELECT + ttrss_entries.guid, + ttrss_entries.title, + content, + marked, + published, + score, + note, + tag_cache, + label_cache, + ttrss_feeds.title AS feed_title, + ttrss_feeds.feed_url AS feed_url, + ttrss_entries.updated + FROM + ttrss_user_entries LEFT JOIN ttrss_feeds ON (ttrss_feeds.id = feed_id), + ttrss_entries + WHERE + (marked = true OR feed_id IS NULL) AND + ref_id = ttrss_entries.id AND + ttrss_user_entries.owner_uid = " . $_SESSION['uid'] . " + ORDER BY ttrss_entries.id LIMIT $limit OFFSET $offset"); + + $exportname = sha1($_SESSION['uid'] . $_SESSION['login']); + + if ($offset == 0) { + $fp = fopen(CACHE_DIR . "/export/$exportname.xml", "w"); + fputs($fp, "<articles schema-version=\"".SCHEMA_VERSION."\">"); + } else { + $fp = fopen(CACHE_DIR . "/export/$exportname.xml", "a"); + } + + if ($fp) { + + while ($line = db_fetch_assoc($result)) { + fputs($fp, "<article>"); + + foreach ($line as $k => $v) { + fputs($fp, "<$k><![CDATA[$v]]></$k>"); + } + + fputs($fp, "</article>"); + } + + $exported = db_num_rows($result); + + if ($exported < $limit && $exported > 0) { + fputs($fp, "</articles>"); + } + + fclose($fp); + } + + } + + print json_encode(array("exported" => $exported)); + } + function remprofiles() { $ids = explode(",", db_escape_string(trim($_REQUEST["ids"]))); diff --git a/include/sanity_check.php b/include/sanity_check.php index c12c09334..4fe28c307 100644 --- a/include/sanity_check.php +++ b/include/sanity_check.php @@ -21,6 +21,14 @@ $err_msg = "HTMLPurifier cache directory should be writable by anyone (chmod -R 777 $purifier_cache_dir)"; } + if (!is_writable(CACHE_DIR . "/images")) { + $err_msg = "Image cache is not writable (chmod -R 777 ".CACHE_DIR."/images)"; + } + + if (!is_writable(CACHE_DIR . "/export")) { + $err_msg = "Data export cache is not writable (chmod -R 777 ".CACHE_DIR."/export)"; + } + if (GENERATED_CONFIG_CHECK != EXPECTED_CONFIG_VERSION) { $err_msg = "Configuration option checker sanity_config.php is outdated, please recreate it using ./utils/regen_config_checks.sh"; } diff --git a/js/prefs.js b/js/prefs.js index 27598285c..7f9e44ad5 100644 --- a/js/prefs.js +++ b/js/prefs.js @@ -1935,3 +1935,81 @@ function showHelp() { exception_error("showHelp", e); } } + +function exportData() { + try { + + var query = "backend.php?op=dlg&method=exportData"; + + if (dijit.byId("dataExportDlg")) + dijit.byId("dataExportDlg").destroyRecursive(); + + var exported = 0; + + dialog = new dijit.Dialog({ + id: "dataExportDlg", + title: __("Export Data"), + style: "width: 600px", + prepare: function() { + + notify_progress("Loading, please wait..."); + + new Ajax.Request("backend.php", { + parameters: "?op=rpc&method=exportrun&offset=" + exported, + onComplete: function(transport) { + try { + var rv = JSON.parse(transport.responseText); + + if (rv && rv.exported != undefined) { + if (rv.exported > 0) { + + exported += rv.exported; + + $("export_status_message").innerHTML = + "<img src='images/indicator_tiny.gif'> " + + "Exported %d articles, please wait...".replace("%d", + exported); + + setTimeout('dijit.byId("dataExportDlg").prepare()', 2000); + + } else { + + $("export_status_message").innerHTML = + __("Finished, exported %d articles. You can download the data <a class='visibleLink' href='%u'>here</a>.") + .replace("%d", exported) + .replace("%u", "backend.php?op=rpc&subop=exportget"); + + exported = 0; + + } + + } else { + $("export_status_message").innerHTML = + "Error occured, could not export data."; + } + } catch (e) { + exception_error("exportData", e, transport.responseText); + } + + notify(''); + + } }); + + }, + execute: function() { + if (this.validate()) { + + + + } + }, + href: query}); + + dialog.show(); + + + } catch (e) { + exception_error("exportData", e); + } +} + |