]> git.wh0rd.org Git - tt-rss.git/blob - plugins/import_export/init.php
plugins/import_export: use PDO
[tt-rss.git] / plugins / import_export / init.php
1 <?php
2 class Import_Export extends Plugin implements IHandler {
3         private $host;
4
5         function init($host) {
6                 $this->host = $host;
7                 $this->pdo = Db::pdo();
8
9                 $host->add_hook($host::HOOK_PREFS_TAB, $this);
10                 $host->add_command("xml-import", "import articles from XML", $this, ":", "FILE");
11         }
12
13         function about() {
14                 return array(1.0,
15                         "Imports and exports user data using neutral XML format",
16                         "fox");
17         }
18
19         private function bool_to_sql_bool($s) {
20                 return $s ? 'true' : 'false';
21         }
22
23         function xml_import($args) {
24
25                 $filename = $args['xml_import'];
26
27                 if (!is_file($filename)) {
28                         print "error: input filename ($filename) doesn't exist.\n";
29                         return;
30                 }
31
32                 _debug("please enter your username:");
33
34                 $username = db_escape_string(trim(read_stdin()));
35
36                 _debug("importing $filename for user $username...\n");
37
38                 $sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE login = ?");
39                 $sth->execute([$username]);
40
41                 if ($sth->rowCount() == 0) {
42                         print "error: could not find user $username.\n";
43                         return;
44                 }
45
46                 $owner_uid = $sth->fetchColumn(0);
47
48                 $this->perform_data_import($filename, $owner_uid);
49         }
50
51         function save() {
52                 $example_value = db_escape_string($_POST["example_value"]);
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
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."));
67
68                 print "<p>";
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
89                 print "</form>";
90
91                 print "</p>";
92
93                 print "</div>"; # pane
94         }
95
96         function csrf_ignore($method) {
97                 return in_array($method, array("exportget"));
98         }
99
100         /**
101          * @SuppressWarnings(PHPMD.UnusedFormalParameter)
102          */
103         function before($method) {
104                 return $_SESSION["uid"] != false;
105         }
106
107         function after() {
108                 return true;
109         }
110
111         /**
112          * @SuppressWarnings(unused)
113          */
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
121                         $timestamp_suffix = date("Y-m-d", filemtime($exportname));
122
123                         if (function_exists('gzencode')) {
124                                 header("Content-Disposition: attachment; filename=TinyTinyRSS_exported_${timestamp_suffix}.xml.gz");
125                                 echo gzencode(file_get_contents($exportname));
126                         } else {
127                                 header("Content-Disposition: attachment; filename=TinyTinyRSS_exported_${timestamp_suffix}.xml");
128                                 echo file_get_contents($exportname);
129                         }
130                 } else {
131                         echo "File not found.";
132                 }
133         }
134
135         function exportrun() {
136                 $offset = (int) $_REQUEST['offset'];
137                 $exported = 0;
138                 $limit = 250;
139
140                 if ($offset < 10000 && is_writable(CACHE_DIR . "/export")) {
141                         $sth = $this->pdo->prepare("SELECT
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
161                                         ttrss_user_entries.owner_uid = ?
162                                 ORDER BY ttrss_entries.id LIMIT ? OFFSET ?");
163                         $sth->execute([$_SESSION['uid'], $limit, $offset]);
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
176                                 while ($line = $sth->fetch(PDO::FETCH_ASSOC)) {
177                                         fputs($fp, "<article>");
178
179                                         foreach ($line as $k => $v) {
180                                                 $v = str_replace("]]>", "]]]]><![CDATA[>", $v);
181                                                 fputs($fp, "<$k><![CDATA[$v]]></$k>");
182                                         }
183
184                                         fputs($fp, "</article>");
185                                 }
186
187                                 $exported = $sth->rowCount();
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
201         function perform_data_import($filename, $owner_uid) {
202
203                 $num_imported = 0;
204                 $num_processed = 0;
205                 $num_feeds_created = 0;
206
207                 libxml_disable_entity_loader(false);
208
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)
223                                 $doc = DOMDocument::loadXML($data);
224                 }
225
226                 libxml_disable_entity_loader(true);
227
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) {
257                                                 if ($child->nodeName == 'content') {
258                                                         $article[$child->nodeName] = db_escape_string($child->nodeValue, false);
259                                                 } else if ($child->nodeName == 'label_cache') {
260                                                         $article[$child->nodeName] = $child->nodeValue;
261                                                 } else {
262                                                         $article[$child->nodeName] = db_escape_string($child->nodeValue);
263                                                 }
264                                         }
265
266                                         //print_r($article);
267
268                                         if ($article['guid']) {
269
270                                                 ++$num_processed;
271
272                                                 //db_query("BEGIN");
273
274                                                 //print 'GUID:' . $article['guid'] . "\n";
275
276                                                 $sth = $this->pdo->prepare("SELECT id FROM ttrss_entries
277                                                         WHERE guid = ?");
278                                                 $sth->execute([$article['guid']]);
279
280                                                 if ($sth->rowCount() == 0) {
281
282                                                         $sth = $this->pdo->prepare(
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
297                                                                         (?,
298                                                                         ?,
299                                                                         ?,
300                                                                         ?,
301                                                                         ?,
302                                                                         ?,
303                                                                         false,
304                                                                         NOW(),
305                                                                         NOW(),
306                                                                         '',
307                                                                         '0',
308                                                                         '')");
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);
324                                                         }
325
326                                                 } else {
327                                                         $ref_id = $sth->fetchColumn(0);
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) {
340                                                                 $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
341                                                                         WHERE feed_url = ? AND owner_uid = ?");
342                                                                 $sth->execute([$feed_url, $owner_uid]);
343
344                                                                 if ($sth->rowCount() != 0) {
345                                                                         $feed = $sth->fetchColumn(0);
346                                                                 } else {
347                                                                         // try autocreating feed in Uncategorized...
348
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]);
352
353                                                                         $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
354                                                                                 WHERE feed_url = ? AND owner_uid = ?");
355                                                                         $sth->execute([$feed_url, $owner_uid]);
356
357                                                                         if ($sth->rowCount() != 0) {
358                                                                                 ++$num_feeds_created;
359
360                                                                                 $feed = $sth->fetchColumn(0);
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
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]);
375
376                                                         if ($sth->rowCount() == 0) {
377
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']));
380                                                                 $score = (int) $article['score'];
381
382                                                                 $tag_cache = $article['tag_cache'];
383                                                                 $note = $article['note'];
384
385                                                                 //print "Importing " . $article['title'] . "<br/>";
386
387                                                                 ++$num_imported;
388
389                                                                 $sth = $this->pdo->prepare(
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)
393                                                                         VALUES (?, ?, ?, false,
394                                                                                 NULL, ?, ?, ?, ?,
395                                                                                         '', '', ?)");
396                                                                 $sth->execute([$ref_id, $owner_uid, $feed, $marked, $published, $score, $tag_cache, $note]);
397
398                                                                 $label_cache = json_decode($article['label_cache'], true);
399
400                                                                 if (is_array($label_cache) && $label_cache["no-labels"] != 1) {
401                                                                         foreach ($label_cache as $label) {
402
403                                                                                 Labels::create($label[1],
404                                                                                         $label[2], $label[3], $owner_uid);
405
406                                                                                 Labels::add_article($ref_id, $label[1], $owner_uid);
407
408                                                                         }
409                                                                 }
410
411                                                                 //db_query("COMMIT");
412                                                         }
413                                                 }
414                                         }
415                                 }
416                         }
417
418                         print "<p>" .
419                                 __("Finished: ").
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).
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
455                 if ($_FILES['export_file']['error'] != 0) {
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 {
460
461                         $tmp_file = false;
462
463                         if (is_uploaded_file($_FILES['export_file']['tmp_name'])) {
464                                 $tmp_file = tempnam(CACHE_DIR . '/upload', 'export');
465
466                                 $result = move_uploaded_file($_FILES['export_file']['tmp_name'],
467                                         $tmp_file);
468
469                                 if (!$result) {
470                                         print_error(__("Unable to move uploaded file."));
471                                         return;
472                                 }
473                         } else {
474                                 print_error(__('Error: please upload OPML file.'));
475                                 return;
476                         }
477
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                         }
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
495         function api_version() {
496                 return 2;
497         }
498
499 }