initial attempt to remove mysql-related stuff from tt-rss

This commit is contained in:
Andrew Dolgov
2025-04-14 12:59:00 +03:00
parent 60606aaa97
commit b154bc7a10
159 changed files with 101 additions and 2089 deletions

View File

@@ -85,15 +85,13 @@ class Article extends Handler_Protected {
content = ?, content_hash = ? WHERE id = ?");
$sth->execute([$content, $content_hash, $ref_id]);
if (Config::get(Config::DB_TYPE) == "pgsql") {
$sth = $pdo->prepare("UPDATE ttrss_entries
SET tsvector_combined = to_tsvector( :ts_content)
WHERE id = :id");
$params = [
":ts_content" => mb_substr(\Soundasleep\Html2Text::convert($content), 0, 900000),
":id" => $ref_id];
$sth->execute($params);
}
$sth = $pdo->prepare("UPDATE ttrss_entries
SET tsvector_combined = to_tsvector( :ts_content)
WHERE id = :id");
$params = [
":ts_content" => mb_substr(\Soundasleep\Html2Text::convert($content), 0, 900000),
":id" => $ref_id];
$sth->execute($params);
$sth = $pdo->prepare("UPDATE ttrss_user_entries SET published = true,
last_published = NOW() WHERE
@@ -130,15 +128,15 @@ class Article extends Handler_Protected {
if ($row = $sth->fetch()) {
$ref_id = $row["id"];
if (Config::get(Config::DB_TYPE) == "pgsql"){
$sth = $pdo->prepare("UPDATE ttrss_entries
SET tsvector_combined = to_tsvector( :ts_content)
WHERE id = :id");
$params = [
":ts_content" => mb_substr(\Soundasleep\Html2Text::convert($content), 0, 900000),
":id" => $ref_id];
$sth->execute($params);
}
$sth = $pdo->prepare("UPDATE ttrss_entries
SET tsvector_combined = to_tsvector( :ts_content)
WHERE id = :id");
$params = [
":ts_content" => mb_substr(\Soundasleep\Html2Text::convert($content), 0, 900000),
":id" => $ref_id];
$sth->execute($params);
$sth = $pdo->prepare("INSERT INTO ttrss_user_entries
(ref_id, uuid, feed_id, orig_feed_id, owner_uid, published, tag_cache, label_cache,
last_read, note, unread, last_published)
@@ -465,16 +463,9 @@ class Article extends Handler_Protected {
static function _purge_orphans(): void {
// purge orphaned posts in main content table
if (Config::get(Config::DB_TYPE) == "mysql")
$limit_qpart = "LIMIT 5000";
else
$limit_qpart = "";
$pdo = Db::pdo();
$res = $pdo->query("DELETE FROM ttrss_entries WHERE
NOT EXISTS (SELECT ref_id FROM ttrss_user_entries WHERE ref_id = id) $limit_qpart");
NOT EXISTS (SELECT ref_id FROM ttrss_user_entries WHERE ref_id = id)");
if (Debug::enabled()) {
$rows = $res->rowCount();

View File

@@ -24,7 +24,7 @@ class Config {
*
*/
/** database type: pgsql or mysql */
/** database type: pgsql */
const DB_TYPE = "DB_TYPE";
/** database server hostname */
@@ -42,10 +42,6 @@ class Config {
/** database server port */
const DB_PORT = "DB_PORT";
/** connection charset for MySQL. if you have a legacy database and/or experience
* garbage unicode characters with this option, try setting it to a blank string. */
const MYSQL_CHARSET = "MYSQL_CHARSET";
/** this is a fallback falue for the CLI SAPI, it should be set to a fully-qualified tt-rss URL */
const SELF_URL_PATH = "SELF_URL_PATH";
@@ -204,7 +200,6 @@ class Config {
Config::DB_NAME => [ "", Config::T_STRING ],
Config::DB_PASS => [ "", Config::T_STRING ],
Config::DB_PORT => [ "5432", Config::T_STRING ],
Config::MYSQL_CHARSET => [ "UTF8", Config::T_STRING ],
Config::SELF_URL_PATH => [ "https://example.com/tt-rss", Config::T_STRING ],
Config::SINGLE_USER_MODE => [ "", Config::T_BOOL ],
Config::SIMPLE_UPDATE_MODE => [ "", Config::T_BOOL ],
@@ -481,25 +476,6 @@ class Config {
}
/* sanity check stuff */
/** checks for mysql tables not using InnoDB (tt-rss is incompatible with MyISAM)
* @return array<int, array<string, string>> A list of entries identifying tt-rss tables with bad config
*/
private static function check_mysql_tables() {
$pdo = Db::pdo();
$sth = $pdo->prepare("SELECT engine, table_name FROM information_schema.tables WHERE
table_schema = ? AND table_name LIKE 'ttrss_%' AND engine != 'InnoDB'");
$sth->execute([self::get(Config::DB_NAME)]);
$bad_tables = [];
while ($line = $sth->fetch()) {
array_push($bad_tables, $line);
}
return $bad_tables;
}
static function sanity_check(): void {
/*
@@ -604,29 +580,6 @@ class Config {
}
}
if (self::get(Config::DB_TYPE) == "mysql") {
$bad_tables = self::check_mysql_tables();
if (count($bad_tables) > 0) {
$bad_tables_fmt = [];
foreach ($bad_tables as $bt) {
array_push($bad_tables_fmt, sprintf("%s (%s)", $bt['table_name'], $bt['engine']));
}
$msg = "<p>The following tables use an unsupported MySQL engine: <b>" .
implode(", ", $bad_tables_fmt) . "</b>.</p>";
$msg .= "<p>The only supported engine on MySQL is InnoDB. MyISAM lacks functionality to run
tt-rss.
Please backup your data (via OPML) and re-import the schema before continuing.</p>
<p><b>WARNING: importing the schema would mean LOSS OF ALL YOUR DATA.</b></p>";
array_push($errors, $msg);
}
}
if (count($errors) > 0 && php_sapi_name() != "cli") {
http_response_code(503); ?>

View File

@@ -9,9 +9,6 @@ class Db {
ORM::configure('username', Config::get(Config::DB_USER));
ORM::configure('password', Config::get(Config::DB_PASS));
ORM::configure('return_result_sets', true);
if (Config::get(Config::DB_TYPE) == "mysql" && Config::get(Config::MYSQL_CHARSET)) {
ORM::configure('driver_options', array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES ' . Config::get(Config::MYSQL_CHARSET)));
}
}
/**
@@ -29,13 +26,8 @@ class Db {
public static function get_dsn(): string {
$db_port = Config::get(Config::DB_PORT) ? ';port=' . Config::get(Config::DB_PORT) : '';
$db_host = Config::get(Config::DB_HOST) ? ';host=' . Config::get(Config::DB_HOST) : '';
if (Config::get(Config::DB_TYPE) == "mysql" && Config::get(Config::MYSQL_CHARSET)) {
$db_charset = ';charset=' . Config::get(Config::MYSQL_CHARSET);
} else {
$db_charset = '';
}
return Config::get(Config::DB_TYPE) . ':dbname=' . Config::get(Config::DB_NAME) . $db_host . $db_port . $db_charset;
return Config::get(Config::DB_TYPE) . ':dbname=' . Config::get(Config::DB_NAME) . $db_host . $db_port;
}
// this really shouldn't be used unless a separate PDO connection is needed
@@ -53,20 +45,10 @@ class Db {
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
if (Config::get(Config::DB_TYPE) == "pgsql") {
$pdo->query("set client_encoding = 'UTF-8'");
$pdo->query("set datestyle = 'ISO, european'");
$pdo->query("set TIME ZONE 0");
$pdo->query("set cpu_tuple_cost = 0.5");
} else if (Config::get(Config::DB_TYPE) == "mysql") {
$pdo->query("SET time_zone = '+0:0'");
if (Config::get(Config::MYSQL_CHARSET)) {
$pdo->query("SET NAMES " . Config::get(Config::MYSQL_CHARSET));
}
}
$pdo->query("set client_encoding = 'UTF-8'");
$pdo->query("set datestyle = 'ISO, european'");
$pdo->query("set TIME ZONE 0");
$pdo->query("set cpu_tuple_cost = 0.5");
return $pdo;
}
@@ -90,15 +72,12 @@ class Db {
}
public static function sql_random_function(): string {
if (Config::get(Config::DB_TYPE) == "mysql") {
return "RAND()";
}
return "RANDOM()";
}
/**
* Helper to build a query part comparing a field against a past datetime (determined by "$now - $some_interval")
*
*
* The example below could be read as "last_digest_sent is older than 1 day ago".
* ```php
* Db::past_comparison_qpart('last_digest_sent', '<', 1, 'day');
@@ -106,7 +85,6 @@ class Db {
*
* @todo validate value of $unit and fail if invalid (or massage if practical)?
* @link https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-INTERVAL-INPUT
* @link https://dev.mysql.com/doc/refman/9.2/en/expressions.html#temporal-intervals
* @param string $field the table field being checked
* @param '<'|'>'|'<='|'>='|'=' $operator the comparison operator
* @param positive-int $quantity the amount of $unit
@@ -114,8 +92,6 @@ class Db {
* @return string the query part string
*/
static function past_comparison_qpart(string $field, string $operator, int $quantity, string $unit): string {
if (Config::get(Config::DB_TYPE) == 'pgsql')
return "$field $operator NOW() - INTERVAL '$quantity $unit' ";
return "$field $operator DATE_SUB(NOW(), INTERVAL $quantity $unit) ";
return "$field $operator NOW() - INTERVAL '$quantity $unit' ";
}
}

View File

@@ -88,9 +88,7 @@ class Db_Migrations {
$lines = $this->get_lines($version);
if (count($lines) > 0) {
// mysql doesn't support transactions for DDL statements
if (Config::get(Config::DB_TYPE) != "mysql")
$this->pdo->beginTransaction();
$this->pdo->beginTransaction();
foreach ($lines as $line) {
Debug::log($line, Debug::LOG_EXTENDED);
@@ -107,8 +105,7 @@ class Db_Migrations {
else
$this->set_version($version);
if (Config::get(Config::DB_TYPE) != "mysql")
$this->pdo->commit();
$this->pdo->commit();
Debug::log("Migration finished, current version: " . $this->get_version(), Debug::LOG_VERBOSE);

View File

@@ -91,6 +91,7 @@ abstract class FeedItem_Common extends FeedItem {
$enclosures = $this->xpath->query("media:content", $this->elem);
/** @var DOMElement $enclosure */
foreach ($enclosures as $enclosure) {
$enc = new FeedEnclosure();
$enc->type = clean($enclosure->getAttribute('type'));
@@ -143,6 +144,7 @@ abstract class FeedItem_Common extends FeedItem {
$enclosures = $this->xpath->query("media:thumbnail", $this->elem);
/** @var DOMElement $enclosure */
foreach ($enclosures as $enclosure) {
$enc = new FeedEnclosure();
$enc->type = 'image/generic';
@@ -193,10 +195,6 @@ abstract class FeedItem_Common extends FeedItem {
$cat = preg_replace('/[,\'\"]/', "", $cat);
if (Config::get(Config::DB_TYPE) == "mysql") {
$cat = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $cat);
}
if (mb_strlen($cat) > 250)
$cat = mb_substr($cat, 0, 250);

View File

@@ -217,17 +217,6 @@ class Feeds extends Handler_Protected {
$id = $line["id"];
// frontend doesn't expect pdo returning booleans as strings on mysql
if (Config::get(Config::DB_TYPE) == "mysql") {
foreach (["unread", "marked", "published"] as $k) {
if (is_integer($line[$k])) {
$line[$k] = $line[$k] === 1;
} else {
$line[$k] = $line[$k] === "1";
}
}
}
// normalize archived feed
if ($line['feed_id'] === null) {
$line['feed_id'] = Feeds::FEED_ARCHIVED;
@@ -582,7 +571,7 @@ class Feeds extends Handler_Protected {
function search(): void {
print json_encode([
"show_language" => Config::get(Config::DB_TYPE) == "pgsql",
"show_language" => true,
"show_syntax_help" => count(PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH)) == 0,
"all_languages" => Pref_Feeds::get_ts_languages(),
"default_language" => Prefs::get(Prefs::DEFAULT_SEARCH_LANGUAGE, $_SESSION['uid'], $_SESSION['profile'] ?? null)
@@ -1407,18 +1396,16 @@ class Feeds extends Handler_Protected {
list($search_query_part, $search_words) = self::_search_to_sql($search, $search_language, $owner_uid, $profile);
}
if (Config::get(Config::DB_TYPE) == "pgsql") {
$test_sth = $pdo->prepare("select $search_query_part
FROM ttrss_entries, ttrss_user_entries WHERE id = ref_id limit 1");
$test_sth = $pdo->prepare("select $search_query_part
FROM ttrss_entries, ttrss_user_entries WHERE id = ref_id limit 1");
try {
$test_sth->execute();
} catch (PDOException $e) {
// looks like tsquery syntax is invalid
$search_query_part = "false";
try {
$test_sth->execute();
} catch (PDOException $e) {
// looks like tsquery syntax is invalid
$search_query_part = "false";
$query_error_override = T_sprintf("Incorrect search syntax: %s.", implode(" ", $search_words));
}
$query_error_override = T_sprintf("Incorrect search syntax: %s.", implode(" ", $search_words));
}
$search_query_part .= " AND ";
@@ -1635,11 +1622,7 @@ class Feeds extends Handler_Protected {
$first_id = 0;
if (Config::get(Config::DB_TYPE) == "pgsql") {
$yyiw_qpart = "to_char(date_entered, 'IYYY-IW') AS yyiw";
} else {
$yyiw_qpart = "date_format(date_entered, '%Y-%u') AS yyiw";
}
$yyiw_qpart = "to_char(date_entered, 'IYYY-IW') AS yyiw";
if (is_numeric($feed)) {
// proper override_order applied above
@@ -1679,12 +1662,8 @@ class Feeds extends Handler_Protected {
$sanity_interval_qpart = Db::past_comparison_qpart('date_entered', '>=', 1, 'hour') . ' AND ';
if (Config::get(Config::DB_TYPE) == "pgsql") {
$distinct_columns = str_replace("desc", "", strtolower($order_by));
$distinct_qpart = "DISTINCT ON (id, $distinct_columns)";
} else {
$distinct_qpart = "DISTINCT"; //fallback
}
$distinct_columns = str_replace("desc", "", strtolower($order_by));
$distinct_qpart = "DISTINCT ON (id, $distinct_columns)";
// except for Labels category
if (Prefs::get(Prefs::HEADLINES_NO_DISTINCT, $owner_uid, $profile)
@@ -1785,12 +1764,8 @@ class Feeds extends Handler_Protected {
if (Prefs::get(Prefs::HEADLINES_NO_DISTINCT, $owner_uid, $profile)) {
$distinct_qpart = "";
} else {
if (Config::get(Config::DB_TYPE) == "pgsql") {
$distinct_columns = str_replace("desc", "", strtolower($order_by));
$distinct_qpart = "DISTINCT ON (id, $distinct_columns)";
} else {
$distinct_qpart = "DISTINCT"; //fallback
}
$distinct_columns = str_replace("desc", "", strtolower($order_by));
$distinct_qpart = "DISTINCT ON (id, $distinct_columns)";
}
$query = "SELECT $distinct_qpart
@@ -2099,27 +2074,14 @@ class Feeds extends Handler_Protected {
else
$query_limit = "";
if (Config::get(Config::DB_TYPE) == "pgsql") {
$sth = $pdo->prepare("DELETE FROM ttrss_user_entries
USING ttrss_entries
WHERE ttrss_entries.id = ref_id AND
marked = false AND
feed_id = ? AND
$query_limit
ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
$sth->execute([$feed_id]);
} else {
$sth = $pdo->prepare("DELETE FROM ttrss_user_entries
USING ttrss_user_entries, ttrss_entries
WHERE ttrss_entries.id = ref_id AND
marked = false AND
feed_id = ? AND
$query_limit
ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)");
$sth->execute([$feed_id]);
}
$sth = $pdo->prepare("DELETE FROM ttrss_user_entries
USING ttrss_entries
WHERE ttrss_entries.id = ref_id AND
marked = false AND
feed_id = ? AND
$query_limit
ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'");
$sth->execute([$feed_id]);
$rows_deleted = $sth->rowCount();
@@ -2286,16 +2248,12 @@ class Feeds extends Handler_Protected {
$k = mb_strtolower($k);
if (Config::get(Config::DB_TYPE) == "pgsql") {
// A hacky way for phrases (e.g. "hello world") to get through PDO quoting.
// Term '"foo bar baz"' becomes '(foo <-> bar <-> baz)' ("<->" meaning "immediately followed by").
if (preg_match('/\s+/', $k))
$k = '(' . preg_replace('/\s+/', ' <-> ', $k) . ')';
// A hacky way for phrases (e.g. "hello world") to get through PDO quoting.
// Term '"foo bar baz"' becomes '(foo <-> bar <-> baz)' ("<->" meaning "immediately followed by").
if (preg_match('/\s+/', $k))
$k = '(' . preg_replace('/\s+/', ' <-> ', $k) . ')';
array_push($search_query_leftover, $not ? "!$k" : $k);
} else {
array_push($search_query_leftover, $not ? "-$k" : $k);
}
array_push($search_query_leftover, $not ? "!$k" : $k);
if (!$not) array_push($search_words, $k);
}
@@ -2304,26 +2262,18 @@ class Feeds extends Handler_Protected {
if (count($search_query_leftover) > 0) {
if (Config::get(Config::DB_TYPE) == "pgsql") {
// if there's no joiners consider this a "simple" search and
// concatenate everything with &, otherwise don't try to mess with tsquery syntax
if (preg_match("/[&|]/", implode(" " , $search_query_leftover))) {
$tsquery = $pdo->quote(implode(" ", $search_query_leftover));
} else {
$tsquery = $pdo->quote(implode(" & ", $search_query_leftover));
}
$search_language = $pdo->quote(mb_strtolower($search_language ?: Prefs::get(Prefs::DEFAULT_SEARCH_LANGUAGE, $owner_uid, $profile)));
array_push($query_keywords,
"(tsvector_combined @@ to_tsquery($search_language, $tsquery))");
// if there's no joiners consider this a "simple" search and
// concatenate everything with &, otherwise don't try to mess with tsquery syntax
if (preg_match("/[&|]/", implode(" " , $search_query_leftover))) {
$tsquery = $pdo->quote(implode(" ", $search_query_leftover));
} else {
$ft_query = $pdo->quote(implode(" ", $search_query_leftover));
array_push($query_keywords,
"MATCH (ttrss_entries.title, ttrss_entries.content) AGAINST ($ft_query IN BOOLEAN MODE)");
$tsquery = $pdo->quote(implode(" & ", $search_query_leftover));
}
$search_language = $pdo->quote(mb_strtolower($search_language ?: Prefs::get(Prefs::DEFAULT_SEARCH_LANGUAGE, $owner_uid, $profile)));
array_push($query_keywords,
"(tsvector_combined @@ to_tsquery($search_language, $tsquery))");
}
if (count($query_keywords) > 0)

View File

@@ -15,12 +15,8 @@ class Pref_Feeds extends Handler_Protected {
* @return array<int, string>
*/
public static function get_ts_languages(): array {
if (Config::get(Config::DB_TYPE) == 'pgsql') {
return array_map('ucfirst',
array_column(ORM::for_table('pg_ts_config')->select('cfgname')->find_array(), 'cfgname'));
}
return [];
return array_map('ucfirst',
array_column(ORM::for_table('pg_ts_config')->select('cfgname')->find_array(), 'cfgname'));
}
function renameCat(): void {
@@ -597,7 +593,7 @@ class Pref_Feeds extends Handler_Protected {
"access_level" => $user->access_level
],
"lang" => [
"enabled" => Config::get(Config::DB_TYPE) == "pgsql",
"enabled" => true,
"default" => Prefs::get(Prefs::DEFAULT_SEARCH_LANGUAGE, $_SESSION['uid'], $profile),
"all" => $this::get_ts_languages(),
]
@@ -653,13 +649,11 @@ class Pref_Feeds extends Handler_Protected {
</fieldset>
<?php } ?>
<?php if (Config::get(Config::DB_TYPE) == "pgsql") { ?>
<fieldset>
<label><?= __('Language:') ?></label>
<?= \Controls\select_tag("feed_language", "", $this::get_ts_languages(), ["disabled"=> 1]) ?>
<?= $this->_batch_toggle_checkbox("feed_language") ?>
</fieldset>
<?php } ?>
</section>
<hr/>
@@ -1141,12 +1135,6 @@ class Pref_Feeds extends Handler_Protected {
function inactiveFeeds(): void {
if (Config::get(Config::DB_TYPE) == "pgsql") {
$interval_qpart = "NOW() - INTERVAL '3 months'";
} else {
$interval_qpart = "DATE_SUB(NOW(), INTERVAL 3 MONTH)";
}
$inactive_feeds = ORM::for_table('ttrss_feeds')
->table_alias('f')
->select_many('f.id', 'f.title', 'f.site_url', 'f.feed_url')
@@ -1158,7 +1146,7 @@ class Pref_Feeds extends Handler_Protected {
"(SELECT MAX(ttrss_entries.updated)
FROM ttrss_entries
JOIN ttrss_user_entries ON ttrss_entries.id = ttrss_user_entries.ref_id
WHERE ttrss_user_entries.feed_id = f.id) < $interval_qpart")
WHERE ttrss_user_entries.feed_id = f.id) < NOW() - INTERVAL '3 months'")
->group_by('f.title')
->group_by('f.id')
->group_by('f.site_url')

View File

@@ -591,10 +591,6 @@ class Pref_Prefs extends Handler_Protected {
continue;
}
if ($pref_name == Prefs::DEFAULT_SEARCH_LANGUAGE && Config::get(Config::DB_TYPE) != "pgsql") {
continue;
}
if (isset($prefs_available[$pref_name])) {
$item = $prefs_available[$pref_name];

View File

@@ -252,33 +252,18 @@ class RPC extends Handler_Protected {
$default_interval = (int) Prefs::get_default(Prefs::DEFAULT_UPDATE_INTERVAL);
// Test if the feed need a update (update interval exceded).
if (Config::get(Config::DB_TYPE) == "pgsql") {
$update_limit_qpart = "AND ((
update_interval = 0
AND (p.value IS NULL OR p.value != '-1')
AND last_updated < NOW() - CAST((COALESCE(p.value, '$default_interval') || ' minutes') AS INTERVAL)
) OR (
update_interval > 0
AND last_updated < NOW() - CAST((update_interval || ' minutes') AS INTERVAL)
) OR (
update_interval >= 0
AND (p.value IS NULL OR p.value != '-1')
AND (last_updated = '1970-01-01 00:00:00' OR last_updated IS NULL)
))";
} else {
$update_limit_qpart = "AND ((
update_interval = 0
AND (p.value IS NULL OR p.value != '-1')
AND last_updated < DATE_SUB(NOW(), INTERVAL CONVERT(COALESCE(p.value, '$default_interval'), SIGNED INTEGER) MINUTE)
) OR (
update_interval > 0
AND last_updated < DATE_SUB(NOW(), INTERVAL update_interval MINUTE)
) OR (
update_interval >= 0
AND (p.value IS NULL OR p.value != '-1')
AND (last_updated = '1970-01-01 00:00:00' OR last_updated IS NULL)
))";
}
$update_limit_qpart = "AND ((
update_interval = 0
AND (p.value IS NULL OR p.value != '-1')
AND last_updated < NOW() - CAST((COALESCE(p.value, '$default_interval') || ' minutes') AS INTERVAL)
) OR (
update_interval > 0
AND last_updated < NOW() - CAST((update_interval || ' minutes') AS INTERVAL)
) OR (
update_interval >= 0
AND (p.value IS NULL OR p.value != '-1')
AND (last_updated = '1970-01-01 00:00:00' OR last_updated IS NULL)
))";
// Test if feed is currently being updated by another process.
$updstart_thresh_qpart = 'AND (last_update_started IS NULL OR '

View File

@@ -35,11 +35,6 @@ class RSSUtils {
return sha1(implode(",", $pluginhost->get_plugin_names()) . $tmp);
}
// Strips utf8mb4 characters (i.e. emoji) for mysql
static function strip_utf8mb4(string $str): string {
return preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $str);
}
static function cleanup_feed_browser(): void {
$pdo = Db::pdo();
$pdo->query("DELETE FROM ttrss_feedbrowser_cache");
@@ -123,33 +118,18 @@ class RSSUtils {
$default_interval = (int) Prefs::get_default(Prefs::DEFAULT_UPDATE_INTERVAL);
if (Config::get(Config::DB_TYPE) == "pgsql") {
$update_limit_qpart = "AND ((
update_interval = 0
AND (p.value IS NULL OR p.value != '-1')
AND last_updated < NOW() - CAST((COALESCE(p.value, '$default_interval') || ' minutes') AS INTERVAL)
) OR (
update_interval > 0
AND last_updated < NOW() - CAST((update_interval || ' minutes') AS INTERVAL)
) OR (
update_interval >= 0
AND (p.value IS NULL OR p.value != '-1')
AND (last_updated = '1970-01-01 00:00:00' OR last_updated IS NULL)
))";
} else {
$update_limit_qpart = "AND ((
update_interval = 0
AND (p.value IS NULL OR p.value != '-1')
AND last_updated < DATE_SUB(NOW(), INTERVAL CONVERT(COALESCE(p.value, '$default_interval'), SIGNED INTEGER) MINUTE)
) OR (
update_interval > 0
AND last_updated < DATE_SUB(NOW(), INTERVAL update_interval MINUTE)
) OR (
update_interval >= 0
AND (p.value IS NULL OR p.value != '-1')
AND (last_updated = '1970-01-01 00:00:00' OR last_updated IS NULL)
))";
}
$update_limit_qpart = "AND ((
update_interval = 0
AND (p.value IS NULL OR p.value != '-1')
AND last_updated < NOW() - CAST((COALESCE(p.value, '$default_interval') || ' minutes') AS INTERVAL)
) OR (
update_interval > 0
AND last_updated < NOW() - CAST((update_interval || ' minutes') AS INTERVAL)
) OR (
update_interval >= 0
AND (p.value IS NULL OR p.value != '-1')
AND (last_updated = '1970-01-01 00:00:00' OR last_updated IS NULL)
))";
// Test if feed is currently being updated by another process.
// TODO: Update RPC::updaterandomfeed_real() to also use 10 minutes?
@@ -159,10 +139,7 @@ class RSSUtils {
$query_limit = $limit ? sprintf("LIMIT %d", $limit) : "";
// Update the least recently updated feeds first
$query_order = "ORDER BY last_updated";
if (Config::get(Config::DB_TYPE) == "pgsql")
$query_order .= " NULLS FIRST";
$query_order = "ORDER BY last_updated NULLS FIRST";
$query = "SELECT f.feed_url, f.last_updated
FROM
@@ -856,15 +833,6 @@ class RSSUtils {
continue;
}
// Yet another episode of "mysql utf8_general_ci is gimped"
if (Config::get(Config::DB_TYPE) == "mysql" && Config::get(Config::MYSQL_CHARSET) != "UTF8MB4") {
foreach ((array)$e as $prop => $val) {
if (is_string($val)) {
$e->$prop = self::strip_utf8mb4($val);
}
}
}
array_push($enclosures, $e);
}
@@ -935,16 +903,6 @@ class RSSUtils {
Debug::log("plugin data: {$entry_plugin_data}", Debug::LOG_VERBOSE);
// Workaround: 4-byte unicode requires utf8mb4 in MySQL. See https://tt-rss.org/forum/viewtopic.php?f=1&t=3377&p=20077#p20077
if (Config::get(Config::DB_TYPE) == "mysql" && Config::get(Config::MYSQL_CHARSET) != "UTF8MB4") {
foreach ($article as $k => $v) {
// i guess we'll have to take the risk of 4byte unicode labels & tags here
if (is_string($article[$k])) {
$article[$k] = self::strip_utf8mb4($v);
}
}
}
/* Collect article tags here so we could filter by them: */
$matched_rules = [];
@@ -1186,14 +1144,9 @@ class RSSUtils {
Debug::log("resulting RID: $entry_ref_id, IID: $entry_int_id", Debug::LOG_VERBOSE);
if (Config::get(Config::DB_TYPE) == "pgsql")
$tsvector_qpart = "tsvector_combined = to_tsvector(:ts_lang, :ts_content),";
else
$tsvector_qpart = "";
$sth = $pdo->prepare("UPDATE ttrss_entries
SET title = :title,
$tsvector_qpart
tsvector_combined = to_tsvector(:ts_lang, :ts_content),
content = :content,
content_hash = :content_hash,
updated = :updated,
@@ -1212,12 +1165,10 @@ class RSSUtils {
":plugin_data" => $entry_plugin_data,
":author" => "$entry_author",
":lang" => $entry_language,
":id" => $ref_id];
if (Config::get(Config::DB_TYPE) == "pgsql") {
$params[":ts_lang"] = $feed_language;
$params[":ts_content"] = mb_substr(strip_tags($entry_title) . " " . \Soundasleep\Html2Text::convert($entry_content), 0, 900000);
}
":id" => $ref_id,
":ts_lang" => $feed_language,
":ts_content" => mb_substr(strip_tags($entry_title) . " " . \Soundasleep\Html2Text::convert($entry_content), 0, 900000)
];
$sth->execute($params);