Commit 4ae18c8e authored by Sebastian Reichel's avatar Sebastian Reichel

New upstream version 18.12+dfsg

parent 54f4b852
Pipeline #35345 skipped with stage
......@@ -16,3 +16,6 @@ tags
cache/htmlpurifier/*/*ser
lib/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/*/*ser
web.config
/.save.cson
/.tags*
/.gutentags
......@@ -19,6 +19,7 @@
require_once "functions.php";
require_once "sessions.php";
ini_set('session.use_cookies', 0);
ini_set("session.gc_maxlifetime", 86400);
define('AUTH_DISABLE_OTP', true);
......
......@@ -11,7 +11,7 @@ class API extends Handler {
static function param_to_bool($p) {
return $p && ($p !== "f" && $p !== "false");
}
function before($method) {
if (parent::before($method)) {
header("Content-Type: text/json");
......@@ -186,7 +186,7 @@ class API extends Handler {
function getHeadlines() {
$feed_id = clean($_REQUEST["feed_id"]);
if ($feed_id != "") {
if ($feed_id !== "") {
if (is_numeric($feed_id)) $feed_id = (int) $feed_id;
......@@ -293,8 +293,8 @@ class API extends Handler {
$article_qmarks = arr_qmarks($article_ids);
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
$field = $set_to $additional_fields
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
$field = $set_to $additional_fields
WHERE ref_id IN ($article_qmarks) AND owner_uid = ?");
$sth->execute(array_merge($article_ids, [$_SESSION['uid']]));
......@@ -379,6 +379,8 @@ class API extends Handler {
$article = $p->hook_render_article_api(array("article" => $article));
}
$article['content'] = rewrite_cached_urls($article['content']);
array_push($articles, $article);
}
......@@ -623,7 +625,7 @@ class API extends Handler {
id, feed_url, cat_id, title, order_id, ".
SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated
FROM ttrss_feeds WHERE
(cat_id = :cat OR (:cat = 0 AND cat_id IS NULL))
(cat_id = :cat OR (:cat = 0 AND cat_id IS NULL))
AND owner_uid = :uid
ORDER BY cat_id, title " . $limit_qpart);
$sth->execute([":uid" => $_SESSION['uid'], ":cat" => $cat_id]);
......@@ -753,7 +755,7 @@ class API extends Handler {
"is_updated" => $is_updated,
"title" => $line["title"],
"link" => $line["link"],
"feed_id" => $line["feed_id"],
"feed_id" => $line["feed_id"] ? $line['feed_id'] : 0,
"tags" => $tags,
);
......@@ -799,6 +801,8 @@ class API extends Handler {
$headline_row = $p->hook_render_article_api(array("headline" => $headline_row));
}
$headline_row['content'] = rewrite_cached_urls($headline_row['content']);
array_push($headlines, $headline_row);
}
} else if (is_numeric($result) && $result == -1) {
......
......@@ -126,7 +126,7 @@ class Article extends Handler_Protected {
if (filter_var($url, FILTER_VALIDATE_URL) === FALSE) return false;
$pdo = Db::pdo();
$pdo->beginTransaction();
// only check for our user data here, others might have shared this with different content etc
......@@ -309,7 +309,7 @@ class Article extends Handler_Protected {
if ($tag != '') {
$sth = $this->pdo->prepare("INSERT INTO ttrss_tags
(post_int_id, owner_uid, tag_name)
(post_int_id, owner_uid, tag_name)
VALUES (?, ?, ?)");
$sth->execute([$int_id, $_SESSION['uid'], $tag]);
......@@ -372,8 +372,7 @@ class Article extends Handler_Protected {
$ids = explode(",", clean($_REQUEST["ids"]));
$label_id = clean($_REQUEST["lid"]);
$label = db_escape_string(Labels::find_caption($label_id,
$_SESSION["uid"]));
$label = Labels::find_caption($label_id, $_SESSION["uid"]);
$reply["info-for-headlines"] = array();
......@@ -610,6 +609,8 @@ class Article extends Handler_Protected {
$line = $p->hook_render_article($line);
}
$line['content'] = rewrite_cached_urls($line['content']);
$num_comments = (int) $line["num_comments"];
$entry_comments = "";
......@@ -629,21 +630,57 @@ class Article extends Handler_Protected {
}
}
$enclosures = self::get_article_enclosures($line["id"]);
if ($zoom_mode) {
header("Content-Type: text/html");
$rv['content'] .= "<html><head>
$rv['content'] .= "<!DOCTYPE html>
<html><head>
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
<title>".$line["title"]."</title>".
stylesheet_tag("css/default.css")."
<link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
<link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
<link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">";
$rv['content'] .= "<meta property=\"og:title\" content=\"".htmlspecialchars($line["title"])."\"/>\n";
$rv['content'] .= "<meta property=\"og:site_name\" content=\"".htmlspecialchars($line["feed_title"])."\"/>\n";
$rv['content'] .= "<meta property=\"og:description\" content=\"".
htmlspecialchars(truncate_string(strip_tags($line["content"]), 500, "..."))."\"/>\n";
$rv['content'] .= "</head>";
$og_image = false;
foreach ($enclosures as $enc) {
if (strpos($enc["content_type"], "image/") !== FALSE) {
$og_image = $enc["content_url"];
break;
}
}
if (!$og_image) {
$tmpdoc = new DOMDocument();
if (@$tmpdoc->loadHTML(mb_substr($line["content"], 0, 131070))) {
$tmpxpath = new DOMXPath($tmpdoc);
$first_img = $tmpxpath->query("//img")->item(0);
</head><body class=\"claro ttrss_utility ttrss_zoom\">";
if ($first_img) {
$og_image = $first_img->getAttribute("src");
}
}
}
if ($og_image) {
$rv['content'] .= "<meta property=\"og:image\" content=\"" . htmlspecialchars($og_image) . "\"/>";
}
$rv['content'] .= "<body class=\"claro ttrss_utility ttrss_zoom\">";
}
$rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
$rv['content'] .= "<div class=\"post\" id=\"POST-$id\">";
$rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
$rv['content'] .= "<div class=\"header\">";
$entry_author = $line["author"];
......@@ -655,25 +692,25 @@ class Article extends Handler_Protected {
$owner_uid, true);
if (!$zoom_mode)
$rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
$rv['content'] .= "<div class=\"date\">$parsed_updated</div>";
if ($line["link"]) {
$rv['content'] .= "<div class='postTitle'><a target='_blank' rel='noopener noreferrer'
$rv['content'] .= "<div class='title'><a target='_blank' rel='noopener noreferrer'
title=\"".htmlspecialchars($line['title'])."\"
href=\"" .
htmlspecialchars($line["link"]) . "\">" .
$line["title"] . "</a>" .
"<span class='author'>$entry_author</span></div>";
} else {
$rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
$rv['content'] .= "<div class='title'>" . $line["title"] . "$entry_author</div>";
}
if ($zoom_mode) {
$feed_title = htmlspecialchars($line["feed_title"]);
$rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
$rv['content'] .= "<div class=\"feed-title\">$feed_title</div>";
$rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
$rv['content'] .= "<div class=\"date\">$parsed_updated</div>";
}
$tags_str = Article::format_tags_string($line["tags"], $id);
......@@ -749,7 +786,7 @@ class Article extends Handler_Protected {
if (!$line['lang']) $line['lang'] = 'en';
$rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
$rv['content'] .= "<div class=\"content\" lang=\"".$line['lang']."\">";
$rv['content'] .= $line["content"];
......@@ -791,7 +828,7 @@ class Article extends Handler_Protected {
$pdo = Db::pdo();
$sth = $pdo->prepare("SELECT DISTINCT tag_name,
owner_uid as owner FROM ttrss_tags
owner_uid as owner FROM ttrss_tags
WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
ref_id = ? AND owner_uid = ? LIMIT 1) ORDER BY tag_name");
......@@ -899,24 +936,24 @@ class Article extends Handler_Protected {
return $rv;
}
static function purge_orphans($do_output = false) {
static function purge_orphans() {
// purge orphaned posts in main content table
// purge orphaned posts in main content table
if (DB_TYPE == "mysql")
$limit_qpart = "LIMIT 5000";
else
$limit_qpart = "";
if (DB_TYPE == "mysql")
$limit_qpart = "LIMIT 5000";
else
$limit_qpart = "";
$pdo = Db::pdo();
$res = $pdo->query("DELETE FROM ttrss_entries WHERE
$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");
if ($do_output) {
$rows = $res->rowCount();
_debug("Purged $rows orphaned posts.");
}
}
if (Debug::enabled()) {
$rows = $res->rowCount();
Debug::log("Purged $rows orphaned posts.");
}
}
static function catchupArticlesById($ids, $cmode, $owner_uid = false) {
......
......@@ -130,7 +130,7 @@ class CCache {
if (!$pcat_fast) {
$sth = $pdo->prepare("SELECT id FROM ttrss_feeds
WHERE owner_uid = :uid AND
WHERE owner_uid = :uid AND
(cat_id = :cat OR (:cat = 0 AND cat_id IS NULL))");
$sth->execute([":uid" => $owner_uid, ":cat" => $feed_id]);
......@@ -141,7 +141,7 @@ class CCache {
$sth = $pdo->prepare("SELECT SUM(value) AS sv
FROM ttrss_counters_cache, ttrss_feeds
WHERE id = feed_id AND
WHERE id = feed_id AND
(cat_id = :cat OR (:cat = 0 AND cat_id IS NULL)) AND
ttrss_counters_cache.owner_uid = :uid AND
ttrss_feeds.owner_uid = :uid");
......
......@@ -49,13 +49,15 @@ class Db
error_reporting($er);
}
private function pdo_connect() {
// this really shouldn't be used unless a separate PDO connection is needed
// normal usage is Db::pdo()->prepare(...) etc
public function pdo_connect() {
$db_port = defined('DB_PORT') && DB_PORT ? ';port=' . DB_PORT : '';
$db_host = defined('DB_HOST') && DB_HOST ? ';host=' . DB_HOST : '';
try {
$this->pdo = new PDO(DB_TYPE . ':dbname=' . DB_NAME . $db_host . $db_port,
$pdo = new PDO(DB_TYPE . ':dbname=' . DB_NAME . $db_host . $db_port,
DB_USER,
DB_PASS);
} catch (Exception $e) {
......@@ -63,22 +65,31 @@ class Db
exit(101);
}
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
if (DB_TYPE == "pgsql") {
$this->pdo->query("set client_encoding = 'UTF-8'");
$this->pdo->query("set datestyle = 'ISO, european'");
$this->pdo->query("set TIME ZONE 0");
$this->pdo->query("set cpu_tuple_cost = 0.5");
$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 (DB_TYPE == "mysql") {
$this->pdo->query("SET time_zone = '+0:0'");
$pdo->query("SET time_zone = '+0:0'");
if (defined('MYSQL_CHARSET') && MYSQL_CHARSET) {
$this->pdo->query("SET NAMES " . MYSQL_CHARSET);
$pdo->query("SET NAMES " . MYSQL_CHARSET);
}
}
return $pdo;
}
public static function instance() {
if (self::$instance == null)
self::$instance = new self();
return self::$instance;
}
public static function get() {
......@@ -97,9 +108,9 @@ class Db
self::$instance = new self();
if (!self::$instance->pdo) {
self::$instance->pdo_connect();
self::$instance->pdo = self::$instance->pdo_connect();
}
return self::$instance->pdo;
}
}
\ No newline at end of file
}
......@@ -53,16 +53,14 @@ class Db_Prefs {
function read($pref_name, $user_id = false, $die_on_error = false) {
$profile = false;
if (!$user_id) {
$user_id = $_SESSION["uid"];
@$profile = $_SESSION["profile"];
} else {
$user_id = sprintf("%d", $user_id);
$profile = false;
}
if (isset($this->cache[$pref_name]) && !$user_id) {
if ($user_id == $_SESSION['uid'] && isset($this->cache[$pref_name])) {
$tuple = $this->cache[$pref_name];
return $this->convert($tuple["value"], $tuple["type"]);
}
......@@ -114,8 +112,6 @@ class Db_Prefs {
if (!$user_id) {
$user_id = $_SESSION["uid"];
@$profile = $_SESSION["profile"];
} else {
$user_id = sprintf("%d", $user_id);
}
if (!$profile || get_schema_version() < 63) $profile = null;
......@@ -149,7 +145,7 @@ class Db_Prefs {
$value = "false";
}
} else if ($type_name == "integer") {
$value = sprintf("%d", $value);
$value = (int)$value;
}
if ($pref_name == 'USER_TIMEZONE' && $value == '') {
......
......@@ -47,8 +47,8 @@ class DbUpdater {
print_notice("Query: $line");
print_error("Error: " . implode(", ", $this->pdo->errorInfo()));
} else {
_debug("Query: $line");
_debug("Error: " . implode(", ", $this->pdo->errorInfo()));
Debug::log("Query: $line");
Debug::log("Error: " . implode(", ", $this->pdo->errorInfo()));
}
return false;
......
<?php
class Debug {
public static $LOG_NORMAL = 0;
public static $LOG_VERBOSE = 1;
public static $LOG_EXTENDED = 2;
private static $enabled = false;
private static $quiet = false;
private static $logfile = false;
private static $loglevel = 0;
public static function set_logfile($logfile) {
Debug::$logfile = $logfile;
}
public static function enabled() {
return Debug::$enabled;
}
public static function set_enabled($enable) {
Debug::$enabled = $enable;
}
public static function set_quiet($quiet) {
Debug::$quiet = $quiet;
}
public static function set_loglevel($level) {
Debug::$loglevel = $level;
}
public static function get_loglevel() {
return Debug::$loglevel;
}
public static function log($message, $level = 0) {
if (!Debug::$enabled || Debug::$loglevel < $level) return false;
$ts = strftime("%H:%M:%S", time());
if (function_exists('posix_getpid')) {
$ts = "$ts/" . posix_getpid();
}
if (Debug::$logfile) {
$fp = fopen(Debug::$logfile, 'a+');
if ($fp) {
$locked = false;
if (function_exists("flock")) {
$tries = 0;
// try to lock logfile for writing
while ($tries < 5 && !$locked = flock($fp, LOCK_EX | LOCK_NB)) {
sleep(1);
++$tries;
}
if (!$locked) {
fclose($fp);
user_error("Unable to lock debugging log file: " . Debug::$logfile, E_USER_WARNING);
return;
}
}
fputs($fp, "[$ts] $message\n");
if (function_exists("flock")) {
flock($fp, LOCK_UN);
}
fclose($fp);
if (Debug::$quiet)
return;
} else {
user_error("Unable to open debugging log file: " . Debug::$logfile, E_USER_WARNING);
}
}
print "[$ts] $message\n";
}
}
\ No newline at end of file
......@@ -9,14 +9,12 @@ class Digest
* @param integer $limit The maximum number of articles by digest.
* @return boolean Return false if digests are not enabled.
*/
static function send_headlines_digests($debug = false) {
require_once 'classes/ttrssmailer.php';
static function send_headlines_digests() {
$user_limit = 15; // amount of users to process (e.g. emails to send out)
$limit = 1000; // maximum amount of headlines to include
if ($debug) _debug("Sending digests, batch of max $user_limit users, headline limit = $limit");
Debug::log("Sending digests, batch of max $user_limit users, headline limit = $limit");
if (DB_TYPE == "pgsql") {
$interval_qpart = "last_digest_sent < NOW() - INTERVAL '1 days'";
......@@ -39,7 +37,7 @@ class Digest
time() - $preferred_ts <= 7200
) {
if ($debug) _debug("Sending digest for UID:" . $line['id'] . " - " . $line["email"]);
Debug::log("Sending digest for UID:" . $line['id'] . " - " . $line["email"]);
$do_catchup = get_pref('DIGEST_CATCHUP', $line['id'], false);
......@@ -56,20 +54,26 @@ class Digest
if ($headlines_count > 0) {
$mail = new ttrssMailer();
$mailer = new Mailer();
//$rc = $mail->quickMail($line["email"], $line["login"], DIGEST_SUBJECT, $digest, $digest_text);
$rc = $mail->quickMail($line["email"], $line["login"], DIGEST_SUBJECT, $digest, $digest_text);
$rc = $mailer->mail(["to_name" => $line["login"],
"to_address" => $line["email"],
"subject" => DIGEST_SUBJECT,
"message" => $digest_text,
"message_html" => $digest]);
if (!$rc && $debug) _debug("ERROR: " . $mail->ErrorInfo);
//if (!$rc && $debug) Debug::log("ERROR: " . $mailer->lastError());
if ($debug) _debug("RC=$rc");
Debug::log("RC=$rc");
if ($rc && $do_catchup) {
if ($debug) _debug("Marking affected articles as read...");
Debug::log("Marking affected articles as read...");
Article::catchupArticlesById($affected_ids, 0, $line["id"]);
}
} else {
if ($debug) _debug("No headlines");
Debug::log("No headlines");
}
$sth = $pdo->prepare("UPDATE ttrss_users SET last_digest_sent = NOW()
......@@ -80,7 +84,7 @@ class Digest
}
}
if ($debug) _debug("All done.");
Debug::log("All done.");
}
......@@ -198,4 +202,4 @@ class Digest
return array($tmp, $headlines_count, $affected_ids, $tmp_t);
}
}
\ No newline at end of file
}
......@@ -11,5 +11,6 @@ abstract class FeedItem {
abstract function get_categories();
abstract function get_enclosures();
abstract function get_author();
abstract function get_language();
}
<?php
class FeedItem_Atom extends FeedItem_Common {
const NS_XML = "http://www.w3.org/XML/1998/namespace";
function get_id() {
$id = $this->elem->getElementsByTagName("id")->item(0);
......@@ -137,64 +138,23 @@ class FeedItem_Atom extends FeedItem_Common {
}
}
$enclosures = $this->xpath->query("media:content", $this->elem);
$encs = array_merge($encs, parent::get_enclosures());
foreach ($enclosures as $enclosure) {
$enc = new FeedEnclosure();
$enc->type = $enclosure->getAttribute("type");
$enc->link = $enclosure->getAttribute("url");
$enc->length = $enclosure->getAttribute("length");
$enc->height = $enclosure->getAttribute("height");
$enc->width = $enclosure->getAttribute("width");
$desc = $this->xpath->query("media:description", $enclosure)->item(0);
if ($desc) $enc->title = strip_tags($desc->nodeValue);
array_push($encs, $enc);
}
$enclosures = $this->xpath->query("media:group", $this->elem);
foreach ($enclosures as $enclosure) {
$enc = new FeedEnclosure();
$content = $this->xpath->query("media:content", $enclosure)->item(0);
return $encs;
}
if ($content) {
$enc->type = $content->getAttribute("type");
$enc->link = $content->getAttribute("url");
$enc->length = $content->getAttribute("length");
$enc->height = $content->getAttribute("height");
$enc->width = $content->getAttribute("width");
function get_language() {
$lang = $this->elem->getAttributeNS(self::NS_XML, "lang");
$desc = $this->xpath->query("media:description", $content)->item(0);
if ($desc) {
$enc->title = strip_tags($desc->nodeValue);
} else {
$desc = $this->xpath->query("media:description", $enclosure)->item(0);
if ($desc) $enc->title = strip_tags($desc->nodeValue);
if (!empty($lang)) {
return $lang;
} else {