codysnider /
tt-rss
| 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
Loading history...
|
|||
| 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 |