1 | #!/usr/bin/env php |
||
2 | <?php |
||
3 | set_include_path(dirname(__FILE__) ."/include" . PATH_SEPARATOR . |
||
4 | get_include_path()); |
||
5 | |||
6 | define('DISABLE_SESSIONS', true); |
||
7 | |||
8 | chdir(dirname(__FILE__)); |
||
9 | |||
10 | require_once "autoload.php"; |
||
11 | require_once "functions.php"; |
||
12 | require_once "config.php"; |
||
13 | require_once "sanity_check.php"; |
||
14 | require_once "db.php"; |
||
15 | require_once "db-prefs.php"; |
||
16 | |||
17 | function cleanup_tags($days = 14, $limit = 1000) { |
||
18 | |||
19 | $days = (int) $days; |
||
20 | |||
21 | if (DB_TYPE == "pgsql") { |
||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
22 | $interval_query = "date_updated < NOW() - INTERVAL '$days days'"; |
||
23 | } else if (DB_TYPE == "mysql") { |
||
24 | $interval_query = "date_updated < DATE_SUB(NOW(), INTERVAL $days DAY)"; |
||
25 | } |
||
26 | |||
27 | $tags_deleted = 0; |
||
28 | |||
29 | $pdo = Db::pdo(); |
||
30 | |||
31 | while ($limit > 0) { |
||
32 | $limit_part = 500; |
||
33 | |||
34 | $sth = $pdo->prepare("SELECT ttrss_tags.id AS id |
||
35 | FROM ttrss_tags, ttrss_user_entries, ttrss_entries |
||
36 | WHERE post_int_id = int_id AND $interval_query AND |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
37 | ref_id = ttrss_entries.id AND tag_cache != '' LIMIT ?"); |
||
38 | $sth->bindValue(1, $limit_part, PDO::PARAM_INT); |
||
39 | $sth->execute(); |
||
40 | |||
41 | $ids = array(); |
||
42 | |||
43 | while ($line = $sth->fetch()) { |
||
44 | array_push($ids, $line['id']); |
||
45 | } |
||
46 | |||
47 | if (count($ids) > 0) { |
||
48 | $ids = join(",", $ids); |
||
49 | |||
50 | $usth = $pdo->query("DELETE FROM ttrss_tags WHERE id IN ($ids)"); |
||
51 | $tags_deleted = $usth->rowCount(); |
||
52 | } else { |
||
53 | break; |
||
54 | } |
||
55 | |||
56 | $limit -= $limit_part; |
||
57 | } |
||
58 | |||
59 | return $tags_deleted; |
||
60 | } |
||
61 | |||
62 | if (!defined('PHP_EXECUTABLE')) |
||
63 | define('PHP_EXECUTABLE', '/usr/bin/php'); |
||
64 | |||
65 | $pdo = Db::pdo(); |
||
66 | |||
67 | init_plugins(); |
||
68 | |||
69 | $longopts = array("feeds", |
||
70 | "daemon", |
||
71 | "daemon-loop", |
||
72 | "send-digests", |
||
73 | "task:", |
||
74 | "cleanup-tags", |
||
75 | "quiet", |
||
76 | "log:", |
||
77 | "log-level:", |
||
78 | "indexes", |
||
79 | "pidlock:", |
||
80 | "update-schema", |
||
81 | "convert-filters", |
||
82 | "force-update", |
||
83 | "gen-search-idx", |
||
84 | "list-plugins", |
||
85 | "debug-feed:", |
||
86 | "force-refetch", |
||
87 | "force-rehash", |
||
88 | "help"); |
||
89 | |||
90 | foreach (PluginHost::getInstance()->get_commands() as $command => $data) { |
||
91 | array_push($longopts, $command . $data["suffix"]); |
||
92 | } |
||
93 | |||
94 | $options = getopt("", $longopts); |
||
95 | |||
96 | if (!is_array($options)) { |
||
0 ignored issues
–
show
|
|||
97 | die("error: getopt() failed. ". |
||
98 | "Most probably you are using PHP CGI to run this script ". |
||
99 | "instead of required PHP CLI. Check tt-rss wiki page on updating feeds for ". |
||
100 | "additional information.\n"); |
||
101 | } |
||
102 | |||
103 | if (count($options) == 0 && !defined('STDIN')) { |
||
104 | ?> |
||
105 | <!DOCTYPE html> |
||
106 | <html> |
||
107 | <head> |
||
108 | <title>Tiny Tiny RSS data update script.</title> |
||
109 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> |
||
110 | </head> |
||
111 | |||
112 | <body> |
||
113 | <h1><?php echo __("Tiny Tiny RSS data update script.") ?></h1> |
||
114 | |||
115 | <?php print_error("Please run this script from the command line. Use option \"--help\" to display command help if this error is displayed erroneously."); ?> |
||
116 | |||
117 | </body></html> |
||
118 | <?php |
||
119 | exit; |
||
120 | } |
||
121 | |||
122 | if (count($options) == 0 || isset($options["help"]) ) { |
||
123 | print "Tiny Tiny RSS data update script.\n\n"; |
||
124 | print "Options:\n"; |
||
125 | print " --feeds - update feeds\n"; |
||
126 | print " --daemon - start single-process update daemon\n"; |
||
127 | print " --task N - create lockfile using this task id\n"; |
||
128 | print " --cleanup-tags - perform tags table maintenance\n"; |
||
129 | print " --quiet - don't output messages to stdout\n"; |
||
130 | print " --log FILE - log messages to FILE\n"; |
||
131 | print " --log-level N - log verbosity level\n"; |
||
132 | print " --indexes - recreate missing schema indexes\n"; |
||
133 | print " --update-schema - update database schema\n"; |
||
134 | print " --gen-search-idx - generate basic PostgreSQL fulltext search index\n"; |
||
135 | print " --convert-filters - convert type1 filters to type2\n"; |
||
136 | print " --send-digests - send pending email digests\n"; |
||
137 | print " --force-update - force update of all feeds\n"; |
||
138 | print " --list-plugins - list all available plugins\n"; |
||
139 | print " --debug-feed N - perform debug update of feed N\n"; |
||
140 | print " --force-refetch - debug update: force refetch feed data\n"; |
||
141 | print " --force-rehash - debug update: force rehash articles\n"; |
||
142 | print " --help - show this help\n"; |
||
143 | print "Plugin options:\n"; |
||
144 | |||
145 | foreach (PluginHost::getInstance()->get_commands() as $command => $data) { |
||
146 | $args = $data['arghelp']; |
||
147 | printf(" --%-19s - %s\n", "$command $args", $data["description"]); |
||
148 | } |
||
149 | |||
150 | return; |
||
151 | } |
||
152 | |||
153 | if (!isset($options['daemon'])) { |
||
154 | require_once "errorhandler.php"; |
||
155 | } |
||
156 | |||
157 | if (!isset($options['update-schema'])) { |
||
158 | $schema_version = get_schema_version(); |
||
159 | |||
160 | if ($schema_version != SCHEMA_VERSION) { |
||
161 | die("Schema version is wrong, please upgrade the database (--update-schema).\n"); |
||
162 | } |
||
163 | } |
||
164 | |||
165 | Debug::set_enabled(true); |
||
166 | |||
167 | if (isset($options["log-level"])) { |
||
168 | Debug::set_loglevel((int)$options["log-level"]); |
||
169 | } |
||
170 | |||
171 | if (isset($options["log"])) { |
||
172 | Debug::set_quiet(isset($options['quiet'])); |
||
173 | Debug::set_logfile($options["log"]); |
||
174 | Debug::log("Logging to " . $options["log"]); |
||
175 | } else { |
||
176 | if (isset($options['quiet'])) { |
||
177 | Debug::set_loglevel(Debug::$LOG_DISABLED); |
||
178 | } |
||
179 | } |
||
180 | |||
181 | if (!isset($options["daemon"])) { |
||
182 | $lock_filename = "update.lock"; |
||
183 | } else { |
||
184 | $lock_filename = "update_daemon.lock"; |
||
185 | } |
||
186 | |||
187 | if (isset($options["task"])) { |
||
188 | Debug::log("Using task id " . $options["task"]); |
||
189 | $lock_filename = $lock_filename . "-task_" . $options["task"]; |
||
190 | } |
||
191 | |||
192 | if (isset($options["pidlock"])) { |
||
193 | $my_pid = $options["pidlock"]; |
||
194 | $lock_filename = "update_daemon-$my_pid.lock"; |
||
195 | |||
196 | } |
||
197 | |||
198 | Debug::log("Lock: $lock_filename"); |
||
199 | |||
200 | $lock_handle = make_lockfile($lock_filename); |
||
201 | $must_exit = false; |
||
202 | |||
203 | if (isset($options["task"]) && isset($options["pidlock"])) { |
||
204 | $waits = $options["task"] * 5; |
||
205 | Debug::log("Waiting before update ($waits)"); |
||
206 | sleep($waits); |
||
207 | } |
||
208 | |||
209 | // Try to lock a file in order to avoid concurrent update. |
||
210 | if (!$lock_handle) { |
||
0 ignored issues
–
show
|
|||
211 | die("error: Can't create lockfile ($lock_filename). ". |
||
212 | "Maybe another update process is already running.\n"); |
||
213 | } |
||
214 | |||
215 | if (isset($options["force-update"])) { |
||
216 | Debug::log("marking all feeds as needing update..."); |
||
217 | |||
218 | $pdo->query( "UPDATE ttrss_feeds SET |
||
219 | last_update_started = '1970-01-01', last_updated = '1970-01-01'"); |
||
220 | } |
||
221 | |||
222 | if (isset($options["feeds"])) { |
||
223 | RSSUtils::update_daemon_common(); |
||
224 | RSSUtils::housekeeping_common(true); |
||
225 | |||
226 | PluginHost::getInstance()->run_hooks(PluginHost::HOOK_UPDATE_TASK, "hook_update_task", $op); |
||
227 | } |
||
228 | |||
229 | if (isset($options["daemon"])) { |
||
230 | while (true) { |
||
231 | $quiet = (isset($options["quiet"])) ? "--quiet" : ""; |
||
232 | $log = isset($options['log']) ? '--log '.$options['log'] : ''; |
||
233 | $log_level = isset($options['log-level']) ? '--log-level '.$options['log-level'] : ''; |
||
234 | |||
235 | passthru(PHP_EXECUTABLE . " " . $argv[0] ." --daemon-loop $quiet $log $log_level"); |
||
236 | |||
237 | // let's enforce a minimum spawn interval as to not forkbomb the host |
||
238 | $spawn_interval = max(60, DAEMON_SLEEP_INTERVAL); |
||
0 ignored issues
–
show
|
|||
239 | |||
240 | Debug::log("Sleeping for $spawn_interval seconds..."); |
||
241 | sleep($spawn_interval); |
||
242 | } |
||
243 | } |
||
244 | |||
245 | if (isset($options["daemon-loop"])) { |
||
246 | if (!make_stampfile('update_daemon.stamp')) { |
||
247 | Debug::log("warning: unable to create stampfile\n"); |
||
248 | } |
||
249 | |||
250 | RSSUtils::update_daemon_common(isset($options["pidlock"]) ? 50 : DAEMON_FEED_LIMIT); |
||
0 ignored issues
–
show
|
|||
251 | |||
252 | if (!isset($options["pidlock"]) || $options["task"] == 0) |
||
253 | RSSUtils::housekeeping_common(true); |
||
254 | |||
255 | PluginHost::getInstance()->run_hooks(PluginHost::HOOK_UPDATE_TASK, "hook_update_task", $op); |
||
256 | } |
||
257 | |||
258 | if (isset($options["cleanup-tags"])) { |
||
259 | $rc = cleanup_tags( 14, 50000); |
||
260 | Debug::log("$rc tags deleted.\n"); |
||
261 | } |
||
262 | |||
263 | if (isset($options["indexes"])) { |
||
264 | Debug::log("PLEASE BACKUP YOUR DATABASE BEFORE PROCEEDING!"); |
||
265 | Debug::log("Type 'yes' to continue."); |
||
266 | |||
267 | if (read_stdin() != 'yes') |
||
268 | exit; |
||
269 | |||
270 | Debug::log("clearing existing indexes..."); |
||
271 | |||
272 | if (DB_TYPE == "pgsql") { |
||
0 ignored issues
–
show
|
|||
273 | $sth = $pdo->query( "SELECT relname FROM |
||
274 | pg_catalog.pg_class WHERE relname LIKE 'ttrss_%' |
||
275 | AND relname NOT LIKE '%_pkey' |
||
276 | AND relkind = 'i'"); |
||
277 | } else { |
||
278 | $sth = $pdo->query( "SELECT index_name,table_name FROM |
||
279 | information_schema.statistics WHERE index_name LIKE 'ttrss_%'"); |
||
280 | } |
||
281 | |||
282 | while ($line = $sth->fetch()) { |
||
283 | if (DB_TYPE == "pgsql") { |
||
284 | $statement = "DROP INDEX " . $line["relname"]; |
||
285 | Debug::log($statement); |
||
286 | } else { |
||
287 | $statement = "ALTER TABLE ". |
||
288 | $line['table_name']." DROP INDEX ".$line['index_name']; |
||
289 | Debug::log($statement); |
||
290 | } |
||
291 | $pdo->query($statement); |
||
292 | } |
||
293 | |||
294 | Debug::log("reading indexes from schema for: " . DB_TYPE); |
||
295 | |||
296 | $fp = fopen("schema/ttrss_schema_" . DB_TYPE . ".sql", "r"); |
||
297 | if ($fp) { |
||
298 | while ($line = fgets($fp)) { |
||
299 | $matches = array(); |
||
300 | |||
301 | if (preg_match("/^create index ([^ ]+) on ([^ ]+)$/i", $line, $matches)) { |
||
302 | $index = $matches[1]; |
||
303 | $table = $matches[2]; |
||
304 | |||
305 | $statement = "CREATE INDEX $index ON $table"; |
||
306 | |||
307 | Debug::log($statement); |
||
308 | $pdo->query($statement); |
||
309 | } |
||
310 | } |
||
311 | fclose($fp); |
||
312 | } else { |
||
313 | Debug::log("unable to open schema file."); |
||
314 | } |
||
315 | Debug::log("all done."); |
||
316 | } |
||
317 | |||
318 | if (isset($options["convert-filters"])) { |
||
319 | Debug::log("WARNING: this will remove all existing type2 filters."); |
||
320 | Debug::log("Type 'yes' to continue."); |
||
321 | |||
322 | if (read_stdin() != 'yes') |
||
323 | exit; |
||
324 | |||
325 | Debug::log("converting filters..."); |
||
326 | |||
327 | $pdo->query("DELETE FROM ttrss_filters2"); |
||
328 | |||
329 | $res = $pdo->query("SELECT * FROM ttrss_filters ORDER BY id"); |
||
330 | |||
331 | while ($line = $res->fetch()) { |
||
332 | $owner_uid = $line["owner_uid"]; |
||
333 | |||
334 | // date filters are removed |
||
335 | if ($line["filter_type"] != 5) { |
||
336 | $filter = array(); |
||
337 | |||
338 | if (sql_bool_to_bool($line["cat_filter"])) { |
||
339 | $feed_id = "CAT:" . (int)$line["cat_id"]; |
||
340 | } else { |
||
341 | $feed_id = (int)$line["feed_id"]; |
||
342 | } |
||
343 | |||
344 | $filter["enabled"] = $line["enabled"] ? "on" : "off"; |
||
345 | $filter["rule"] = array( |
||
346 | json_encode(array( |
||
347 | "reg_exp" => $line["reg_exp"], |
||
348 | "feed_id" => $feed_id, |
||
349 | "filter_type" => $line["filter_type"]))); |
||
350 | |||
351 | $filter["action"] = array( |
||
352 | json_encode(array( |
||
353 | "action_id" => $line["action_id"], |
||
354 | "action_param_label" => $line["action_param"], |
||
355 | "action_param" => $line["action_param"]))); |
||
356 | |||
357 | // Oh god it's full of hacks |
||
358 | |||
359 | $_REQUEST = $filter; |
||
360 | $_SESSION["uid"] = $owner_uid; |
||
361 | |||
362 | $filters = new Pref_Filters($_REQUEST); |
||
363 | $filters->add(); |
||
364 | } |
||
365 | } |
||
366 | |||
367 | } |
||
368 | |||
369 | if (isset($options["update-schema"])) { |
||
370 | Debug::log("Checking for updates (" . DB_TYPE . ")..."); |
||
371 | |||
372 | $updater = new DbUpdater(DB_TYPE, SCHEMA_VERSION); |
||
373 | |||
374 | if ($updater->isUpdateRequired()) { |
||
375 | Debug::log("Schema update required, version " . $updater->getSchemaVersion() . " to " . SCHEMA_VERSION); |
||
376 | |||
377 | if (DB_TYPE == "mysql") |
||
378 | Debug::Log("READ THIS: Due to MySQL limitations, your database is not completely protected while updating.\n". |
||
379 | "Errors may put it in an inconsistent state requiring manual rollback.\nBACKUP YOUR DATABASE BEFORE CONTINUING."); |
||
380 | else |
||
381 | Debug::log("WARNING: please backup your database before continuing."); |
||
382 | |||
383 | Debug::log("Type 'yes' to continue."); |
||
384 | |||
385 | if (read_stdin() != 'yes') |
||
386 | exit; |
||
387 | |||
388 | Debug::log("Performing updates to version " . SCHEMA_VERSION); |
||
389 | |||
390 | for ($i = $updater->getSchemaVersion() + 1; $i <= SCHEMA_VERSION; $i++) { |
||
391 | Debug::log("* Updating to version $i..."); |
||
392 | |||
393 | $result = $updater->performUpdateTo($i, false); |
||
394 | |||
395 | if ($result) { |
||
396 | Debug::log("* Completed."); |
||
397 | } else { |
||
398 | Debug::log("One of the updates failed. Either retry the process or perform updates manually."); |
||
399 | return; |
||
400 | } |
||
401 | |||
402 | } |
||
403 | } else { |
||
404 | Debug::log("Update not required."); |
||
405 | } |
||
406 | |||
407 | } |
||
408 | |||
409 | if (isset($options["gen-search-idx"])) { |
||
410 | echo "Generating search index (stemming set to English)...\n"; |
||
411 | |||
412 | $res = $pdo->query("SELECT COUNT(id) AS count FROM ttrss_entries WHERE tsvector_combined IS NULL"); |
||
413 | $row = $res->fetch(); |
||
414 | $count = $row['count']; |
||
415 | |||
416 | print "Articles to process: $count.\n"; |
||
417 | |||
418 | $limit = 500; |
||
419 | $processed = 0; |
||
420 | |||
421 | $sth = $pdo->prepare("SELECT id, title, content FROM ttrss_entries WHERE |
||
422 | tsvector_combined IS NULL ORDER BY id LIMIT ?"); |
||
423 | $sth->execute([$limit]); |
||
424 | |||
425 | $usth = $pdo->prepare("UPDATE ttrss_entries |
||
426 | SET tsvector_combined = to_tsvector('english', ?) WHERE id = ?"); |
||
427 | |||
428 | while (true) { |
||
429 | |||
430 | while ($line = $sth->fetch()) { |
||
431 | $tsvector_combined = mb_substr(strip_tags($line["title"] . " " . $line["content"]), 0, 1000000); |
||
432 | |||
433 | $usth->execute([$tsvector_combined, $line['id']]); |
||
434 | |||
435 | $processed++; |
||
436 | } |
||
437 | |||
438 | print "Processed $processed articles...\n"; |
||
439 | |||
440 | if ($processed < $limit) { |
||
441 | echo "All done.\n"; |
||
442 | break; |
||
443 | } |
||
444 | } |
||
445 | } |
||
446 | |||
447 | if (isset($options["list-plugins"])) { |
||
448 | $tmppluginhost = new PluginHost(); |
||
449 | $tmppluginhost->load_all($tmppluginhost::KIND_ALL, false); |
||
450 | $enabled = array_map("trim", explode(",", PLUGINS)); |
||
0 ignored issues
–
show
|
|||
451 | |||
452 | echo "List of all available plugins:\n"; |
||
453 | |||
454 | foreach ($tmppluginhost->get_plugins() as $name => $plugin) { |
||
455 | $about = $plugin->about(); |
||
456 | |||
457 | $status = $about[3] ? "system" : "user"; |
||
458 | |||
459 | if (in_array($name, $enabled)) $name .= "*"; |
||
460 | |||
461 | printf("%-50s %-10s v%.2f (by %s)\n%s\n\n", |
||
462 | $name, $status, $about[0], $about[2], $about[1]); |
||
463 | } |
||
464 | |||
465 | echo "Plugins marked by * are currently enabled for all users.\n"; |
||
466 | |||
467 | } |
||
468 | |||
469 | if (isset($options["debug-feed"])) { |
||
470 | $feed = $options["debug-feed"]; |
||
471 | |||
472 | if (isset($options["force-refetch"])) $_REQUEST["force_refetch"] = true; |
||
473 | if (isset($options["force-rehash"])) $_REQUEST["force_rehash"] = true; |
||
474 | |||
475 | Debug::set_loglevel(Debug::$LOG_EXTENDED); |
||
476 | |||
477 | $rc = RSSUtils::update_rss_feed($feed) != false ? 0 : 1; |
||
478 | |||
479 | exit($rc); |
||
480 | } |
||
481 | |||
482 | if (isset($options["send-digests"])) { |
||
483 | Digest::send_headlines_digests(); |
||
484 | } |
||
485 | |||
486 | PluginHost::getInstance()->run_commands($options); |
||
487 | |||
488 | if (file_exists(LOCK_DIRECTORY . "/$lock_filename")) |
||
0 ignored issues
–
show
|
|||
489 | if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') |
||
490 | fclose($lock_handle); |
||
491 | unlink(LOCK_DIRECTORY . "/$lock_filename"); |
||
492 |