Passed
Push — master ( fade3b...5069a0 )
by Cody
05:12
created

update.php (9 issues)

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
The constant DB_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
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
The variable $interval_query does not seem to be defined for all execution paths leading up to this point.
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
	$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
The condition is_array($options) is always true.
Loading history...
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
The condition $lock_handle is always false.
Loading history...
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
The constant DAEMON_SLEEP_INTERVAL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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
The constant DAEMON_FEED_LIMIT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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
The constant DB_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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
The constant PLUGINS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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
The constant LOCK_DIRECTORY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
489
		if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN')
490
			fclose($lock_handle);
491
		unlink(LOCK_DIRECTORY . "/$lock_filename");
492