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