]> git.wh0rd.org - tt-rss.git/blame - plugins/import_export/init.php
plugins/import_export: use PDO
[tt-rss.git] / plugins / import_export / init.php
CommitLineData
6c2637d9
AD
1<?php
2class Import_Export extends Plugin implements IHandler {
6c2637d9
AD
3 private $host;
4
d2a421e3 5 function init($host) {
6c2637d9 6 $this->host = $host;
785ffca6 7 $this->pdo = Db::pdo();
6c2637d9
AD
8
9 $host->add_hook($host::HOOK_PREFS_TAB, $this);
f58df872 10 $host->add_command("xml-import", "import articles from XML", $this, ":", "FILE");
6c2637d9
AD
11 }
12
d2a421e3 13 function about() {
7a866114 14 return array(1.0,
0ac22f29 15 "Imports and exports user data using neutral XML format",
7a866114
AD
16 "fox");
17 }
18
365f5c8b
AD
19 private function bool_to_sql_bool($s) {
20 return $s ? 'true' : 'false';
21 }
22
6c2637d9 23 function xml_import($args) {
6c2637d9 24
f58df872 25 $filename = $args['xml_import'];
6c2637d9
AD
26
27 if (!is_file($filename)) {
28 print "error: input filename ($filename) doesn't exist.\n";
29 return;
30 }
31
f58df872
AD
32 _debug("please enter your username:");
33
a42c55f0 34 $username = db_escape_string(trim(read_stdin()));
f58df872 35
6c2637d9
AD
36 _debug("importing $filename for user $username...\n");
37
785ffca6
LD
38 $sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE login = ?");
39 $sth->execute([$username]);
6c2637d9 40
785ffca6 41 if ($sth->rowCount() == 0) {
6c2637d9
AD
42 print "error: could not find user $username.\n";
43 return;
44 }
45
785ffca6 46 $owner_uid = $sth->fetchColumn(0);
6c2637d9 47
a42c55f0 48 $this->perform_data_import($filename, $owner_uid);
6c2637d9
AD
49 }
50
51 function save() {
a42c55f0 52 $example_value = db_escape_string($_POST["example_value"]);
6c2637d9
AD
53
54 echo "Value set to $example_value (not really)";
55 }
56
57 function get_prefs_js() {
58 return file_get_contents(dirname(__FILE__) . "/import_export.js");
59 }
60
61 function hook_prefs_tab($args) {
62 if ($args != "prefFeeds") return;
63
64 print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Import and export')."\">";
65
11334fdf 66 print_notice(__("You can export and import your Starred and Archived articles for safekeeping or when migrating between tt-rss instances of same version."));
6c2637d9 67
11334fdf 68 print "<p>";
6c2637d9
AD
69
70 print "<button dojoType=\"dijit.form.Button\" onclick=\"return exportData()\">".
71 __('Export my data')."</button> ";
72
73 print "<hr>";
74
75 print "<iframe id=\"data_upload_iframe\"
76 name=\"data_upload_iframe\" onload=\"dataImportComplete(this)\"
77 style=\"width: 400px; height: 100px; display: none;\"></iframe>";
78
79 print "<form name=\"import_form\" style='display : block' target=\"data_upload_iframe\"
80 enctype=\"multipart/form-data\" method=\"POST\"
81 action=\"backend.php\">
82 <input id=\"export_file\" name=\"export_file\" type=\"file\">&nbsp;
83 <input type=\"hidden\" name=\"op\" value=\"pluginhandler\">
84 <input type=\"hidden\" name=\"plugin\" value=\"import_export\">
85 <input type=\"hidden\" name=\"method\" value=\"dataimport\">
86 <button dojoType=\"dijit.form.Button\" onclick=\"return importData();\" type=\"submit\">" .
87 __('Import') . "</button>";
88
b229a184 89 print "</form>";
6c2637d9 90
11334fdf
AD
91 print "</p>";
92
6c2637d9
AD
93 print "</div>"; # pane
94 }
95
96 function csrf_ignore($method) {
97 return in_array($method, array("exportget"));
98 }
99
21ce7d9e
AD
100 /**
101 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
102 */
6c2637d9
AD
103 function before($method) {
104 return $_SESSION["uid"] != false;
105 }
106
107 function after() {
108 return true;
109 }
110
21ce7d9e
AD
111 /**
112 * @SuppressWarnings(unused)
113 */
6c2637d9
AD
114 function exportget() {
115 $exportname = CACHE_DIR . "/export/" .
116 sha1($_SESSION['uid'] . $_SESSION['login']) . ".xml";
117
118 if (file_exists($exportname)) {
119 header("Content-type: text/xml");
120
77e81006
AD
121 $timestamp_suffix = date("Y-m-d", filemtime($exportname));
122
6c2637d9 123 if (function_exists('gzencode')) {
77e81006 124 header("Content-Disposition: attachment; filename=TinyTinyRSS_exported_${timestamp_suffix}.xml.gz");
6c2637d9
AD
125 echo gzencode(file_get_contents($exportname));
126 } else {
77e81006 127 header("Content-Disposition: attachment; filename=TinyTinyRSS_exported_${timestamp_suffix}.xml");
6c2637d9
AD
128 echo file_get_contents($exportname);
129 }
130 } else {
131 echo "File not found.";
132 }
133 }
134
135 function exportrun() {
785ffca6 136 $offset = (int) $_REQUEST['offset'];
6c2637d9
AD
137 $exported = 0;
138 $limit = 250;
139
140 if ($offset < 10000 && is_writable(CACHE_DIR . "/export")) {
785ffca6 141 $sth = $this->pdo->prepare("SELECT
6c2637d9
AD
142 ttrss_entries.guid,
143 ttrss_entries.title,
144 content,
145 marked,
146 published,
147 score,
148 note,
149 link,
150 tag_cache,
151 label_cache,
152 ttrss_feeds.title AS feed_title,
153 ttrss_feeds.feed_url AS feed_url,
154 ttrss_entries.updated
155 FROM
156 ttrss_user_entries LEFT JOIN ttrss_feeds ON (ttrss_feeds.id = feed_id),
157 ttrss_entries
158 WHERE
159 (marked = true OR feed_id IS NULL) AND
160 ref_id = ttrss_entries.id AND
785ffca6
LD
161 ttrss_user_entries.owner_uid = ?
162 ORDER BY ttrss_entries.id LIMIT ? OFFSET ?");
163 $sth->execute([$_SESSION['uid'], $limit, $offset]);
6c2637d9
AD
164
165 $exportname = sha1($_SESSION['uid'] . $_SESSION['login']);
166
167 if ($offset == 0) {
168 $fp = fopen(CACHE_DIR . "/export/$exportname.xml", "w");
169 fputs($fp, "<articles schema-version=\"".SCHEMA_VERSION."\">");
170 } else {
171 $fp = fopen(CACHE_DIR . "/export/$exportname.xml", "a");
172 }
173
174 if ($fp) {
175
785ffca6 176 while ($line = $sth->fetch(PDO::FETCH_ASSOC)) {
6c2637d9
AD
177 fputs($fp, "<article>");
178
179 foreach ($line as $k => $v) {
c541d3a5 180 $v = str_replace("]]>", "]]]]><![CDATA[>", $v);
6c2637d9
AD
181 fputs($fp, "<$k><![CDATA[$v]]></$k>");
182 }
183
184 fputs($fp, "</article>");
185 }
186
785ffca6 187 $exported = $sth->rowCount();
6c2637d9
AD
188
189 if ($exported < $limit && $exported > 0) {
190 fputs($fp, "</articles>");
191 }
192
193 fclose($fp);
194 }
195
196 }
197
198 print json_encode(array("exported" => $exported));
199 }
200
a42c55f0 201 function perform_data_import($filename, $owner_uid) {
6c2637d9
AD
202
203 $num_imported = 0;
204 $num_processed = 0;
205 $num_feeds_created = 0;
206
4262e001 207 libxml_disable_entity_loader(false);
208
6c2637d9
AD
209 $doc = @DOMDocument::load($filename);
210
211 if (!$doc) {
212 $contents = file_get_contents($filename);
213
214 if ($contents) {
215 $data = @gzuncompress($contents);
216 }
217
218 if (!$data) {
219 $data = @gzdecode($contents);
220 }
221
222 if ($data)
2c51facf 223 $doc = DOMDocument::loadXML($data);
6c2637d9
AD
224 }
225
4262e001 226 libxml_disable_entity_loader(true);
227
6c2637d9
AD
228 if ($doc) {
229
230 $xpath = new DOMXpath($doc);
231
232 $container = $doc->firstChild;
233
234 if ($container && $container->hasAttribute('schema-version')) {
235 $schema_version = $container->getAttribute('schema-version');
236
237 if ($schema_version != SCHEMA_VERSION) {
238 print "<p>" .__("Could not import: incorrect schema version.") . "</p>";
239 return;
240 }
241
242 } else {
243 print "<p>" . __("Could not import: unrecognized document format.") . "</p>";
244 return;
245 }
246
247 $articles = $xpath->query("//article");
248
249 foreach ($articles as $article_node) {
250 if ($article_node->childNodes) {
251
252 $ref_id = 0;
253
254 $article = array();
255
256 foreach ($article_node->childNodes as $child) {
ee4c4602
AD
257 if ($child->nodeName == 'content') {
258 $article[$child->nodeName] = db_escape_string($child->nodeValue, false);
259 } else if ($child->nodeName == 'label_cache') {
6c2637d9 260 $article[$child->nodeName] = $child->nodeValue;
ee4c4602
AD
261 } else {
262 $article[$child->nodeName] = db_escape_string($child->nodeValue);
263 }
6c2637d9
AD
264 }
265
266 //print_r($article);
267
268 if ($article['guid']) {
269
270 ++$num_processed;
271
a42c55f0 272 //db_query("BEGIN");
6c2637d9
AD
273
274 //print 'GUID:' . $article['guid'] . "\n";
275
785ffca6
LD
276 $sth = $this->pdo->prepare("SELECT id FROM ttrss_entries
277 WHERE guid = ?");
278 $sth->execute([$article['guid']]);
6c2637d9 279
785ffca6 280 if ($sth->rowCount() == 0) {
6c2637d9 281
785ffca6 282 $sth = $this->pdo->prepare(
6c2637d9
AD
283 "INSERT INTO ttrss_entries
284 (title,
285 guid,
286 link,
287 updated,
288 content,
289 content_hash,
290 no_orig_date,
291 date_updated,
292 date_entered,
293 comments,
294 num_comments,
295 author)
296 VALUES
785ffca6
LD
297 (?,
298 ?,
299 ?,
300 ?,
301 ?,
302 ?,
6c2637d9
AD
303 false,
304 NOW(),
305 NOW(),
306 '',
307 '0',
308 '')");
785ffca6
LD
309 $sth->execute([
310 $article['title'],
311 $article['guid'],
312 $article['link'],
313 $article['updated'],
314 $article['content'],
315 sha1($article['content'])
316 ]);
317
318 $sth = $this->pdo->prepare("SELECT id FROM ttrss_entries
319 WHERE guid = ?");
320 $sth->execute([$article['guid']]);
321
322 if ($sth->rowCount() != 0) {
323 $ref_id = $sth->fetchColumn(0);
6c2637d9
AD
324 }
325
326 } else {
785ffca6 327 $ref_id = $sth->fetchColumn(0);
6c2637d9
AD
328 }
329
330 //print "Got ref ID: $ref_id\n";
331
332 if ($ref_id) {
333
334 $feed_url = $article['feed_url'];
335 $feed_title = $article['feed_title'];
336
337 $feed = 'NULL';
338
339 if ($feed_url && $feed_title) {
785ffca6
LD
340 $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
341 WHERE feed_url = ? AND owner_uid = ?");
342 $sth->execute([$feed_url, $owner_uid]);
6c2637d9 343
785ffca6
LD
344 if ($sth->rowCount() != 0) {
345 $feed = $sth->fetchColumn(0);
6c2637d9
AD
346 } else {
347 // try autocreating feed in Uncategorized...
348
785ffca6
LD
349 $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds (owner_uid,
350 feed_url, title) VALUES (?, ?, ?)");
351 $sth->execute([$owner_uid, $feed_url, $feed_title]);
6c2637d9 352
785ffca6
LD
353 $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
354 WHERE feed_url = ? AND owner_uid = ?");
355 $sth->execute([$feed_url, $owner_uid]);
6c2637d9 356
785ffca6 357 if ($sth->rowCount() != 0) {
6c2637d9
AD
358 ++$num_feeds_created;
359
785ffca6 360 $feed = $sth->fetchColumn(0);
6c2637d9
AD
361 }
362 }
363 }
364
365 if ($feed != 'NULL')
366 $feed_qpart = "feed_id = $feed";
367 else
368 $feed_qpart = "feed_id IS NULL";
369
370 //print "$ref_id / $feed / " . $article['title'] . "\n";
371
785ffca6
LD
372 $sth = $this->pdo->prepare("SELECT int_id FROM ttrss_user_entries
373 WHERE ref_id = ? AND owner_uid = ? AND ?");
374 $sth->execute([$ref_id, $owner_uid, $feed_qpart]);
6c2637d9 375
785ffca6 376 if ($sth->rowCount() == 0) {
6c2637d9 377
365f5c8b
AD
378 $marked = $this->bool_to_sql_bool(sql_bool_to_bool($article['marked']));
379 $published = $this->bool_to_sql_bool(sql_bool_to_bool($article['published']));
6c2637d9
AD
380 $score = (int) $article['score'];
381
382 $tag_cache = $article['tag_cache'];
6c2637d9
AD
383 $note = $article['note'];
384
385 //print "Importing " . $article['title'] . "<br/>";
386
387 ++$num_imported;
388
785ffca6 389 $sth = $this->pdo->prepare(
6c2637d9
AD
390 "INSERT INTO ttrss_user_entries
391 (ref_id, owner_uid, feed_id, unread, last_read, marked,
392 published, score, tag_cache, label_cache, uuid, note)
785ffca6
LD
393 VALUES (?, ?, ?, false,
394 NULL, ?, ?, ?, ?,
395 '', '', ?)");
396 $sth->execute([$ref_id, $owner_uid, $feed, $marked, $published, $score, $tag_cache, $note]);
6c2637d9 397
ee4c4602 398 $label_cache = json_decode($article['label_cache'], true);
6c2637d9
AD
399
400 if (is_array($label_cache) && $label_cache["no-labels"] != 1) {
401 foreach ($label_cache as $label) {
402
7c9b5a3f 403 Labels::create($label[1],
6c2637d9
AD
404 $label[2], $label[3], $owner_uid);
405
7c9b5a3f 406 Labels::add_article($ref_id, $label[1], $owner_uid);
6c2637d9
AD
407
408 }
409 }
410
a42c55f0 411 //db_query("COMMIT");
6c2637d9
AD
412 }
413 }
414 }
415 }
416 }
417
418 print "<p>" .
f58df872 419 __("Finished: ").
d3b0e348
AD
420 vsprintf(_ngettext("%d article processed, ", "%d articles processed, ", $num_processed), $num_processed).
421 vsprintf(_ngettext("%d imported, ", "%d imported, ", $num_imported), $num_imported).
422 vsprintf(_ngettext("%d feed created.", "%d feeds created.", $num_feeds_created), $num_feeds_created).
6c2637d9
AD
423 "</p>";
424
425 } else {
426
427 print "<p>" . __("Could not load XML document.") . "</p>";
428
429 }
430 }
431
432 function exportData() {
433
434 print "<p style='text-align : center' id='export_status_message'>You need to prepare exported data first by clicking the button below.</p>";
435
436 print "<div align='center'>";
437 print "<button dojoType=\"dijit.form.Button\"
438 onclick=\"dijit.byId('dataExportDlg').prepare()\">".
439 __('Prepare data')."</button>";
440
441 print "<button dojoType=\"dijit.form.Button\"
442 onclick=\"dijit.byId('dataExportDlg').hide()\">".
443 __('Close this window')."</button>";
444
445 print "</div>";
446
447
448 }
449
450 function dataImport() {
451 header("Content-Type: text/html"); # required for iframe
452
453 print "<div style='text-align : center'>";
454
b229a184 455 if ($_FILES['export_file']['error'] != 0) {
1a322ff3
AD
456 print_error(T_sprintf("Upload failed with error code %d (%s)",
457 $_FILES['export_file']['error'],
458 get_upload_error_message($_FILES['export_file']['error'])));
459 } else {
b229a184 460
1a322ff3 461 $tmp_file = false;
6c2637d9 462
1a322ff3
AD
463 if (is_uploaded_file($_FILES['export_file']['tmp_name'])) {
464 $tmp_file = tempnam(CACHE_DIR . '/upload', 'export');
6c2637d9 465
1a322ff3
AD
466 $result = move_uploaded_file($_FILES['export_file']['tmp_name'],
467 $tmp_file);
b229a184 468
1a322ff3
AD
469 if (!$result) {
470 print_error(__("Unable to move uploaded file."));
471 return;
472 }
473 } else {
474 print_error(__('Error: please upload OPML file.'));
b229a184
AD
475 return;
476 }
6c2637d9 477
1a322ff3
AD
478 if (is_file($tmp_file)) {
479 $this->perform_data_import($tmp_file, $_SESSION['uid']);
480 unlink($tmp_file);
481 } else {
482 print_error(__('No file uploaded.'));
483 return;
484 }
6c2637d9
AD
485 }
486
487 print "<button dojoType=\"dijit.form.Button\"
488 onclick=\"dijit.byId('dataImportDlg').hide()\">".
489 __('Close this window')."</button>";
490
491 print "</div>";
492
493 }
494
106a3de9
AD
495 function api_version() {
496 return 2;
497 }
6c2637d9 498
365f5c8b 499}