return sha1(implode(",", $pluginhost->get_plugin_names()) . $tmp);
}
+ // Strips utf8mb4 characters (i.e. emoji) for mysql
+ static function strip_utf8mb4($str) {
+ return preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $str);
+ }
+
static function update_feedbrowser_cache() {
$pdo = Db::pdo();
if (!$tmph->fetch()) {
$tmph = $pdo->prepare("INSERT INTO ttrss_feedbrowser_cache
- (feed_url, site_url, title, subscribers)
- VALUES
+ (feed_url, site_url, title, subscribers)
+ VALUES
(?, ?, ?, ?)");
$tmph->execute([$feed_url, $site_url, $title, $subscribers]);
array_push($batch_owners, $tline["owner_uid"]);
$fstarted = microtime(true);
- RSSUtils::update_rss_feed($tline["id"], true, false);
+
+ try {
+ RSSUtils::update_rss_feed($tline["id"], true, false);
+ } catch (PDOException $e) {
+ Logger::get()->log_error(E_USER_NOTICE, $e->getMessage(), $e->getFile(), $e->getLine(), $e->getTraceAsString());
+
+ try {
+ $pdo->rollback();
+ } catch (PDOException $e) {
+ // it doesn't matter if there wasn't actually anything to rollback, PDO Exception can be
+ // thrown outside of an active transaction during feed update
+ }
+ }
_debug_suppress(false);
_debug(sprintf(" %.4f (sec)", microtime(true) - $fstarted));
$pdo = Db::pdo();
- $sth = $pdo->prepare("SELECT owner_uid,feed_url,auth_pass,auth_login,auth_pass_encrypted
+ $sth = $pdo->prepare("SELECT owner_uid,feed_url,auth_pass,auth_login
FROM ttrss_feeds WHERE id = ?");
$sth->execute([$feed]);
if ($row = $sth->fetch()) {
$owner_uid = $row["owner_uid"];
-
- $auth_pass_encrypted = sql_bool_to_bool($row["auth_pass_encrypted"]);
-
$auth_login = $row["auth_login"];
$auth_pass = $row["auth_pass"];
-
- if ($auth_pass_encrypted && function_exists("mcrypt_decrypt")) {
- require_once "crypt.php";
- $auth_pass = decrypt_string($auth_pass);
- }
-
$fetch_url = $row["feed_url"];
$pluginhost = new PluginHost();
*/
static function update_rss_feed($feed, $no_cache = false) {
- $debug_enabled = defined('DAEMON_EXTENDED_DEBUG') || $_REQUEST['xdebug'];
+ $debug_enabled = defined('DAEMON_EXTENDED_DEBUG') || clean($_REQUEST['xdebug']);
_debug_suppress(!$debug_enabled);
_debug("start", $debug_enabled);
$sth = $pdo->prepare("SELECT id,update_interval,auth_login,
feed_url,auth_pass,cache_images,
mark_unread_on_update, owner_uid,
- auth_pass_encrypted, feed_language,
- last_modified,
- ".SUBSTRING_FOR_DATE."(last_unconditional, 1, 19) AS last_unconditional
+ auth_pass_encrypted, feed_language,
+ last_modified,
+ ".SUBSTRING_FOR_DATE."(last_unconditional, 1, 19) AS last_unconditional
FROM ttrss_feeds WHERE id = ?");
$sth->execute([$feed]);
-
+
if ($row = $sth->fetch()) {
$owner_uid = $row["owner_uid"];
- $mark_unread_on_update = sql_bool_to_bool($row["mark_unread_on_update"]);
- $auth_pass_encrypted = sql_bool_to_bool($row["auth_pass_encrypted"]);
+ $mark_unread_on_update = $row["mark_unread_on_update"];
$sth = $pdo->prepare("UPDATE ttrss_feeds SET last_update_started = NOW()
WHERE id = ?");
$auth_login = $row["auth_login"];
$auth_pass = $row["auth_pass"];
-
- if ($auth_pass_encrypted && function_exists("mcrypt_decrypt")) {
- require_once "crypt.php";
- $auth_pass = decrypt_string($auth_pass);
- }
-
$stored_last_modified = $row["last_modified"];
$last_unconditional = $row["last_unconditional"];
- $cache_images = sql_bool_to_bool($row["cache_images"]);
+ $cache_images = $row["cache_images"];
$fetch_url = $row["feed_url"];
+
$feed_language = mb_strtolower($row["feed_language"]);
if (!$feed_language) $feed_language = 'english';
$date_feed_processed = date('Y-m-d H:i');
- $cache_filename = CACHE_DIR . "/simplepie/" . sha1($fetch_url) . ".xml";
+ $cache_filename = CACHE_DIR . "/feeds/" . sha1($fetch_url) . ".xml";
$pluginhost = new PluginHost();
$pluginhost->set_debug($debug_enabled);
_debug("not using CURL due to open_basedir restrictions");
}
- if (time() - strtotime($last_unconditional) > MAX_CONDITIONAL_INTERVAL) {
- _debug("maximum allowed interval for conditional requests exceeded, forcing refetch");
+ if (time() - strtotime($last_unconditional) > MAX_CONDITIONAL_INTERVAL) {
+ _debug("maximum allowed interval for conditional requests exceeded, forcing refetch");
- $force_refetch = true;
- } else {
- _debug("stored last modified for conditional request: $stored_last_modified", $debug_enabled);
- }
+ $force_refetch = true;
+ } else {
+ _debug("stored last modified for conditional request: $stored_last_modified", $debug_enabled);
+ }
- _debug("fetching [$fetch_url] (force_refetch: $force_refetch)...", $debug_enabled);
+ _debug("fetching [$fetch_url] (force_refetch: $force_refetch)...", $debug_enabled);
$feed_data = fetch_file_contents([
"url" => $fetch_url,
}
// cache vanilla feed data for re-use
- if ($feed_data && !$auth_pass && !$auth_login && is_writable(CACHE_DIR . "/simplepie")) {
+ if ($feed_data && !$auth_pass && !$auth_login && is_writable(CACHE_DIR . "/feeds")) {
$new_rss_hash = sha1($feed_data);
if ($new_rss_hash != $rss_hash) {
$rss = new FeedParser($feed_data);
$rss->init();
- $feed = $feed;
-
if (!$rss->error()) {
// We use local pluginhost here because we need to load different per-user feed plugins
$sth->execute([$feed]);
if ($row = $sth->fetch()) {
- $favicon_needs_check = sql_bool_to_bool($row["favicon_needs_check"]);
+ $favicon_needs_check = $row["favicon_needs_check"];
$favicon_avg_color = $row["favicon_avg_color"];
$owner_uid = $row["owner_uid"];
} else {
$filters = load_filters($feed, $owner_uid);
if ($debug_enabled) {
- print_r($filters);
- }
+ print_r($filters);
+ }
_debug("" . count($filters) . " filters loaded.", $debug_enabled);
foreach ($items as $item) {
$pdo->beginTransaction();
- if ($_REQUEST['xdebug'] == 3) {
+ if (clean($_REQUEST['xdebug']) == 3) {
print_r($item);
}
$entry_link = rewrite_relative_url($site_url, $item->get_link());
+ $entry_language = mb_substr(trim($item->get_language()), 0, 2);
+
_debug("title $entry_title", $debug_enabled);
_debug("link $entry_link", $debug_enabled);
+ _debug("language $entry_language", $debug_enabled);
if (!$entry_title) $entry_title = date("Y-m-d H:i:s", $entry_timestamp);;
$entry_content = $item->get_content();
if (!$entry_content) $entry_content = $item->get_description();
- if ($_REQUEST["xdebug"] == 2) {
+ if (clean($_REQUEST["xdebug"]) == 2) {
print "content: ";
print htmlspecialchars($entry_content);
print "\n";
$entry_tags = array_unique($additional_tags);
- for ($i = 0; $i < count($entry_tags); $i++)
+ for ($i = 0; $i < count($entry_tags); $i++) {
$entry_tags[$i] = mb_strtolower($entry_tags[$i], 'utf-8');
+ // we don't support numeric tags, let's prefix them
+ if (is_numeric($entry_tags[$i])) $entry_tags[$i] = 't:' . $entry_tags[$i];
+ }
+
_debug("tags found: " . join(",", $entry_tags), $debug_enabled);
_debug("done collecting data.", $debug_enabled);
$base_entry_id = $row["id"];
$entry_stored_hash = $row["content_hash"];
$article_labels = Article::get_article_labels($base_entry_id, $owner_uid);
- $entry_language = $row["lang"];
$existing_tags = Article::get_article_tags($base_entry_id, $owner_uid);
$entry_tags = array_unique(array_merge($entry_tags, $existing_tags));
$base_entry_id = false;
$entry_stored_hash = "";
$article_labels = array();
- $entry_language = "";
}
$article = array("owner_uid" => $owner_uid, // read only
$entry_plugin_data .= mb_strtolower(get_class($plugin)) . ",";
}
- if ($_REQUEST["xdebug"] == 2) {
+ if (clean($_REQUEST["xdebug"]) == 2) {
print "processed content: ";
print htmlspecialchars($article["content"]);
print "\n";
_debug("plugin data: $entry_plugin_data", $debug_enabled);
// Workaround: 4-byte unicode requires utf8mb4 in MySQL. See https://tt-rss.org/forum/viewtopic.php?f=1&t=3377&p=20077#p20077
- if (DB_TYPE == "mysql") {
+ if (DB_TYPE == "mysql" && 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] = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $v);
+ $article[$k] = RSSUtils::strip_utf8mb4($v);
}
}
}
$usth = $pdo->prepare(
"INSERT INTO ttrss_entries
- (title,
+ (title,
guid,
link,
updated,
$entry_guid_hashed,
$entry_link,
$entry_timestamp_fmt,
- $entry_content,
+ "$entry_content",
$entry_current_hash,
$date_feed_processed,
$entry_comments,
- $num_comments,
+ (int)$num_comments,
$entry_plugin_data,
- $entry_language,
- $entry_author]);
+ "$entry_language",
+ "$entry_author"]);
}
_debug("user record FOUND: RID: $entry_ref_id, IID: $entry_int_id", $debug_enabled);
} else {
-
+
_debug("user record not found, creating...", $debug_enabled);
if ($score >= -500 && !RSSUtils::find_article_filter($article_filters, 'catchup') && !$entry_force_catchup) {
$last_read_qpart = null;
} else {
$unread = 0;
- $last_read_qpart = 'NOW()';
+ $last_read_qpart = date("Y-m-d H:i"); // we can't use NOW() here because it gets quoted
}
if (RSSUtils::find_article_filter($article_filters, 'mark') || $score > 1000) {
$published = 0;
}
- $last_marked = ($marked == 'true') ? 'NOW()' : null;
- $last_published = ($published == 'true') ? 'NOW()' : null;
+ $last_marked = ($marked == 1) ? 'NOW()' : 'NULL';
+ $last_published = ($published == 1) ? 'NOW()' : 'NULL';
$sth = $pdo->prepare(
"INSERT INTO ttrss_user_entries
(ref_id, owner_uid, feed_id, unread, last_read, marked,
published, score, tag_cache, label_cache, uuid,
last_marked, last_published)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, '', '', '', ?, ?)");
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, '', '', '', ".$last_marked.", ".$last_published.")");
$sth->execute([$ref_id, $owner_uid, $feed, $unread, $last_read_qpart, $marked,
- $published, $score, $last_marked, $last_published]);
+ $published, $score]);
$sth = $pdo->prepare("SELECT int_id FROM ttrss_user_entries WHERE
ref_id = ? AND owner_uid = ? AND
_debug("resulting RID: $entry_ref_id, IID: $entry_int_id", $debug_enabled);
- if (DB_TYPE == "pgsql") {
- $tsvector_combined = mb_substr($entry_title . ' ' . strip_tags(str_replace('<', ' <', $entry_content)),
- 0, 1000000);
-
- $tsvector_qpart = "tsvector_combined = to_tsvector('$feed_language', ".$pdo->quote($tsvector_combined)."),";
-
- } else {
+ if (DB_TYPE == "pgsql")
+ $tsvector_qpart = "tsvector_combined = to_tsvector(:ts_lang, :ts_content),";
+ else
$tsvector_qpart = "";
- }
$sth = $pdo->prepare("UPDATE ttrss_entries
- SET title = ?,
- content = ?,
- content_hash = ?,
- updated = ?,
+ SET title = :title,
$tsvector_qpart
- num_comments = ?,
- plugin_data = ?,
- author = ?,
- lang = ?
- WHERE id = ?");
+ content = :content,
+ content_hash = :content_hash,
+ updated = :updated,
+ date_updated = NOW(),
+ num_comments = :num_comments,
+ plugin_data = :plugin_data,
+ author = :author,
+ lang = :lang
+ WHERE id = :id");
+
+ $params = [":title" => $entry_title,
+ ":content" => "$entry_content",
+ ":content_hash" => $entry_current_hash,
+ ":updated" => $entry_timestamp_fmt,
+ ":num_comments" => (int)$num_comments,
+ ":plugin_data" => $entry_plugin_data,
+ ":author" => "$entry_author",
+ ":lang" => $entry_language,
+ ":id" => $ref_id];
- $sth->execute([$entry_title, $entry_content, $entry_current_hash, $entry_timestamp_fmt,
- $num_comments, $entry_plugin_data, $entry_author, $entry_language, $ref_id]);
+ if (DB_TYPE == "pgsql") {
+ $params[":ts_lang"] = $feed_language;
+ $params[":ts_content"] = mb_substr(strip_tags($entry_title . " " . $entry_content), 0, 900000);
+ }
+
+ $sth->execute($params);
// update aux data
$sth = $pdo->prepare("UPDATE ttrss_user_entries
$e_item = array(
rewrite_relative_url($site_url, $e->link),
$e->type, $e->length, $e->title, $e->width, $e->height);
+
+ // Yet another episode of "mysql utf8_general_ci is gimped"
+ if (DB_TYPE == "mysql" && MYSQL_CHARSET != "UTF8MB4") {
+ for ($i = 0; $i < count($e_item); $i++) {
+ if (is_string($e_item[$i])) {
+ $e_item[$i] = RSSUtils::strip_utf8mb4($e_item[$i]);
+ }
+ }
+ }
+
array_push($enclosures, $e_item);
}
}
}
$esth = $pdo->prepare("SELECT id FROM ttrss_enclosures
- WHERE content_url = ? AND post_id = ?");
+ WHERE content_url = ? AND content_type = ? AND post_id = ?");
$usth = $pdo->prepare("INSERT INTO ttrss_enclosures
(content_url, content_type, title, duration, post_id, width, height) VALUES
$enc_width = intval($enc[4]);
$enc_height = intval($enc[5]);
- $esth->execute([$enc_url, $entry_ref_id]);
+ $esth->execute([$enc_url, $enc_type, $entry_ref_id]);
if (!$esth->fetch()) {
$usth->execute([$enc_url, $enc_type, (string)$enc_title, $enc_dur, $entry_ref_id, $enc_width, $enc_height]);
if ($file_content && strlen($file_content) > MIN_CACHE_FILE_SIZE) {
file_put_contents($local_filename, $file_content);
}
- } else {
+ } else if (is_writable($local_filename)) {
touch($local_filename);
}
}
$local_filename = CACHE_DIR . "/images/" . sha1($src);
- if ($debug) _debug("cache_media: downloading: $src to $local_filename");
+ if ($debug) _debug("cache_media: checking $src");
if (!file_exists($local_filename)) {
+ if ($debug) _debug("cache_media: downloading: $src to $local_filename");
+
$file_content = fetch_file_contents($src);
if ($file_content && strlen($file_content) > MIN_CACHE_FILE_SIZE) {
file_put_contents($local_filename, $file_content);
}
- } else {
+ } else if (is_writable($local_filename)) {
touch($local_filename);
}
}
}
static function expire_cached_files($debug) {
- foreach (array("simplepie", "images", "export", "upload") as $dir) {
+ foreach (array("simplepie", "feeds", "images", "export", "upload") as $dir) {
$cache_dir = CACHE_DIR . "/$dir";
// if ($debug) _debug("Expiring $cache_dir");