diff options
78 files changed, 5399 insertions, 5944 deletions
diff --git a/atom-to-html.xsl b/atom-to-html.xsl index 1b827bb8f..f32d59983 100644 --- a/atom-to-html.xsl +++ b/atom-to-html.xsl @@ -9,11 +9,11 @@ <html> <head> <title><xsl:value-of select="atom:title"/></title> - <link rel="stylesheet" type="text/css" href="css/utility.css"/> + <link rel="stylesheet" type="text/css" href="css/default.css"/> <script language="javascript" src="lib/xsl_mop-up.js"></script> </head> - <body onload="go_decoding()"> + <body onload="go_decoding()" class="ttrss_utility"> <div id="cometestme" style="display:none;"> <xsl:text disable-output-escaping="yes">&amp;</xsl:text> diff --git a/cache/feeds/.empty b/cache/feeds/.empty new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/cache/feeds/.empty diff --git a/classes/api.php b/classes/api.php index bb4d33247..4c321d77e 100644 --- a/classes/api.php +++ b/classes/api.php @@ -8,6 +8,10 @@ class API extends Handler { private $seq; + static function param_to_bool($p) { + return $p && ($p !== "f" && $p !== "false"); + } + function before($method) { if (parent::before($method)) { header("Content-Type: text/json"); @@ -22,7 +26,7 @@ class API extends Handler { return false; } - $this->seq = (int) $_REQUEST['seq']; + $this->seq = (int) clean($_REQUEST['seq']); return true; } @@ -49,16 +53,17 @@ class API extends Handler { @session_destroy(); @session_start(); - $login = $this->dbh->escape_string($_REQUEST["user"]); - $password = $_REQUEST["password"]; - $password_base64 = base64_decode($_REQUEST["password"]); + $login = clean($_REQUEST["user"]); + $password = clean($_REQUEST["password"]); + $password_base64 = base64_decode(clean($_REQUEST["password"])); if (SINGLE_USER_MODE) $login = "admin"; - $result = $this->dbh->query("SELECT id FROM ttrss_users WHERE login = '$login'"); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE login = ?"); + $sth->execute([$login]); - if ($this->dbh->num_rows($result) != 0) { - $uid = $this->dbh->fetch_result($result, 0, "id"); + if ($row = $sth->fetch()) { + $uid = $row["id"]; } else { $uid = 0; } @@ -95,8 +100,8 @@ class API extends Handler { } function getUnread() { - $feed_id = $this->dbh->escape_string($_REQUEST["feed_id"]); - $is_cat = $this->dbh->escape_string($_REQUEST["is_cat"]); + $feed_id = clean($_REQUEST["feed_id"]); + $is_cat = clean($_REQUEST["is_cat"]); if ($feed_id) { $this->wrap(self::STATUS_OK, array("unread" => getFeedUnread($feed_id, $is_cat))); @@ -111,11 +116,11 @@ class API extends Handler { } function getFeeds() { - $cat_id = $this->dbh->escape_string($_REQUEST["cat_id"]); - $unread_only = sql_bool_to_bool($_REQUEST["unread_only"]); - $limit = (int) $this->dbh->escape_string($_REQUEST["limit"]); - $offset = (int) $this->dbh->escape_string($_REQUEST["offset"]); - $include_nested = sql_bool_to_bool($_REQUEST["include_nested"]); + $cat_id = clean($_REQUEST["cat_id"]); + $unread_only = API::param_to_bool(clean($_REQUEST["unread_only"])); + $limit = (int) clean($_REQUEST["limit"]); + $offset = (int) clean($_REQUEST["offset"]); + $include_nested = API::param_to_bool(clean($_REQUEST["include_nested"])); $feeds = $this->api_get_feeds($cat_id, $unread_only, $limit, $offset, $include_nested); @@ -123,9 +128,9 @@ class API extends Handler { } function getCategories() { - $unread_only = sql_bool_to_bool($_REQUEST["unread_only"]); - $enable_nested = sql_bool_to_bool($_REQUEST["enable_nested"]); - $include_empty = sql_bool_to_bool($_REQUEST['include_empty']); + $unread_only = API::param_to_bool(clean($_REQUEST["unread_only"])); + $enable_nested = API::param_to_bool(clean($_REQUEST["enable_nested"])); + $include_empty = API::param_to_bool(clean($_REQUEST['include_empty'])); // TODO do not return empty categories, return Uncategorized and standard virtual cats @@ -134,7 +139,7 @@ class API extends Handler { else $nested_qpart = "true"; - $result = $this->dbh->query("SELECT + $sth = $this->pdo->prepare("SELECT id, title, order_id, (SELECT COUNT(id) FROM ttrss_feeds WHERE ttrss_feed_categories.id IS NOT NULL AND cat_id = ttrss_feed_categories.id) AS num_feeds, @@ -142,12 +147,12 @@ class API extends Handler { ttrss_feed_categories AS c2 WHERE c2.parent_cat = ttrss_feed_categories.id) AS num_cats FROM ttrss_feed_categories - WHERE $nested_qpart AND owner_uid = " . - $_SESSION["uid"]); + WHERE $nested_qpart AND owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); $cats = array(); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { if ($include_empty || $line["num_feeds"] > 0 || $line["num_cats"] > 0) { $unread = getFeedUnread($line["id"], true); @@ -180,39 +185,39 @@ class API extends Handler { } function getHeadlines() { - $feed_id = $this->dbh->escape_string($_REQUEST["feed_id"]); + $feed_id = clean($_REQUEST["feed_id"]); if ($feed_id != "") { if (is_numeric($feed_id)) $feed_id = (int) $feed_id; - $limit = (int)$this->dbh->escape_string($_REQUEST["limit"]); + $limit = (int)clean($_REQUEST["limit"]); if (!$limit || $limit >= 200) $limit = 200; - $offset = (int)$this->dbh->escape_string($_REQUEST["skip"]); - $filter = $this->dbh->escape_string($_REQUEST["filter"]); - $is_cat = sql_bool_to_bool($_REQUEST["is_cat"]); - $show_excerpt = sql_bool_to_bool($_REQUEST["show_excerpt"]); - $show_content = sql_bool_to_bool($_REQUEST["show_content"]); + $offset = (int)clean($_REQUEST["skip"]); + $filter = clean($_REQUEST["filter"]); + $is_cat = API::param_to_bool(clean($_REQUEST["is_cat"])); + $show_excerpt = API::param_to_bool(clean($_REQUEST["show_excerpt"])); + $show_content = API::param_to_bool(clean($_REQUEST["show_content"])); /* all_articles, unread, adaptive, marked, updated */ - $view_mode = $this->dbh->escape_string($_REQUEST["view_mode"]); - $include_attachments = sql_bool_to_bool($_REQUEST["include_attachments"]); - $since_id = (int)$this->dbh->escape_string($_REQUEST["since_id"]); - $include_nested = sql_bool_to_bool($_REQUEST["include_nested"]); + $view_mode = clean($_REQUEST["view_mode"]); + $include_attachments = API::param_to_bool(clean($_REQUEST["include_attachments"])); + $since_id = (int)clean($_REQUEST["since_id"]); + $include_nested = API::param_to_bool(clean($_REQUEST["include_nested"])); $sanitize_content = !isset($_REQUEST["sanitize"]) || - sql_bool_to_bool($_REQUEST["sanitize"]); - $force_update = sql_bool_to_bool($_REQUEST["force_update"]); - $has_sandbox = sql_bool_to_bool($_REQUEST["has_sandbox"]); - $excerpt_length = (int)$this->dbh->escape_string($_REQUEST["excerpt_length"]); - $check_first_id = (int)$this->dbh->escape_string($_REQUEST["check_first_id"]); - $include_header = sql_bool_to_bool($_REQUEST["include_header"]); + API::param_to_bool($_REQUEST["sanitize"]); + $force_update = API::param_to_bool(clean($_REQUEST["force_update"])); + $has_sandbox = API::param_to_bool(clean($_REQUEST["has_sandbox"])); + $excerpt_length = (int)clean($_REQUEST["excerpt_length"]); + $check_first_id = (int)clean($_REQUEST["check_first_id"]); + $include_header = API::param_to_bool(clean($_REQUEST["include_header"])); $_SESSION['hasSandbox'] = $has_sandbox; $skip_first_id_check = false; $override_order = false; - switch ($_REQUEST["order_by"]) { + switch (clean($_REQUEST["order_by"])) { case "title": $override_order = "ttrss_entries.title, date_entered, updated"; break; @@ -227,7 +232,7 @@ class API extends Handler { /* do not rely on params below */ - $search = $this->dbh->escape_string($_REQUEST["search"]); + $search = clean($_REQUEST["search"]); list($headlines, $headlines_header) = $this->api_get_headlines($feed_id, $limit, $offset, $filter, $is_cat, $show_excerpt, $show_content, $view_mode, $override_order, @@ -245,10 +250,10 @@ class API extends Handler { } function updateArticle() { - $article_ids = array_filter(explode(",", $this->dbh->escape_string($_REQUEST["article_ids"])), is_numeric); - $mode = (int) $this->dbh->escape_string($_REQUEST["mode"]); - $data = $this->dbh->escape_string($_REQUEST["data"]); - $field_raw = (int)$this->dbh->escape_string($_REQUEST["field"]); + $article_ids = explode(",", clean($_REQUEST["article_ids"])); + $mode = (int) clean($_REQUEST["mode"]); + $data = clean($_REQUEST["data"]); + $field_raw = (int)clean($_REQUEST["field"]); $field = ""; $set_to = ""; @@ -282,21 +287,25 @@ class API extends Handler { break; } - if ($field == "note") $set_to = "'$data'"; + if ($field == "note") $set_to = $this->pdo->quote($data); if ($field && $set_to && count($article_ids) > 0) { - $article_ids = join(", ", $article_ids); + $article_qmarks = arr_qmarks($article_ids); - $result = $this->dbh->query("UPDATE ttrss_user_entries SET $field = $set_to $additional_fields WHERE ref_id IN ($article_ids) AND owner_uid = " . $_SESSION["uid"]); + $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']])); - $num_updated = $this->dbh->affected_rows($result); + $num_updated = $sth->rowCount(); if ($num_updated > 0 && $field == "unread") { - $result = $this->dbh->query("SELECT DISTINCT feed_id FROM ttrss_user_entries - WHERE ref_id IN ($article_ids)"); + $sth = $this->pdo->prepare("SELECT DISTINCT feed_id FROM ttrss_user_entries + WHERE ref_id IN ($article_qmarks)"); + $sth->execute($article_ids); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { CCache::update($line["feed_id"], $_SESSION["uid"]); } } @@ -312,69 +321,66 @@ class API extends Handler { function getArticle() { - $article_id = join(",", array_filter(explode(",", $this->dbh->escape_string($_REQUEST["article_id"])), is_numeric)); + $article_ids = explode(",", clean($_REQUEST["article_id"])); $sanitize_content = !isset($_REQUEST["sanitize"]) || - sql_bool_to_bool($_REQUEST["sanitize"]); + API::param_to_bool($_REQUEST["sanitize"]); + + if ($article_ids) { - if ($article_id) { + $article_qmarks = arr_qmarks($article_ids); - $query = "SELECT id,guid,title,link,content,feed_id,comments,int_id, + $sth = $this->pdo->prepare("SELECT id,guid,title,link,content,feed_id,comments,int_id, marked,unread,published,score,note,lang, ".SUBSTRING_FOR_DATE."(updated,1,16) as updated, author,(SELECT title FROM ttrss_feeds WHERE id = feed_id) AS feed_title, (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) AS site_url, (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images FROM ttrss_entries,ttrss_user_entries - WHERE id IN ($article_id) AND ref_id = id AND owner_uid = " . - $_SESSION["uid"] ; + WHERE id IN ($article_qmarks) AND ref_id = id AND owner_uid = ?"); - $result = $this->dbh->query($query); + $sth->execute(array_merge($article_ids, [$_SESSION['uid']])); $articles = array(); - if ($this->dbh->num_rows($result) != 0) { - - while ($line = $this->dbh->fetch_assoc($result)) { - - $attachments = Article::get_article_enclosures($line['id']); - - $article = array( - "id" => $line["id"], - "guid" => $line["guid"], - "title" => $line["title"], - "link" => $line["link"], - "labels" => Article::get_article_labels($line['id']), - "unread" => sql_bool_to_bool($line["unread"]), - "marked" => sql_bool_to_bool($line["marked"]), - "published" => sql_bool_to_bool($line["published"]), - "comments" => $line["comments"], - "author" => $line["author"], - "updated" => (int) strtotime($line["updated"]), - "feed_id" => $line["feed_id"], - "attachments" => $attachments, - "score" => (int)$line["score"], - "feed_title" => $line["feed_title"], - "note" => $line["note"], - "lang" => $line["lang"] - ); - - if ($sanitize_content) { - $article["content"] = sanitize( - $line["content"], - sql_bool_to_bool($line['hide_images']), - false, $line["site_url"], false, $line["id"]); - } else { - $article["content"] = $line["content"]; - } - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_API) as $p) { - $article = $p->hook_render_article_api(array("article" => $article)); - } + while ($line = $sth->fetch()) { + + $attachments = Article::get_article_enclosures($line['id']); + + $article = array( + "id" => $line["id"], + "guid" => $line["guid"], + "title" => $line["title"], + "link" => $line["link"], + "labels" => Article::get_article_labels($line['id']), + "unread" => API::param_to_bool($line["unread"]), + "marked" => API::param_to_bool($line["marked"]), + "published" => API::param_to_bool($line["published"]), + "comments" => $line["comments"], + "author" => $line["author"], + "updated" => (int) strtotime($line["updated"]), + "feed_id" => $line["feed_id"], + "attachments" => $attachments, + "score" => (int)$line["score"], + "feed_title" => $line["feed_title"], + "note" => $line["note"], + "lang" => $line["lang"] + ); + + if ($sanitize_content) { + $article["content"] = sanitize( + $line["content"], + API::param_to_bool($line['hide_images']), + false, $line["site_url"], false, $line["id"]); + } else { + $article["content"] = $line["content"]; + } + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_API) as $p) { + $article = $p->hook_render_article_api(array("article" => $article)); + } - array_push($articles, $article); + array_push($articles, $article); - } } $this->wrap(self::STATUS_OK, $articles); @@ -390,18 +396,18 @@ class API extends Handler { $config["daemon_is_running"] = file_is_locked("update_daemon.lock"); - $result = $this->dbh->query("SELECT COUNT(*) AS cf FROM - ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]); - - $num_feeds = $this->dbh->fetch_result($result, 0, "cf"); + $sth = $this->pdo->prepare("SELECT COUNT(*) AS cf FROM + ttrss_feeds WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + $row = $sth->fetch(); - $config["num_feeds"] = (int)$num_feeds; + $config["num_feeds"] = $row["cf"]; $this->wrap(self::STATUS_OK, $config); } function updateFeed() { - $feed_id = (int) $this->dbh->escape_string($_REQUEST["feed_id"]); + $feed_id = (int) clean($_REQUEST["feed_id"]); if (!ini_get("open_basedir")) { RSSUtils::update_rss_feed($feed_id); @@ -411,8 +417,8 @@ class API extends Handler { } function catchupFeed() { - $feed_id = $this->dbh->escape_string($_REQUEST["feed_id"]); - $is_cat = $this->dbh->escape_string($_REQUEST["is_cat"]); + $feed_id = clean($_REQUEST["feed_id"]); + $is_cat = clean($_REQUEST["is_cat"]); Feeds::catchup_feed($feed_id, $is_cat); @@ -420,28 +426,27 @@ class API extends Handler { } function getPref() { - $pref_name = $this->dbh->escape_string($_REQUEST["pref_name"]); + $pref_name = clean($_REQUEST["pref_name"]); $this->wrap(self::STATUS_OK, array("value" => get_pref($pref_name))); } function getLabels() { - //$article_ids = array_filter(explode(",", $this->dbh->escape_string($_REQUEST["article_ids"])), is_numeric); - - $article_id = (int)$_REQUEST['article_id']; + $article_id = (int)clean($_REQUEST['article_id']); $rv = array(); - $result = $this->dbh->query("SELECT id, caption, fg_color, bg_color + $sth = $this->pdo->prepare("SELECT id, caption, fg_color, bg_color FROM ttrss_labels2 - WHERE owner_uid = '".$_SESSION['uid']."' ORDER BY caption"); + WHERE owner_uid = ? ORDER BY caption"); + $sth->execute([$_SESSION['uid']]); if ($article_id) $article_labels = Article::get_article_labels($article_id); else $article_labels = array(); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { $checked = false; foreach ($article_labels as $al) { @@ -464,12 +469,11 @@ class API extends Handler { function setArticleLabel() { - $article_ids = array_filter(explode(",", $this->dbh->escape_string($_REQUEST["article_ids"])), is_numeric); - $label_id = (int) $this->dbh->escape_string($_REQUEST['label_id']); - $assign = sql_bool_to_bool($_REQUEST['assign']); + $article_ids = explode(",", clean($_REQUEST["article_ids"])); + $label_id = (int) clean($_REQUEST['label_id']); + $assign = API::param_to_bool(clean($_REQUEST['assign'])); - $label = $this->dbh->escape_string(Labels::find_caption( - Labels::feed_to_label_id($label_id), $_SESSION["uid"])); + $label = Labels::find_caption(Labels::feed_to_label_id($label_id), $_SESSION["uid"]); $num_updated = 0; @@ -506,9 +510,9 @@ class API extends Handler { } function shareToPublished() { - $title = $this->dbh->escape_string(strip_tags($_REQUEST["title"])); - $url = $this->dbh->escape_string(strip_tags($_REQUEST["url"])); - $content = $this->dbh->escape_string(strip_tags($_REQUEST["content"])); + $title = strip_tags(clean($_REQUEST["title"])); + $url = strip_tags(clean($_REQUEST["url"])); + $content = strip_tags(clean($_REQUEST["content"])); if (Article::create_published_article($title, $url, $content, "", $_SESSION["uid"])) { $this->wrap(self::STATUS_OK, array("status" => 'OK')); @@ -521,6 +525,12 @@ class API extends Handler { $feeds = array(); + $pdo = Db::pdo(); + + $limit = (int) $limit; + $offset = (int) $offset; + $cat_id = (int) $cat_id; + /* Labels */ if ($cat_id == -4 || $cat_id == -2) { @@ -568,12 +578,13 @@ class API extends Handler { /* Child cats */ if ($include_nested && $cat_id) { - $result = db_query("SELECT + $sth = $pdo->prepare("SELECT id, title, order_id FROM ttrss_feed_categories - WHERE parent_cat = '$cat_id' AND owner_uid = " . $_SESSION["uid"] . - " ORDER BY id, title"); + WHERE parent_cat = ? AND owner_uid = ? ORDER BY id, title"); - while ($line = db_fetch_assoc($result)) { + $sth->execute([$cat_id, $_SESSION['uid']]); + + while ($line = $sth->fetch()) { $unread = getFeedUnread($line["id"], true) + Feeds::getCategoryChildrenUnread($line["id"]); @@ -599,31 +610,30 @@ class API extends Handler { } if ($cat_id == -4 || $cat_id == -3) { - $result = db_query("SELECT + $sth = $pdo->prepare("SELECT id, feed_url, cat_id, title, order_id, ". SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated - FROM ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"] . - " ORDER BY cat_id, title " . $limit_qpart); - } else { + FROM ttrss_feeds WHERE owner_uid = ? + ORDER BY cat_id, title " . $limit_qpart); + $sth->execute([$_SESSION['uid']]); - if ($cat_id) - $cat_qpart = "cat_id = '$cat_id'"; - else - $cat_qpart = "cat_id IS NULL"; + } else { - $result = db_query("SELECT + $sth = $pdo->prepare("SELECT id, feed_url, cat_id, title, order_id, ". SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated FROM ttrss_feeds WHERE - $cat_qpart AND owner_uid = " . $_SESSION["uid"] . - " ORDER BY cat_id, title " . $limit_qpart); + (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]); } - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { $unread = getFeedUnread($line["id"]); - $has_icon = feed_has_icon($line['id']); + $has_icon = Feeds::feedHasIcon($line['id']); if ($unread || !$unread_only) { @@ -654,22 +664,26 @@ class API extends Handler { $search = "", $include_nested = false, $sanitize_content = true, $force_update = false, $excerpt_length = 100, $check_first_id = false, $skip_first_id_check = false) { + $pdo = Db::pdo(); + if ($force_update && $feed_id > 0 && is_numeric($feed_id)) { // Update the feed if required with some basic flood control - $result = db_query( + $sth = $pdo->prepare( "SELECT cache_images,".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated - FROM ttrss_feeds WHERE id = '$feed_id'"); + FROM ttrss_feeds WHERE id = ?"); + $sth->execute([$feed_id]); - if (db_num_rows($result) != 0) { - $last_updated = strtotime(db_fetch_result($result, 0, "last_updated")); - $cache_images = sql_bool_to_bool(db_fetch_result($result, 0, "cache_images")); + if ($row = $sth->fetch()) { + $last_updated = strtotime($row["last_updated"]); + $cache_images = API::param_to_bool($row["cache_images"]); if (!$cache_images && time() - $last_updated > 120) { RSSUtils::update_rss_feed($feed_id, true); } else { - db_query("UPDATE ttrss_feeds SET last_updated = '1970-01-01', last_update_started = '1970-01-01' - WHERE id = '$feed_id'"); + $sth = $pdo->prepare("UPDATE ttrss_feeds SET last_updated = '1970-01-01', last_update_started = '1970-01-01' + WHERE id = ?"); + $sth->execute([$feed_id]); } } } @@ -702,7 +716,7 @@ class API extends Handler { 'is_cat' => $is_cat); if (!is_numeric($result)) { - while ($line = db_fetch_assoc($result)) { + while ($line = $result->fetch()) { $line["content_preview"] = truncate_string(strip_tags($line["content"]), $excerpt_length); foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) { $line = $p->hook_query_headlines($line, $excerpt_length, true); @@ -732,9 +746,9 @@ class API extends Handler { $headline_row = array( "id" => (int)$line["id"], "guid" => $line["guid"], - "unread" => sql_bool_to_bool($line["unread"]), - "marked" => sql_bool_to_bool($line["marked"]), - "published" => sql_bool_to_bool($line["published"]), + "unread" => API::param_to_bool($line["unread"]), + "marked" => API::param_to_bool($line["marked"]), + "published" => API::param_to_bool($line["published"]), "updated" => (int)strtotime($line["updated"]), "is_updated" => $is_updated, "title" => $line["title"], @@ -755,7 +769,7 @@ class API extends Handler { if ($sanitize_content) { $headline_row["content"] = sanitize( $line["content"], - sql_bool_to_bool($line['hide_images']), + API::param_to_bool($line['hide_images']), false, $line["site_url"], false, $line["id"]); } else { $headline_row["content"] = $line["content"]; @@ -773,7 +787,7 @@ class API extends Handler { $headline_row["comments_count"] = (int)$line["num_comments"]; $headline_row["comments_link"] = $line["comments"]; - $headline_row["always_display_attachments"] = sql_bool_to_bool($line["always_display_enclosures"]); + $headline_row["always_display_attachments"] = API::param_to_bool($line["always_display_enclosures"]); $headline_row["author"] = $line["author"]; @@ -795,12 +809,13 @@ class API extends Handler { } function unsubscribeFeed() { - $feed_id = (int) $this->dbh->escape_string($_REQUEST["feed_id"]); + $feed_id = (int) clean($_REQUEST["feed_id"]); - $result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE - id = '$feed_id' AND owner_uid = ".$_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE + id = ? AND owner_uid = ?"); + $sth->execute([$feed_id, $_SESSION['uid']]); - if ($this->dbh->num_rows($result) != 0) { + if ($row = $sth->fetch()) { Pref_Feeds::remove_feed($feed_id, $_SESSION["uid"]); $this->wrap(self::STATUS_OK, array("status" => "OK")); } else { @@ -809,10 +824,10 @@ class API extends Handler { } function subscribeToFeed() { - $feed_url = $this->dbh->escape_string($_REQUEST["feed_url"]); - $category_id = (int) $this->dbh->escape_string($_REQUEST["category_id"]); - $login = $this->dbh->escape_string($_REQUEST["login"]); - $password = $this->dbh->escape_string($_REQUEST["password"]); + $feed_url = clean($_REQUEST["feed_url"]); + $category_id = (int) clean($_REQUEST["category_id"]); + $login = clean($_REQUEST["login"]); + $password = clean($_REQUEST["password"]); if ($feed_url) { $rc = Feeds::subscribe_to_feed($feed_url, $category_id, $login, $password); @@ -824,7 +839,7 @@ class API extends Handler { } function getFeedTree() { - $include_empty = sql_bool_to_bool($_REQUEST['include_empty']); + $include_empty = API::param_to_bool(clean($_REQUEST['include_empty'])); $pf = new Pref_Feeds($_REQUEST); @@ -845,16 +860,20 @@ class API extends Handler { private function isCategoryEmpty($id) { if ($id == -2) { - $result = $this->dbh->query("SELECT COUNT(*) AS count FROM ttrss_labels2 - WHERE owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT COUNT(id) AS count FROM ttrss_labels2 + WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + $row = $sth->fetch(); - return $this->dbh->fetch_result($result, 0, "count") == 0; + return $row["count"] == 0; } else if ($id == 0) { - $result = $this->dbh->query("SELECT COUNT(*) AS count FROM ttrss_feeds - WHERE cat_id IS NULL AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT COUNT(id) AS count FROM ttrss_feeds + WHERE cat_id IS NULL AND owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + $row = $sth->fetch(); - return $this->dbh->fetch_result($result, 0, "count") == 0; + return $row["count"] == 0; } diff --git a/classes/article.php b/classes/article.php index 1d5f06e70..edf95b743 100644 --- a/classes/article.php +++ b/classes/article.php @@ -8,14 +8,15 @@ class Article extends Handler_Protected { } function redirect() { - $id = $this->dbh->escape_string($_REQUEST['id']); + $id = clean($_REQUEST['id']); - $result = $this->dbh->query("SELECT link FROM ttrss_entries, ttrss_user_entries - WHERE id = '$id' AND id = ref_id AND owner_uid = '".$_SESSION['uid']."' + $sth = $this->pdo->prepare("SELECT link FROM ttrss_entries, ttrss_user_entries + WHERE id = ? AND id = ref_id AND owner_uid = ? LIMIT 1"); + $sth->execute([$id, $_SESSION['uid']]); - if ($this->dbh->num_rows($result) == 1) { - $article_url = $this->dbh->fetch_result($result, 0, 'link'); + if ($row = $sth->fetch()) { + $article_url = $row['link']; $article_url = str_replace("\n", "", $article_url); header("Location: $article_url"); @@ -27,9 +28,9 @@ class Article extends Handler_Protected { } function view() { - $id = $this->dbh->escape_string($_REQUEST["id"]); - $cids = explode(",", $this->dbh->escape_string($_REQUEST["cids"])); - $mode = $this->dbh->escape_string($_REQUEST["mode"]); + $id = clean($_REQUEST["id"]); + $cids = explode(",", clean($_REQUEST["cids"])); + $mode = clean($_REQUEST["mode"]); // in prefetch mode we only output requested cids, main article // just gets marked as read (it already exists in client cache) @@ -67,19 +68,21 @@ class Article extends Handler_Protected { private function catchupArticleById($id, $cmode) { if ($cmode == 0) { - $this->dbh->query("UPDATE ttrss_user_entries SET + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET unread = false,last_read = NOW() - WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]); + WHERE ref_id = ? AND owner_uid = ?"); } else if ($cmode == 1) { - $this->dbh->query("UPDATE ttrss_user_entries SET + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET unread = true - WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]); + WHERE ref_id = ? AND owner_uid = ?"); } else { - $this->dbh->query("UPDATE ttrss_user_entries SET + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET unread = NOT unread,last_read = NOW() - WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]); + WHERE ref_id = ? AND owner_uid = ?"); } + $sth->execute([$id, $_SESSION['uid']]); + $feed_id = $this->getArticleFeed($id); CCache::update($feed_id, $_SESSION["uid"]); } @@ -102,7 +105,7 @@ class Article extends Handler_Protected { if ($enable_share_anything) { $extracted_content = $af_readability->extract_content($url); - if ($extracted_content) $content = db_escape_string($extracted_content); + if ($extracted_content) $content = $extracted_content; } } } @@ -122,34 +125,42 @@ class Article extends Handler_Protected { if (filter_var($url, FILTER_VALIDATE_URL) === FALSE) return false; - db_query("BEGIN"); + $pdo = Db::pdo(); + + $pdo->beginTransaction(); // only check for our user data here, others might have shared this with different content etc - $result = db_query("SELECT id FROM ttrss_entries, ttrss_user_entries WHERE - guid = '$guid' AND ref_id = id AND owner_uid = '$owner_uid' LIMIT 1"); + $sth = $pdo->prepare("SELECT id FROM ttrss_entries, ttrss_user_entries WHERE + guid = ? AND ref_id = id AND owner_uid = ? LIMIT 1"); + $sth->execute([$guid, $owner_uid]); - if (db_num_rows($result) != 0) { - $ref_id = db_fetch_result($result, 0, "id"); + if ($row = $sth->fetch()) { + $ref_id = $row['id']; - $result = db_query("SELECT int_id FROM ttrss_user_entries WHERE - ref_id = '$ref_id' AND owner_uid = '$owner_uid' LIMIT 1"); + $sth = $pdo->prepare("SELECT int_id FROM ttrss_user_entries WHERE + ref_id = ? AND owner_uid = ? LIMIT 1"); + $sth->execute([$ref_id, $owner_uid]); - if (db_num_rows($result) != 0) { - $int_id = db_fetch_result($result, 0, "int_id"); + if ($row = $sth->fetch()) { + $int_id = $row['int_id']; - db_query("UPDATE ttrss_entries SET - content = '$content', content_hash = '$content_hash' WHERE id = '$ref_id'"); + $sth = $pdo->prepare("UPDATE ttrss_entries SET + content = ?, content_hash = ? WHERE id = ?"); + $sth->execute([$content, $content_hash, $ref_id]); - db_query("UPDATE ttrss_user_entries SET published = true, + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET published = true, last_published = NOW() WHERE - int_id = '$int_id' AND owner_uid = '$owner_uid'"); + int_id = ? AND owner_uid = ?"); + $sth->execute([$int_id, $owner_uid]); + } else { - db_query("INSERT INTO ttrss_user_entries + $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) VALUES - ('$ref_id', '', NULL, NULL, $owner_uid, true, '', '', NOW(), '', false, NOW())"); + (?, '', NULL, NULL, ?, true, '', '', NOW(), '', false, NOW())"); + $sth->execute([$ref_id, $owner_uid]); } if (count($labels) != 0) { @@ -161,21 +172,24 @@ class Article extends Handler_Protected { $rc = true; } else { - $result = db_query("INSERT INTO ttrss_entries + $sth = $pdo->prepare("INSERT INTO ttrss_entries (title, guid, link, updated, content, content_hash, date_entered, date_updated) VALUES - ('$title', '$guid', '$url', NOW(), '$content', '$content_hash', NOW(), NOW())"); + (?, ?, ?, NOW(), ?, ?, NOW(), NOW())"); + $sth->execute([$title, $guid, $url, $content, $content_hash]); - $result = db_query("SELECT id FROM ttrss_entries WHERE guid = '$guid'"); + $sth = $pdo->prepare("SELECT id FROM ttrss_entries WHERE guid = ?"); + $sth->execute([$guid]); - if (db_num_rows($result) != 0) { - $ref_id = db_fetch_result($result, 0, "id"); + if ($row = $sth->fetch()) { + $ref_id = $row["id"]; - db_query("INSERT INTO ttrss_user_entries + $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) VALUES - ('$ref_id', '', NULL, NULL, $owner_uid, true, '', '', NOW(), '', false, NOW())"); + (?, '', NULL, NULL, ?, true, '', '', NOW(), '', false, NOW())"); + $sth->execute([$ref_id, $owner_uid]); if (count($labels) != 0) { foreach ($labels as $label) { @@ -187,7 +201,7 @@ class Article extends Handler_Protected { } } - db_query("COMMIT"); + $pdo->commit(); return $rc; } @@ -196,9 +210,9 @@ class Article extends Handler_Protected { print __("Tags for this article (separated by commas):")."<br>"; - $param = $this->dbh->escape_string($_REQUEST['param']); + $param = clean($_REQUEST['param']); - $tags = Article::get_article_tags($this->dbh->escape_string($param)); + $tags = Article::get_article_tags($param); $tags_str = join(", ", $tags); @@ -227,11 +241,15 @@ class Article extends Handler_Protected { } function setScore() { - $ids = $this->dbh->escape_string($_REQUEST['id']); - $score = (int)$this->dbh->escape_string($_REQUEST['score']); + $ids = explode(",", clean($_REQUEST['id'])); + $score = (int)clean($_REQUEST['score']); + + $ids_qmarks = arr_qmarks($ids); + + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET + score = ? WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); - $this->dbh->query("UPDATE ttrss_user_entries SET - score = '$score' WHERE ref_id IN ($ids) AND owner_uid = " . $_SESSION["uid"]); + $sth->execute(array_merge([$score], $ids, [$_SESSION['uid']])); print json_encode(array("id" => $ids, "score" => (int)$score, @@ -239,10 +257,13 @@ class Article extends Handler_Protected { } function getScore() { - $id = $this->dbh->escape_string($_REQUEST['id']); + $id = clean($_REQUEST['id']); - $result = $this->dbh->query("SELECT score FROM ttrss_user_entries WHERE ref_id = $id AND owner_uid = " . $_SESSION["uid"]); - $score = $this->dbh->fetch_result($result, 0, "score"); + $sth = $this->pdo->prepare("SELECT score FROM ttrss_user_entries WHERE ref_id = ? AND owner_uid = ?"); + $sth->execute([$id, $_SESSION['uid']]); + $row = $sth->fetch(); + + $score = $row['score']; print json_encode(array("id" => $id, "score" => (int)$score, @@ -252,24 +273,26 @@ class Article extends Handler_Protected { function setArticleTags() { - $id = $this->dbh->escape_string($_REQUEST["id"]); + $id = clean($_REQUEST["id"]); - $tags_str = $this->dbh->escape_string($_REQUEST["tags_str"]); + $tags_str = clean($_REQUEST["tags_str"]); $tags = array_unique(trim_array(explode(",", $tags_str))); - $this->dbh->query("BEGIN"); + $this->pdo->beginTransaction(); - $result = $this->dbh->query("SELECT int_id FROM ttrss_user_entries WHERE - ref_id = '$id' AND owner_uid = '".$_SESSION["uid"]."' LIMIT 1"); + $sth = $this->pdo->prepare("SELECT int_id FROM ttrss_user_entries WHERE + ref_id = ? AND owner_uid = ? LIMIT 1"); + $sth->execute([$id, $_SESSION['uid']]); - if ($this->dbh->num_rows($result) == 1) { + if ($row = $sth->fetch()) { $tags_to_cache = array(); - $int_id = $this->dbh->fetch_result($result, 0, "int_id"); + $int_id = $row['int_id']; - $this->dbh->query("DELETE FROM ttrss_tags WHERE - post_int_id = $int_id AND owner_uid = '".$_SESSION["uid"]."'"); + $sth = $this->pdo->prepare("DELETE FROM ttrss_tags WHERE + post_int_id = ? AND owner_uid = ?"); + $sth->execute([$int_id, $_SESSION['uid']]); foreach ($tags as $tag) { $tag = sanitize_tag($tag); @@ -285,8 +308,11 @@ class Article extends Handler_Protected { // print "<!-- $id : $int_id : $tag -->"; if ($tag != '') { - $this->dbh->query("INSERT INTO ttrss_tags - (post_int_id, owner_uid, tag_name) VALUES ('$int_id', '".$_SESSION["uid"]."', '$tag')"); + $sth = $this->pdo->prepare("INSERT INTO ttrss_tags + (post_int_id, owner_uid, tag_name) + VALUES (?, ?, ?)"); + + $sth->execute([$int_id, $_SESSION['uid'], $tag]); } array_push($tags_to_cache, $tag); @@ -297,12 +323,12 @@ class Article extends Handler_Protected { sort($tags_to_cache); $tags_str = join(",", $tags_to_cache); - $this->dbh->query("UPDATE ttrss_user_entries - SET tag_cache = '$tags_str' WHERE ref_id = '$id' - AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries + SET tag_cache = ? WHERE ref_id = ? AND owner_uid = ?"); + $sth->execute([$tags_str, $id, $_SESSION['uid']]); } - $this->dbh->query("COMMIT"); + $this->pdo->commit(); $tags = Article::get_article_tags($id); $tags_str = $this->format_tags_string($tags, $id); @@ -316,15 +342,17 @@ class Article extends Handler_Protected { function completeTags() { - $search = $this->dbh->escape_string($_REQUEST["search"]); + $search = clean($_REQUEST["search"]); - $result = $this->dbh->query("SELECT DISTINCT tag_name FROM ttrss_tags - WHERE owner_uid = '".$_SESSION["uid"]."' AND - tag_name LIKE '$search%' ORDER BY tag_name + $sth = $this->pdo->prepare("SELECT DISTINCT tag_name FROM ttrss_tags + WHERE owner_uid = ? AND + tag_name LIKE ? ORDER BY tag_name LIMIT 10"); + $sth->execute([$_SESSION['uid'], "$search%"]); + print "<ul>"; - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { print "<li>" . $line["tag_name"] . "</li>"; } print "</ul>"; @@ -341,10 +369,10 @@ class Article extends Handler_Protected { private function labelops($assign) { $reply = array(); - $ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"])); - $label_id = $this->dbh->escape_string($_REQUEST["lid"]); + $ids = explode(",", clean($_REQUEST["ids"])); + $label_id = clean($_REQUEST["lid"]); - $label = $this->dbh->escape_string(Labels::find_caption($label_id, + $label = db_escape_string(Labels::find_caption($label_id, $_SESSION["uid"])); $reply["info-for-headlines"] = array(); @@ -372,11 +400,12 @@ class Article extends Handler_Protected { } function getArticleFeed($id) { - $result = db_query("SELECT feed_id FROM ttrss_user_entries - WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT feed_id FROM ttrss_user_entries + WHERE ref_id = ? AND owner_uid = ?"); + $sth->execute([$id, $_SESSION['uid']]); - if (db_num_rows($result) != 0) { - return db_fetch_result($result, 0, "feed_id"); + if ($row = $sth->fetch()) { + return $row["feed_id"]; } else { return 0; } @@ -530,24 +559,29 @@ class Article extends Handler_Protected { /* we can figure out feed_id from article id anyway, why do we * pass feed_id here? let's ignore the argument :(*/ - $result = db_query("SELECT feed_id FROM ttrss_user_entries - WHERE ref_id = '$id'"); + $pdo = Db::pdo(); - $feed_id = (int) db_fetch_result($result, 0, "feed_id"); + $sth = $pdo->prepare("SELECT feed_id FROM ttrss_user_entries + WHERE ref_id = ?"); + $sth->execute([$id]); + $row = $sth->fetch(); + + $feed_id = (int) $row["feed_id"]; $rv['feed_id'] = $feed_id; //if (!$zoom_mode) { print "<article id='$id'><![CDATA["; }; if ($mark_as_read) { - $result = db_query("UPDATE ttrss_user_entries + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false,last_read = NOW() - WHERE ref_id = '$id' AND owner_uid = $owner_uid"); + WHERE ref_id = ? AND owner_uid = ?"); + $sth->execute([$id, $owner_uid]); CCache::update($feed_id, $owner_uid); } - $result = db_query("SELECT id,title,link,content,feed_id,comments,int_id,lang, + $sth = $pdo->prepare("SELECT id,title,link,content,feed_id,comments,int_id,lang, ".SUBSTRING_FOR_DATE."(updated,1,16) as updated, (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url, (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title, @@ -560,17 +594,16 @@ class Article extends Handler_Protected { orig_feed_id, note FROM ttrss_entries,ttrss_user_entries - WHERE id = '$id' AND ref_id = id AND owner_uid = $owner_uid"); - - if ($result) { + WHERE id = ? AND ref_id = id AND owner_uid = ?"); + $sth->execute([$id, $owner_uid]); - $line = db_fetch_assoc($result); + if ($line = $sth->fetch()) { $line["tags"] = Article::get_article_tags($id, $owner_uid, $line["tag_cache"]); unset($line["tag_cache"]); $line["content"] = sanitize($line["content"], - sql_bool_to_bool($line['hide_images']), + $line['hide_images'], $owner_uid, $line["site_url"], false, $line["id"]); foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) { @@ -601,12 +634,11 @@ class Article extends Handler_Protected { $rv['content'] .= "<html><head> <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/> <title>".$line["title"]."</title>". - stylesheet_tag("css/default.css")." - + 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\"> - </head><body id=\"ttrssZoom\">"; + </head><body class=\"claro ttrss_utility ttrss_zoom\">"; } $rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">"; @@ -683,18 +715,17 @@ class Article extends Handler_Protected { if ($line["orig_feed_id"]) { - $tmp_result = db_query("SELECT * FROM ttrss_archived_feeds - WHERE id = ".$line["orig_feed_id"] . " AND owner_uid = " . $_SESSION["uid"]); + $of_sth = $pdo->prepare("SELECT * FROM ttrss_archived_feeds + WHERE id = ? AND owner_uid = ?"); + $of_sth->execute([$line["orig_feed_id"], $owner_uid]); - if (db_num_rows($tmp_result) != 0) { + if ($tmp_line = $of_sth->fetch()) { $rv['content'] .= "<div clear='both'>"; $rv['content'] .= __("Originally from:"); $rv['content'] .= " "; - $tmp_line = db_fetch_assoc($tmp_result); - $rv['content'] .= "<a target='_blank' rel='noopener noreferrer' href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" . $tmp_line['title'] . "</a>"; @@ -724,9 +755,9 @@ class Article extends Handler_Protected { if (!$zoom_mode) { $rv['content'] .= Article::format_article_enclosures($id, - sql_bool_to_bool($line["always_display_enclosures"]), + $line["always_display_enclosures"], $line["content"], - sql_bool_to_bool($line["hide_images"])); + $line["hide_images"]); } $rv['content'] .= "</div>"; @@ -753,25 +784,27 @@ class Article extends Handler_Protected { static function get_article_tags($id, $owner_uid = 0, $tag_cache = false) { - $a_id = db_escape_string($id); + $a_id = $id; if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - $query = "SELECT DISTINCT tag_name, - owner_uid as owner FROM - ttrss_tags WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE - ref_id = '$a_id' AND owner_uid = '$owner_uid' LIMIT 1) ORDER BY tag_name"; + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT DISTINCT tag_name, + 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"); $tags = array(); /* check cache first */ if ($tag_cache === false) { - $result = db_query("SELECT tag_cache FROM ttrss_user_entries - WHERE ref_id = '$id' AND owner_uid = $owner_uid"); + $csth = $pdo->prepare("SELECT tag_cache FROM ttrss_user_entries + WHERE ref_id = ? AND owner_uid = ?"); + $csth->execute([$id, $owner_uid]); - if (db_num_rows($result) != 0) - $tag_cache = db_fetch_result($result, 0, "tag_cache"); + if ($row = $csth->fetch()) $tag_cache = $row["tag_cache"]; } if ($tag_cache) { @@ -780,19 +813,20 @@ class Article extends Handler_Protected { /* do it the hard way */ - $tmp_result = db_query($query); + $sth->execute([$a_id, $owner_uid]); - while ($tmp_line = db_fetch_assoc($tmp_result)) { + while ($tmp_line = $sth->fetch()) { array_push($tags, $tmp_line["tag_name"]); } /* update the cache */ - $tags_str = db_escape_string(join(",", $tags)); + $tags_str = join(",", $tags); - db_query("UPDATE ttrss_user_entries - SET tag_cache = '$tags_str' WHERE ref_id = '$id' - AND owner_uid = $owner_uid"); + $sth = $pdo->prepare("UPDATE ttrss_user_entries + SET tag_cache = ? WHERE ref_id = ? + AND owner_uid = ?"); + $sth->execute([$tags_str, $id, $owner_uid]); } return $tags; @@ -845,22 +879,21 @@ class Article extends Handler_Protected { static function get_article_enclosures($id) { - $query = "SELECT * FROM ttrss_enclosures - WHERE post_id = '$id' AND content_url != ''"; - - $rv = array(); + $pdo = Db::pdo(); - $result = db_query($query); + $sth = $pdo->prepare("SELECT * FROM ttrss_enclosures + WHERE post_id = ? AND content_url != ''"); + $sth->execute([$id]); - if (db_num_rows($result) > 0) { - while ($line = db_fetch_assoc($result)) { + $rv = array(); - if (file_exists(CACHE_DIR . '/images/' . sha1($line["content_url"]))) { - $line["content_url"] = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($line["content_url"]); - } + while ($line = $sth->fetch()) { - array_push($rv, $line); + if (file_exists(CACHE_DIR . '/images/' . sha1($line["content_url"]))) { + $line["content_url"] = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($line["content_url"]); } + + array_push($rv, $line); } return $rv; @@ -869,11 +902,18 @@ class Article extends Handler_Protected { static function purge_orphans($do_output = false) { // purge orphaned posts in main content table - $result = db_query("DELETE FROM ttrss_entries WHERE - NOT EXISTS (SELECT ref_id FROM ttrss_user_entries WHERE ref_id = id)"); + + if (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"); if ($do_output) { - $rows = db_affected_rows($result); + $rows = $res->rowCount(); _debug("Purged $rows orphaned posts."); } } @@ -881,46 +921,47 @@ class Article extends Handler_Protected { static function catchupArticlesById($ids, $cmode, $owner_uid = false) { if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - if (count($ids) == 0) return; - $tmp_ids = array(); + $pdo = Db::pdo(); - foreach ($ids as $id) { - array_push($tmp_ids, "ref_id = '$id'"); - } - - $ids_qpart = join(" OR ", $tmp_ids); + $ids_qmarks = arr_qmarks($ids); if ($cmode == 0) { - db_query("UPDATE ttrss_user_entries SET + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false,last_read = NOW() - WHERE ($ids_qpart) AND owner_uid = $owner_uid"); + WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); } else if ($cmode == 1) { - db_query("UPDATE ttrss_user_entries SET + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = true - WHERE ($ids_qpart) AND owner_uid = $owner_uid"); + WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); } else { - db_query("UPDATE ttrss_user_entries SET - unread = NOT unread,last_read = NOW() - WHERE ($ids_qpart) AND owner_uid = $owner_uid"); + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET + unread = NOT unread,last_read = NOW() + WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); } + $sth->execute(array_merge($ids, [$owner_uid])); + /* update ccache */ - $result = db_query("SELECT DISTINCT feed_id FROM ttrss_user_entries - WHERE ($ids_qpart) AND owner_uid = $owner_uid"); + $sth = $pdo->prepare("SELECT DISTINCT feed_id FROM ttrss_user_entries + WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); + $sth->execute(array_merge($ids, [$owner_uid])); - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { CCache::update($line["feed_id"], $owner_uid); } } static function getLastArticleId() { - $result = db_query("SELECT ref_id AS id FROM ttrss_user_entries - WHERE owner_uid = " . $_SESSION["uid"] . " ORDER BY ref_id DESC LIMIT 1"); + $pdo = DB::pdo(); + + $sth = $pdo->prepare("SELECT ref_id AS id FROM ttrss_user_entries + WHERE owner_uid = ? ORDER BY ref_id DESC LIMIT 1"); + $sth->execute([$_SESSION['uid']]); - if (db_num_rows($result) == 1) { - return db_fetch_result($result, 0, "id"); + if ($row = $sth->fetch()) { + return $row['id']; } else { return -1; } @@ -931,32 +972,34 @@ class Article extends Handler_Protected { if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - $result = db_query("SELECT label_cache FROM - ttrss_user_entries WHERE ref_id = '$id' AND owner_uid = " . - $owner_uid); + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT label_cache FROM + ttrss_user_entries WHERE ref_id = ? AND owner_uid = ?"); + $sth->execute([$id, $owner_uid]); - if (db_num_rows($result) > 0) { - $label_cache = db_fetch_result($result, 0, "label_cache"); + if ($row = $sth->fetch()) { + $label_cache = $row["label_cache"]; if ($label_cache) { - $label_cache = json_decode($label_cache, true); + $tmp = json_decode($label_cache, true); - if ($label_cache["no-labels"] == 1) + if (!$tmp || $tmp["no-labels"] == 1) return $rv; else - return $label_cache; + return $tmp; } } - $result = db_query( - "SELECT DISTINCT label_id,caption,fg_color,bg_color + $sth = $pdo->prepare("SELECT DISTINCT label_id,caption,fg_color,bg_color FROM ttrss_labels2, ttrss_user_labels2 WHERE id = label_id - AND article_id = '$id' - AND owner_uid = ". $owner_uid . " + AND article_id = ? + AND owner_uid = ? ORDER BY caption"); + $sth->execute([$id, $owner_uid]); - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { $rk = array(Labels::label_to_feed_id($line["label_id"]), $line["caption"], $line["fg_color"], $line["bg_color"]); diff --git a/classes/auth/base.php b/classes/auth/base.php index 304431213..dbc77f8cd 100644 --- a/classes/auth/base.php +++ b/classes/auth/base.php @@ -1,9 +1,9 @@ <?php class Auth_Base { - private $dbh; + private $pdo; function __construct() { - $this->dbh = Db::get(); + $this->pdo = Db::pdo(); } /** @@ -29,15 +29,13 @@ class Auth_Base { if (!$password) $password = make_password(); if (!$user_id) { - $login = $this->dbh->escape_string($login); $salt = substr(bin2hex(get_random_bytes(125)), 0, 250); $pwd_hash = encrypt_password($password, $salt, true); - $query = "INSERT INTO ttrss_users + $sth = $this->pdo->prepare("INSERT INTO ttrss_users (login,access_level,last_login,created,pwd_hash,salt) - VALUES ('$login', 0, null, NOW(), '$pwd_hash','$salt')"; - - $this->dbh->query($query); + VALUES (?, 0, null, NOW(), ?,?)"); + $sth->execute([$login, $pwd_hash, $salt]); return $this->find_user_by_login($login); @@ -50,13 +48,12 @@ class Auth_Base { } function find_user_by_login($login) { - $login = $this->dbh->escape_string($login); - - $result = $this->dbh->query("SELECT id FROM ttrss_users WHERE - login = '$login'"); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE + login = ?"); + $sth->execute([$login]); - if ($this->dbh->num_rows($result) > 0) { - return $this->dbh->fetch_result($result, 0, "id"); + if ($row = $sth->fetch()) { + return $row["id"]; } else { return false; } diff --git a/classes/backend.php b/classes/backend.php index c9a595b86..d5d0f5a01 100644 --- a/classes/backend.php +++ b/classes/backend.php @@ -84,7 +84,7 @@ class Backend extends Handler { } function help() { - $topic = basename($_REQUEST["topic"]); + $topic = basename(clean($_REQUEST["topic"])); switch ($topic) { case "main": diff --git a/classes/ccache.php b/classes/ccache.php index 4efb96d97..87c14e78e 100644 --- a/classes/ccache.php +++ b/classes/ccache.php @@ -1,40 +1,46 @@ <?php class CCache { - /* function ccache_zero($feed_id, $owner_uid) { - db_query("UPDATE ttrss_counters_cache SET - value = 0, updated = NOW() WHERE - feed_id = '$feed_id' AND owner_uid = '$owner_uid'"); - } */ - static function zero_all($owner_uid) { - db_query("UPDATE ttrss_counters_cache SET - value = 0 WHERE owner_uid = '$owner_uid'"); + $pdo = Db::pdo(); + + $sth = $pdo->prepare("UPDATE ttrss_counters_cache SET + value = 0 WHERE owner_uid = ?"); + $sth->execute([$owner_uid]); - db_query("UPDATE ttrss_cat_counters_cache SET - value = 0 WHERE owner_uid = '$owner_uid'"); + $sth = $pdo->prepare("UPDATE ttrss_cat_counters_cache SET + value = 0 WHERE owner_uid = ?"); + $sth->execute([$owner_uid]); } static function remove($feed_id, $owner_uid, $is_cat = false) { + $feed_id = (int) $feed_id; + if (!$is_cat) { $table = "ttrss_counters_cache"; } else { $table = "ttrss_cat_counters_cache"; } - db_query("DELETE FROM $table WHERE - feed_id = '$feed_id' AND owner_uid = '$owner_uid'"); + $pdo = Db::pdo(); + + $sth = $pdo->prepare("DELETE FROM $table WHERE + feed_id = ? AND owner_uid = ?"); + $sth->execute([$feed_id, $owner_uid]); } static function update_all($owner_uid) { + $pdo = Db::pdo(); + if (get_pref('ENABLE_FEED_CATS', $owner_uid)) { - $result = db_query("SELECT feed_id FROM ttrss_cat_counters_cache - WHERE feed_id > 0 AND owner_uid = '$owner_uid'"); + $sth = $pdo->prepare("SELECT feed_id FROM ttrss_cat_counters_cache + WHERE feed_id > 0 AND owner_uid = ?"); + $sth->execute([$owner_uid]); - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { CCache::update($line["feed_id"], $owner_uid, true); } @@ -43,10 +49,11 @@ class CCache { CCache::update(0, $owner_uid, true); } else { - $result = db_query("SELECT feed_id FROM ttrss_counters_cache - WHERE feed_id > 0 AND owner_uid = '$owner_uid'"); + $sth = $pdo->prepare("SELECT feed_id FROM ttrss_counters_cache + WHERE feed_id > 0 AND owner_uid = ?"); + $sth->execute([$owner_uid]); - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { print CCache::update($line["feed_id"], $owner_uid); } @@ -57,31 +64,29 @@ class CCache { static function find($feed_id, $owner_uid, $is_cat = false, $no_update = false) { - if (!is_numeric($feed_id)) return; + // "" (null) is valid and should be cast to 0 (uncategorized) + // everything else i.e. tags are not + if (!is_numeric($feed_id) && $feed_id) + return; + + $feed_id = (int) $feed_id; if (!$is_cat) { $table = "ttrss_counters_cache"; - /* if ($feed_id > 0) { - $tmp_result = db_query("SELECT owner_uid FROM ttrss_feeds - WHERE id = '$feed_id'"); - $owner_uid = db_fetch_result($tmp_result, 0, "owner_uid"); - } */ } else { $table = "ttrss_cat_counters_cache"; } - if (DB_TYPE == "pgsql") { - $date_qpart = "updated > NOW() - INTERVAL '15 minutes'"; - } else if (DB_TYPE == "mysql") { - $date_qpart = "updated > DATE_SUB(NOW(), INTERVAL 15 MINUTE)"; - } + $pdo = Db::pdo(); - $result = db_query("SELECT value FROM $table - WHERE owner_uid = '$owner_uid' AND feed_id = '$feed_id' + $sth = $pdo->prepare("SELECT value FROM $table + WHERE owner_uid = ? AND feed_id = ? LIMIT 1"); - if (db_num_rows($result) == 1) { - return db_fetch_result($result, 0, "value"); + $sth->execute([$owner_uid, $feed_id]); + + if ($row = $sth->fetch()) { + return $row["value"]; } else { if ($no_update) { return -1; @@ -95,13 +100,12 @@ class CCache { static function update($feed_id, $owner_uid, $is_cat = false, $update_pcat = true, $pcat_fast = false) { - if (!is_numeric($feed_id)) return; + // "" (null) is valid and should be cast to 0 (uncategorized) + // everything else i.e. tags are not + if (!is_numeric($feed_id) && $feed_id) + return; - /* if (!$is_cat && $feed_id > 0) { - $tmp_result = db_query("SELECT owner_uid FROM ttrss_feeds - WHERE id = '$feed_id'"); - $owner_uid = db_fetch_result($tmp_result, 0, "owner_uid"); - } */ + $feed_id = (int) $feed_id; $prev_unread = CCache::find($feed_id, $owner_uid, $is_cat, true); @@ -119,54 +123,66 @@ class CCache { $table = "ttrss_cat_counters_cache"; } - if ($is_cat && $feed_id >= 0) { - if ($feed_id != 0) { - $cat_qpart = "cat_id = '$feed_id'"; - } else { - $cat_qpart = "cat_id IS NULL"; - } + $pdo = Db::pdo(); + if ($is_cat && $feed_id >= 0) { /* Recalculate counters for child feeds */ if (!$pcat_fast) { - $result = db_query("SELECT id FROM ttrss_feeds - WHERE owner_uid = '$owner_uid' AND $cat_qpart"); + $sth = $pdo->prepare("SELECT id FROM ttrss_feeds + WHERE owner_uid = :uid AND + (cat_id = :cat OR (:cat = 0 AND cat_id IS NULL))"); + $sth->execute([":uid" => $owner_uid, ":cat" => $feed_id]); - while ($line = db_fetch_assoc($result)) { - CCache::update($line["id"], $owner_uid, false, false); + while ($line = $sth->fetch()) { + CCache::update((int)$line["id"], $owner_uid, false, false); } } - $result = db_query("SELECT SUM(value) AS sv + $sth = $pdo->prepare("SELECT SUM(value) AS sv FROM ttrss_counters_cache, ttrss_feeds - WHERE id = feed_id AND $cat_qpart AND - ttrss_counters_cache.owner_uid = $owner_uid AND - ttrss_feeds.owner_uid = '$owner_uid'"); + 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"); + $sth->execute([":uid" => $owner_uid, ":cat" => $feed_id]); + $row = $sth->fetch(); - $unread = (int) db_fetch_result($result, 0, "sv"); + $unread = (int) $row["sv"]; } else { $unread = (int) Feeds::getFeedArticles($feed_id, $is_cat, true, $owner_uid); } - db_query("BEGIN"); + $tr_in_progress = false; - $result = db_query("SELECT feed_id FROM $table - WHERE owner_uid = '$owner_uid' AND feed_id = '$feed_id' LIMIT 1"); + try { + $pdo->beginTransaction(); + } catch (Exception $e) { + $tr_in_progress = true; + } + + $sth = $pdo->prepare("SELECT feed_id FROM $table + WHERE owner_uid = ? AND feed_id = ? LIMIT 1"); + $sth->execute([$owner_uid, $feed_id]); + + if ($sth->fetch()) { + + $sth = $pdo->prepare("UPDATE $table SET + value = ?, updated = NOW() WHERE + feed_id = ? AND owner_uid = ?"); - if (db_num_rows($result) == 1) { - db_query("UPDATE $table SET - value = '$unread', updated = NOW() WHERE - feed_id = '$feed_id' AND owner_uid = '$owner_uid'"); + $sth->execute([$unread, $feed_id, $owner_uid]); } else { - db_query("INSERT INTO $table + $sth = $pdo->prepare("INSERT INTO $table (feed_id, value, owner_uid, updated) VALUES - ($feed_id, $unread, $owner_uid, NOW())"); + (?, ?, ?, NOW())"); + $sth->execute([$feed_id, $unread, $owner_uid]); } - db_query("COMMIT"); + if (!$tr_in_progress) $pdo->commit(); if ($feed_id > 0 && $prev_unread != $unread) { @@ -176,13 +192,13 @@ class CCache { if ($update_pcat) { - $result = db_query("SELECT cat_id FROM ttrss_feeds - WHERE owner_uid = '$owner_uid' AND id = '$feed_id'"); - - $cat_id = (int) db_fetch_result($result, 0, "cat_id"); - - CCache::update($cat_id, $owner_uid, true, true, true); + $sth = $pdo->prepare("SELECT cat_id FROM ttrss_feeds + WHERE owner_uid = ? AND id = ?"); + $sth->execute([$owner_uid, $feed_id]); + if ($row = $sth->fetch()) { + CCache::update((int)$row["cat_id"], $owner_uid, true, true, true); + } } } } else if ($feed_id < 0) { @@ -192,37 +208,4 @@ class CCache { return $unread; } - /* function ccache_cleanup($owner_uid) { - - if (DB_TYPE == "pgsql") { - db_query("DELETE FROM ttrss_counters_cache AS c1 WHERE - (SELECT count(*) FROM ttrss_counters_cache AS c2 - WHERE c1.feed_id = c2.feed_id AND c2.owner_uid = c1.owner_uid) > 1 - AND owner_uid = '$owner_uid'"); - - db_query("DELETE FROM ttrss_cat_counters_cache AS c1 WHERE - (SELECT count(*) FROM ttrss_cat_counters_cache AS c2 - WHERE c1.feed_id = c2.feed_id AND c2.owner_uid = c1.owner_uid) > 1 - AND owner_uid = '$owner_uid'"); - } else { - db_query("DELETE c1 FROM - ttrss_counters_cache AS c1, - ttrss_counters_cache AS c2 - WHERE - c1.owner_uid = '$owner_uid' AND - c1.owner_uid = c2.owner_uid AND - c1.feed_id = c2.feed_id"); - - db_query("DELETE c1 FROM - ttrss_cat_counters_cache AS c1, - ttrss_cat_counters_cache AS c2 - WHERE - c1.owner_uid = '$owner_uid' AND - c1.owner_uid = c2.owner_uid AND - c1.feed_id = c2.feed_id"); - - } - } */ - - }
\ No newline at end of file diff --git a/classes/counters.php b/classes/counters.php index c608acf72..2de7d5a3a 100644 --- a/classes/counters.php +++ b/classes/counters.php @@ -22,15 +22,18 @@ class Counters { array_push($ret_arr, $cv); - $result = db_query("SELECT id AS cat_id, value AS unread, + $pdo = DB::pdo(); + + $sth = $pdo->prepare("SELECT id AS cat_id, value AS unread, (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE c2.parent_cat = ttrss_feed_categories.id) AS num_children FROM ttrss_feed_categories, ttrss_cat_counters_cache WHERE ttrss_cat_counters_cache.feed_id = id AND ttrss_cat_counters_cache.owner_uid = ttrss_feed_categories.owner_uid AND - ttrss_feed_categories.owner_uid = " . $_SESSION["uid"]); + ttrss_feed_categories.owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { $line["cat_id"] = (int) $line["cat_id"]; if ($line["num_children"] > 0) { @@ -67,10 +70,14 @@ class Counters { array_push($ret_arr, $cv); - $result = db_query("SELECT COUNT(id) AS fn FROM - ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]); + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT COUNT(id) AS fn FROM + ttrss_feeds WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + $row = $sth->fetch(); - $subscribed_feeds = db_fetch_result($result, 0, "fn"); + $subscribed_feeds = $row["fn"]; $cv = array("id" => "subscribed-feeds", "counter" => (int) $subscribed_feeds); @@ -124,17 +131,18 @@ class Counters { $ret_arr = array(); - $owner_uid = $_SESSION["uid"]; + $pdo = Db::pdo(); - $result = db_query("SELECT id,caption,SUM(CASE WHEN u1.unread = true THEN 1 ELSE 0 END) AS unread, COUNT(u1.unread) AS total + $sth = $pdo->prepare("SELECT id,caption,SUM(CASE WHEN u1.unread = true THEN 1 ELSE 0 END) AS unread, COUNT(u1.unread) AS total FROM ttrss_labels2 LEFT JOIN ttrss_user_labels2 ON (ttrss_labels2.id = label_id) LEFT JOIN ttrss_user_entries AS u1 ON u1.ref_id = article_id - WHERE ttrss_labels2.owner_uid = $owner_uid AND u1.owner_uid = $owner_uid + WHERE ttrss_labels2.owner_uid = :uid AND u1.owner_uid = :uid GROUP BY ttrss_labels2.id, ttrss_labels2.caption"); + $sth->execute([":uid" => $_SESSION['uid']]); - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { $id = Labels::label_to_feed_id($line["id"]); @@ -155,18 +163,19 @@ class Counters { $ret_arr = array(); - $query = "SELECT ttrss_feeds.id, + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT ttrss_feeds.id, ttrss_feeds.title, ".SUBSTRING_FOR_DATE."(ttrss_feeds.last_updated,1,19) AS last_updated, last_error, value AS count FROM ttrss_feeds, ttrss_counters_cache - WHERE ttrss_feeds.owner_uid = ".$_SESSION["uid"]." + WHERE ttrss_feeds.owner_uid = ? AND ttrss_counters_cache.owner_uid = ttrss_feeds.owner_uid - AND ttrss_counters_cache.feed_id = id"; + AND ttrss_counters_cache.feed_id = id"); + $sth->execute([$_SESSION['uid']]); - $result = db_query($query); - - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { $id = $line["id"]; $count = $line["count"]; @@ -174,7 +183,11 @@ class Counters { $last_updated = make_local_datetime($line['last_updated'], false); - $has_img = feed_has_icon($id); + if (Feeds::feedHasIcon($id)) { + $has_img = filemtime(Feeds::getIconFile($id)); + } else { + $has_img = false; + } if (date('Y') - date('Y', strtotime($line['last_updated'])) > 2) $last_updated = ''; diff --git a/classes/db.php b/classes/db.php index 3b71f3c8f..96a888275 100644 --- a/classes/db.php +++ b/classes/db.php @@ -1,17 +1,29 @@ <?php -class Db implements IDb { +class Db +{ + + /* @var Db $instance */ private static $instance; + + /* @var IDb $adapter */ private $adapter; + private $link; - private function __construct() { + /* @var PDO $pdo */ + private $pdo; + + private function __clone() { + // + } + + private function legacy_connect() { + + user_error("Legacy connect requested to " . DB_TYPE, E_USER_NOTICE); $er = error_reporting(E_ALL); - if (defined('_ENABLE_PDO') && _ENABLE_PDO && class_exists("PDO")) { - $this->adapter = new Db_PDO(); - } else { - switch (DB_TYPE) { + switch (DB_TYPE) { case "mysql": $this->adapter = new Db_Mysqli(); break; @@ -20,7 +32,6 @@ class Db implements IDb { break; default: die("Unknown DB_TYPE: " . DB_TYPE); - } } if (!$this->adapter) { @@ -38,63 +49,57 @@ class Db implements IDb { error_reporting($er); } - private function __clone() { - // - } - - public static function get() { - if (self::$instance == null) - self::$instance = new self(); + private function pdo_connect() { - return self::$instance; - } + $db_port = defined('DB_PORT') && DB_PORT ? ';port=' . DB_PORT : ''; + $db_host = defined('DB_HOST') && DB_HOST ? ';host=' . DB_HOST : ''; - static function quote($str){ - return("'$str'"); - } + try { + $this->pdo = new PDO(DB_TYPE . ':dbname=' . DB_NAME . $db_host . $db_port, + DB_USER, + DB_PASS); + } catch (Exception $e) { + print "<pre>Exception while creating PDO object:" . $e->getMessage() . "</pre>"; + exit(101); + } - function reconnect() { - $this->link = $this->adapter->connect(DB_HOST, DB_USER, DB_PASS, DB_NAME, defined('DB_PORT') ? DB_PORT : ""); - } + $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); - function connect($host, $user, $pass, $db, $port) { - //return $this->adapter->connect($host, $user, $pass, $db, $port); - return ; - } + if (DB_TYPE == "pgsql") { - function escape_string($s, $strip_tags = true) { - return $this->adapter->escape_string($s, $strip_tags); - } + $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"); - function query($query, $die_on_error = true) { - return $this->adapter->query($query, $die_on_error); - } + } else if (DB_TYPE == "mysql") { + $this->pdo->query("SET time_zone = '+0:0'"); - function fetch_assoc($result) { - return $this->adapter->fetch_assoc($result); + if (defined('MYSQL_CHARSET') && MYSQL_CHARSET) { + $this->pdo->query("SET NAMES " . MYSQL_CHARSET); + } + } } - function num_rows($result) { - return $this->adapter->num_rows($result); - } + public static function get() { + if (self::$instance == null) + self::$instance = new self(); - function fetch_result($result, $row, $param) { - return $this->adapter->fetch_result($result, $row, $param); - } + if (!self::$instance->adapter) { + self::$instance->legacy_connect(); + } - function close() { - return $this->adapter->close(); + return self::$instance->adapter; } - function affected_rows($result) { - return $this->adapter->affected_rows($result); - } + public static function pdo() { + if (self::$instance == null) + self::$instance = new self(); - function last_error() { - return $this->adapter->last_error(); - } + if (!self::$instance->pdo) { + self::$instance->pdo_connect(); + } - function last_query_error() { - return $this->adapter->last_query_error(); + return self::$instance->pdo; } }
\ No newline at end of file diff --git a/classes/db/mysqli.php b/classes/db/mysqli.php index 817b48c0c..a05b121fc 100644 --- a/classes/db/mysqli.php +++ b/classes/db/mysqli.php @@ -65,7 +65,7 @@ class Db_Mysqli implements IDb { } function last_error() { - return mysqli_error(); + return mysqli_error($this->link); } function last_query_error() { diff --git a/classes/db/pdo.php b/classes/db/pdo.php deleted file mode 100644 index d3070fac4..000000000 --- a/classes/db/pdo.php +++ /dev/null @@ -1,100 +0,0 @@ -<?php -class Db_PDO implements IDb { - private $pdo; - - function connect($host, $user, $pass, $db, $port) { - $connstr = DB_TYPE . ":host=$host;dbname=$db"; - - if (DB_TYPE == "mysql") $connstr .= ";charset=utf8"; - - try { - $this->pdo = new PDO($connstr, $user, $pass); - $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - $this->init(); - } catch (PDOException $e) { - die($e->getMessage()); - } - - return $this->pdo; - } - - function escape_string($s, $strip_tags = true) { - if ($strip_tags) $s = strip_tags($s); - - $qs = $this->pdo->quote($s); - - return mb_substr($qs, 1, mb_strlen($qs)-2); - } - - function query($query, $die_on_error = true) { - try { - return new Db_Stmt($this->pdo->query($query)); - } catch (PDOException $e) { - user_error($e->getMessage(), $die_on_error ? E_USER_ERROR : E_USER_WARNING); - } - } - - function fetch_assoc($result) { - try { - if ($result) { - return $result->fetch(); - } else { - return null; - } - } catch (PDOException $e) { - user_error($e->getMessage(), E_USER_WARNING); - } - } - - function num_rows($result) { - try { - if ($result) { - return $result->rowCount(); - } else { - return false; - } - } catch (PDOException $e) { - user_error($e->getMessage(), E_USER_WARNING); - } - } - - function fetch_result($result, $row, $param) { - return $result->fetch_result($row, $param); - } - - function close() { - $this->pdo = null; - } - - function affected_rows($result) { - try { - if ($result) { - return $result->rowCount(); - } else { - return null; - } - } catch (PDOException $e) { - user_error($e->getMessage(), E_USER_WARNING); - } - } - - function last_error() { - return join(" ", $this->pdo->errorInfo()); - } - - function init() { - switch (DB_TYPE) { - case "pgsql": - $this->query("set client_encoding = 'UTF-8'"); - $this->query("set datestyle = 'ISO, european'"); - $this->query("set TIME ZONE 0"); - return; - case "mysql": - $this->query("SET time_zone = '+0:0'"); - return; - } - - return true; - } - -}
\ No newline at end of file diff --git a/classes/db/prefs.php b/classes/db/prefs.php index d61cc107b..64238bc78 100644 --- a/classes/db/prefs.php +++ b/classes/db/prefs.php @@ -1,11 +1,11 @@ <?php class Db_Prefs { - private $dbh; + private $pdo; private static $instance; private $cache; function __construct() { - $this->dbh = Db::get(); + $this->pdo = Db::pdo(); $this->cache = array(); if ($_SESSION["uid"]) $this->cache(); @@ -26,26 +26,22 @@ class Db_Prefs { $user_id = $_SESSION["uid"]; @$profile = $_SESSION["profile"]; - if ($profile) { - $profile_qpart = "profile = '$profile' AND"; - } else { - $profile_qpart = "profile IS NULL AND"; - } - - if (get_schema_version() < 63) $profile_qpart = ""; + if (!$profile || get_schema_version() < 63) $profile = null; - $result = db_query("SELECT + $sth = $this->pdo->prepare("SELECT value,ttrss_prefs_types.type_name as type_name,ttrss_prefs.pref_name AS pref_name FROM ttrss_user_prefs,ttrss_prefs,ttrss_prefs_types WHERE - $profile_qpart + (profile = :profile OR (:profile IS NULL AND profile IS NULL)) AND ttrss_prefs.pref_name NOT LIKE '_MOBILE%' AND ttrss_prefs_types.id = type_id AND - owner_uid = '$user_id' AND + owner_uid = :uid AND ttrss_user_prefs.pref_name = ttrss_prefs.pref_name"); - while ($line = db_fetch_assoc($result)) { + $sth->execute([":profile" => $profile, ":uid" => $user_id]); + + while ($line = $sth->fetch()) { if ($user_id == $_SESSION["uid"]) { $pref_name = $line["pref_name"]; @@ -57,7 +53,6 @@ class Db_Prefs { function read($pref_name, $user_id = false, $die_on_error = false) { - $pref_name = db_escape_string($pref_name); $profile = false; if (!$user_id) { @@ -72,28 +67,23 @@ class Db_Prefs { return $this->convert($tuple["value"], $tuple["type"]); } - if ($profile) { - $profile_qpart = "profile = '$profile' AND"; - } else { - $profile_qpart = "profile IS NULL AND"; - } - - if (get_schema_version() < 63) $profile_qpart = ""; + if (!$profile || get_schema_version() < 63) $profile = null; - $result = db_query("SELECT + $sth = $this->pdo->prepare("SELECT value,ttrss_prefs_types.type_name as type_name FROM ttrss_user_prefs,ttrss_prefs,ttrss_prefs_types WHERE - $profile_qpart - ttrss_user_prefs.pref_name = '$pref_name' AND + (profile = :profile OR (:profile IS NULL AND profile IS NULL)) AND + ttrss_user_prefs.pref_name = :pref_name AND ttrss_prefs_types.id = type_id AND - owner_uid = '$user_id' AND + owner_uid = :uid AND ttrss_user_prefs.pref_name = ttrss_prefs.pref_name"); + $sth->execute([":uid" => $user_id, ":profile" => $profile, ":pref_name" => $pref_name]); - if (db_num_rows($result) > 0) { - $value = db_fetch_result($result, 0, "value"); - $type_name = db_fetch_result($result, 0, "type_name"); + if ($row = $sth->fetch()) { + $value = $row["value"]; + $type_name = $row["type_name"]; if ($user_id == $_SESSION["uid"]) { $this->cache[$pref_name]["type"] = $type_name; @@ -119,8 +109,7 @@ class Db_Prefs { } function write($pref_name, $value, $user_id = false, $strip_tags = true) { - $pref_name = db_escape_string($pref_name); - $value = db_escape_string($value, $strip_tags); + if ($strip_tags) $value = strip_tags($value); if (!$user_id) { $user_id = $_SESSION["uid"]; @@ -129,13 +118,7 @@ class Db_Prefs { $user_id = sprintf("%d", $user_id); } - if ($profile) { - $profile_qpart = "AND profile = '$profile'"; - } else { - $profile_qpart = "AND profile IS NULL"; - } - - if (get_schema_version() < 63) $profile_qpart = ""; + if (!$profile || get_schema_version() < 63) $profile = null; $type_name = ""; $current_value = ""; @@ -146,12 +129,14 @@ class Db_Prefs { } if (!$type_name) { - $result = db_query("SELECT type_name + $sth = $this->pdo->prepare("SELECT type_name FROM ttrss_prefs,ttrss_prefs_types - WHERE pref_name = '$pref_name' AND type_id = ttrss_prefs_types.id"); + WHERE pref_name = ? AND type_id = ttrss_prefs_types.id"); + $sth->execute([$pref_name]); + + if ($row = $sth->fetch()) + $type_name = $row["type_name"]; - if (db_num_rows($result) > 0) - $type_name = db_fetch_result($result, 0, "type_name"); } else if ($current_value == $value) { return; } @@ -171,10 +156,12 @@ class Db_Prefs { $value = 'UTC'; } - db_query("UPDATE ttrss_user_prefs SET - value = '$value' WHERE pref_name = '$pref_name' - $profile_qpart - AND owner_uid = " . $user_id); + $sth = $this->pdo->prepare("UPDATE ttrss_user_prefs SET + value = :value WHERE pref_name = :pref_name + AND (profile = :profile OR (:profile IS NULL AND profile IS NULL)) + AND owner_uid = :uid"); + + $sth->execute([":pref_name" => $pref_name, ":value" => $value, ":uid" => $user_id, ":profile" => $profile]); if ($user_id == $_SESSION["uid"]) { $this->cache[$pref_name]["type"] = $type_name; diff --git a/classes/db/stmt.php b/classes/db/stmt.php deleted file mode 100644 index 7d6bbb30a..000000000 --- a/classes/db/stmt.php +++ /dev/null @@ -1,31 +0,0 @@ -<?php -class Db_Stmt { - private $stmt; - private $cache; - - function __construct($stmt) { - $this->stmt = $stmt; - $this->cache = false; - } - - function fetch_result($row, $param) { - if (!$this->cache) { - $this->cache = $this->stmt->fetchAll(); - } - - if (isset($this->cache[$row])) { - return $this->cache[$row][$param]; - } else { - user_error("Unable to jump to row $row", E_USER_WARNING); - return false; - } - } - - function rowCount() { - return $this->stmt->rowCount(); - } - - function fetch() { - return $this->stmt->fetch(); - } -}
\ No newline at end of file diff --git a/classes/dbupdater.php b/classes/dbupdater.php index 2d131fde7..1014fa5a5 100644 --- a/classes/dbupdater.php +++ b/classes/dbupdater.php @@ -1,19 +1,19 @@ <?php class DbUpdater { - private $dbh; + private $pdo; private $db_type; private $need_version; - function __construct($dbh, $db_type, $need_version) { - $this->dbh = $dbh; + function __construct($pdo, $db_type, $need_version) { + $this->pdo = Db::pdo(); //$pdo; $this->db_type = $db_type; $this->need_version = (int) $need_version; } function getSchemaVersion() { - $result = db_query("SELECT schema_version FROM ttrss_version"); - return (int) db_fetch_result($result, 0, "schema_version"); + $row = $this->pdo->query("SELECT schema_version FROM ttrss_version")->fetch(); + return (int) $row['schema_version']; } function isUpdateRequired() { @@ -26,6 +26,7 @@ class DbUpdater { if (file_exists($filename)) { return explode(";", preg_replace("/[\r\n]/", "", file_get_contents($filename))); } else { + user_error("DB Updater: schema file for version $version is not found."); return false; } } @@ -37,17 +38,17 @@ class DbUpdater { if (is_array($lines)) { - db_query("BEGIN"); + $this->pdo->beginTransaction(); foreach ($lines as $line) { if (strpos($line, "--") !== 0 && $line) { - if (!db_query($line, false)) { + if (!$this->pdo->query($line)) { if ($html_output) { print_notice("Query: $line"); - print_error("Error: " . db_last_query_error()); + print_error("Error: " . implode(", ", $this->pdo->errorInfo())); } else { _debug("Query: $line"); - _debug("Error: " . db_last_query_error()); + _debug("Error: " . implode(", ", $this->pdo->errorInfo())); } return false; @@ -58,10 +59,10 @@ class DbUpdater { $db_version = $this->getSchemaVersion(); if ($db_version == $version) { - db_query("COMMIT"); + $this->pdo->commit(); return true; } else { - db_query("ROLLBACK"); + $this->pdo->rollBack(); return false; } } else { diff --git a/classes/digest.php b/classes/digest.php index 5a50eb071..83f39a865 100644 --- a/classes/digest.php +++ b/classes/digest.php @@ -19,15 +19,17 @@ class Digest if ($debug) _debug("Sending digests, batch of max $user_limit users, headline limit = $limit"); if (DB_TYPE == "pgsql") { - $interval_query = "last_digest_sent < NOW() - INTERVAL '1 days'"; + $interval_qpart = "last_digest_sent < NOW() - INTERVAL '1 days'"; } else if (DB_TYPE == "mysql") { - $interval_query = "last_digest_sent < DATE_SUB(NOW(), INTERVAL 1 DAY)"; + $interval_qpart = "last_digest_sent < DATE_SUB(NOW(), INTERVAL 1 DAY)"; } - $result = db_query("SELECT id,email FROM ttrss_users - WHERE email != '' AND (last_digest_sent IS NULL OR $interval_query)"); + $pdo = Db::pdo(); - while ($line = db_fetch_assoc($result)) { + $res = $pdo->query("SELECT id,email FROM ttrss_users + WHERE email != '' AND (last_digest_sent IS NULL OR $interval_qpart)"); + + while ($line = $res->fetch()) { if (@get_pref('DIGEST_ENABLE', $line['id'], false)) { $preferred_ts = strtotime(get_pref('DIGEST_PREFERRED_TIME', $line['id'], '00:00')); @@ -70,8 +72,9 @@ class Digest if ($debug) _debug("No headlines"); } - db_query("UPDATE ttrss_users SET last_digest_sent = NOW() - WHERE id = " . $line["id"]); + $sth = $pdo->prepare("UPDATE ttrss_users SET last_digest_sent = NOW() + WHERE id = ?"); + $sth->execute([$line["id"]]); } } @@ -102,13 +105,17 @@ class Digest $affected_ids = array(); + $days = (int) $days; + if (DB_TYPE == "pgsql") { - $interval_query = "ttrss_entries.date_updated > NOW() - INTERVAL '$days days'"; + $interval_qpart = "ttrss_entries.date_updated > NOW() - INTERVAL '$days days'"; } else if (DB_TYPE == "mysql") { - $interval_query = "ttrss_entries.date_updated > DATE_SUB(NOW(), INTERVAL $days DAY)"; + $interval_qpart = "ttrss_entries.date_updated > DATE_SUB(NOW(), INTERVAL $days DAY)"; } - $result = db_query("SELECT ttrss_entries.title, + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT ttrss_entries.title, ttrss_feeds.title AS feed_title, COALESCE(ttrss_feed_categories.title, '" . __('Uncategorized') . "') AS cat_title, date_updated, @@ -124,19 +131,20 @@ class Digest WHERE ref_id = ttrss_entries.id AND feed_id = ttrss_feeds.id AND include_in_digest = true - AND $interval_query - AND ttrss_user_entries.owner_uid = $user_id + AND $interval_qpart + AND ttrss_user_entries.owner_uid = ? AND unread = true AND score >= 0 ORDER BY ttrss_feed_categories.title, ttrss_feeds.title, score DESC, date_updated DESC - LIMIT $limit"); - - $headlines_count = db_num_rows($result); + LIMIT ?"); + $sth->execute([$user_id, $limit]); + $headlines_count = 0; $headlines = array(); - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { array_push($headlines, $line); + $headlines_count++; } for ($i = 0; $i < sizeof($headlines); $i++) { @@ -148,12 +156,6 @@ class Digest $updated = make_local_datetime($line['last_updated'], false, $user_id); - /* if ($line["score"] != 0) { - if ($line["score"] > 0) $line["score"] = '+' . $line["score"]; - - $line["title"] .= " (".$line['score'].")"; - } */ - if (get_pref('ENABLE_FEED_CATS', $user_id)) { $line['feed_title'] = $line['cat_title'] . " / " . $line['feed_title']; } diff --git a/classes/dlg.php b/classes/dlg.php index 53fa60280..9ac5cd12f 100644 --- a/classes/dlg.php +++ b/classes/dlg.php @@ -7,7 +7,7 @@ class Dlg extends Handler_Protected { if (parent::before($method)) { header("Content-Type: text/html"); # required for iframe - $this->param = $this->dbh->escape_string($_REQUEST["param"]); + $this->param = $_REQUEST["param"]; return true; } return false; @@ -18,16 +18,12 @@ class Dlg extends Handler_Protected { print "<div class=\"prefFeedOPMLHolder\">"; - $this->dbh->query("BEGIN"); - print "<ul class='nomarks'>"; $opml = new Opml($_REQUEST); $opml->opml_import($_SESSION["uid"]); - $this->dbh->query("COMMIT"); - print "</ul>"; print "</div>"; @@ -102,15 +98,14 @@ class Dlg extends Handler_Protected { // from here: http://www.roscripts.com/Create_tag_cloud-71.html - $query = "SELECT tag_name, COUNT(post_int_id) AS count - FROM ttrss_tags WHERE owner_uid = ".$_SESSION["uid"]." - GROUP BY tag_name ORDER BY count DESC LIMIT 50"; - - $result = $this->dbh->query($query); + $sth = $this->pdo->prepare("SELECT tag_name, COUNT(post_int_id) AS count + FROM ttrss_tags WHERE owner_uid = ? + GROUP BY tag_name ORDER BY count DESC LIMIT 50"); + $sth->execute([$_SESSION['uid']]); $tags = array(); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { $tags[$line["tag_name"]] = $line["count"]; } @@ -164,7 +159,7 @@ class Dlg extends Handler_Protected { function generatedFeed() { $this->params = explode(":", $this->param, 3); - $feed_id = $this->dbh->escape_string($this->params[0]); + $feed_id = $this->params[0]; $is_cat = (bool) $this->params[1]; $key = get_feed_access_key($feed_id, $is_cat); @@ -190,4 +185,16 @@ class Dlg extends Handler_Protected { //return; } + function defaultPasswordWarning() { + + print_warning(__("You are using default tt-rss password. Please change it in the Preferences (Personal data / Authentication).")); + + print "<div align='center'>"; + print "<button dojoType=\"dijit.form.Button\" onclick=\"gotoPreferences()\">". + __('Open Preferences')."</button> "; + print "<button dojoType=\"dijit.form.Button\" + onclick=\"return closeInfoBox()\">". + __('Close this window')."</button>"; + print "</div>"; + } }
\ No newline at end of file diff --git a/classes/feeds.php b/classes/feeds.php index 70271802a..e875eeb7f 100755 --- a/classes/feeds.php +++ b/classes/feeds.php @@ -39,7 +39,7 @@ class Feeds extends Handler_Protected { $search_q = ""; } - $reply .= "<span class=\"holder\">"; + $reply = "<span class=\"holder\">"; $rss_link = htmlspecialchars(get_self_url_prefix() . "/public.php?op=rss&id=$feed_id$cat_q$search_q"); @@ -64,7 +64,7 @@ class Feeds extends Handler_Protected { $target = "target=\"_blank\""; $reply .= "<a title=\"$last_updated\" $target href=\"$feed_site_url\">". - truncate_string($feed_title, 30)."</a>"; + truncate_string(strip_tags($feed_title), 30)."</a>"; if ($error) { $error = htmlspecialchars($error); @@ -72,7 +72,7 @@ class Feeds extends Handler_Protected { } } else { - $reply .= $feed_title; + $reply .= strip_tags($feed_title); } $reply .= "</span>"; @@ -193,24 +193,32 @@ class Feeds extends Handler_Protected { if (!$any_needs_curl) { - $result = $this->dbh->query( - "SELECT cache_images," . SUBSTRING_FOR_DATE . "(last_updated,1,19) AS last_updated - FROM ttrss_feeds WHERE id = '$feed'"); + $sth = $this->pdo->prepare("SELECT cache_images," . SUBSTRING_FOR_DATE . "(last_updated,1,19) AS last_updated + FROM ttrss_feeds WHERE id = ?"); + $sth->execute([$feed]); - if ($this->dbh->num_rows($result) != 0) { - $last_updated = strtotime($this->dbh->fetch_result($result, 0, "last_updated")); - $cache_images = sql_bool_to_bool($this->dbh->fetch_result($result, 0, "cache_images")); + if ($row = $sth->fetch()) { + $last_updated = strtotime($row["last_updated"]); + $cache_images = $row["cache_images"]; if (!$cache_images && time() - $last_updated > 120) { - RSSUtils::update_rss_feed($feed, true); + try { + RSSUtils::update_rss_feed($feed, true); + } catch (PDOException $e) { + user_error("PDO Exception while doing on-demand feed update for $feed: " . $e->getMessage(), E_USER_NOTICE); + } } else { - $this->dbh->query("UPDATE ttrss_feeds SET last_updated = '1970-01-01', last_update_started = '1970-01-01' - WHERE id = '$feed'"); + $sth = $this->pdo->prepare("UPDATE ttrss_feeds + SET last_updated = '1970-01-01', last_update_started = '1970-01-01' + WHERE id = ?"); + $sth->execute([$feed]); } } } else { - $this->dbh->query("UPDATE ttrss_feeds SET last_updated = '1970-01-01', last_update_started = '1970-01-01' - WHERE id = '$feed'"); + $sth = $this->pdo->prepare("UPDATE ttrss_feeds + SET last_updated = '1970-01-01', last_update_started = '1970-01-01' + WHERE id = ?"); + $sth->execute([$feed]); } } @@ -221,16 +229,16 @@ class Feeds extends Handler_Protected { // FIXME: might break tag display? if (is_numeric($feed) && $feed > 0 && !$cat_view) { - $result = $this->dbh->query( - "SELECT id FROM ttrss_feeds WHERE id = '$feed' LIMIT 1"); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE id = ? LIMIT 1"); + $sth->execute([$feed]); - if ($this->dbh->num_rows($result) == 0) { + if (!$sth->fetch()) { $reply['content'] = "<div align='center'>".__('Feed not found.')."</div>"; } } - @$search = $this->dbh->escape_string($_REQUEST["query"]); - @$search_language = $this->dbh->escape_string($_REQUEST["search_language"]); // PGSQL only + @$search = $_REQUEST["query"]; + @$search_language = $_REQUEST["search_language"]; // PGSQL only if ($search) { $disable_cache = true; @@ -238,7 +246,6 @@ class Feeds extends Handler_Protected { if ($_REQUEST["debug"]) $timing_info = print_checkpoint("H0", $timing_info); - if (!$cat_view && is_numeric($feed) && $feed < PLUGIN_FEED_BASE_INDEX && $feed > LABEL_BASE_INDEX) { $handler = PluginHost::getInstance()->get_feed_handler( PluginHost::feed_to_pfeed_id($feed)); @@ -283,7 +290,7 @@ class Feeds extends Handler_Protected { if ($_REQUEST["debug"]) $timing_info = print_checkpoint("H1", $timing_info); - $result = $qfh_ret[0]; + $result = $qfh_ret[0]; // this could be either a PDO query result or a -1 if first id changed $feed_title = $qfh_ret[1]; $feed_site_url = $qfh_ret[2]; $last_error = $qfh_ret[3]; @@ -300,8 +307,6 @@ class Feeds extends Handler_Protected { $feed, $cat_view, $search, $last_error, $last_updated); - $headlines_count = is_numeric($result) ? 0 : $this->dbh->num_rows($result); - if ($offset == 0) { foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HEADLINES_BEFORE) as $p) { $reply['content'] .= $p->hook_headlines_before($feed, $cat_view, $qfh_ret); @@ -310,18 +315,18 @@ class Feeds extends Handler_Protected { $reply['content'] = ''; - if ($headlines_count > 0) { + $headlines_count = 0; - $lnum = $offset; + $lnum = $offset; + $num_unread = 0; + if ($_REQUEST["debug"]) $timing_info = print_checkpoint("PS", $timing_info); + $expand_cdm = get_pref('CDM_EXPANDED'); - $num_unread = 0; - $cur_feed_title = ''; + if (is_object($result)) { - if ($_REQUEST["debug"]) $timing_info = print_checkpoint("PS", $timing_info); + while ($line = $result->fetch()) { - $expand_cdm = get_pref('CDM_EXPANDED'); - - while ($line = $this->dbh->fetch_assoc($result)) { + ++$headlines_count; $line["content_preview"] = "— " . truncate_string(strip_tags($line["content"]), 250); @@ -338,6 +343,8 @@ class Feeds extends Handler_Protected { $label_cache = $line["label_cache"]; $labels = false; + $mouseover_attrs = "onmouseover='postMouseIn(event, $id)' onmouseout='postMouseOut($id)'"; + if ($label_cache) { $label_cache = json_decode($label_cache, true); @@ -361,45 +368,18 @@ class Feeds extends Handler_Protected { $class = ""; - if (sql_bool_to_bool($line["unread"])) { + if ($line["unread"]) { $class .= " Unread"; ++$num_unread; } - if (sql_bool_to_bool($line["marked"])) { - $marked_pic = "<img - src=\"images/mark_set.png\" - class=\"markedPic\" alt=\"Unstar article\" - onclick='toggleMark($id)'>"; - $class .= " marked"; - } else { - $marked_pic = "<img - src=\"images/mark_unset.png\" - class=\"markedPic\" alt=\"Star article\" - onclick='toggleMark($id)'>"; - } + $marked_pic_src = $line["marked"] ? "mark_set.png" : "mark_unset.png"; + $class .= $line["marked"] ? " marked" : ""; + $marked_pic = "<img src=\"images/$marked_pic_src\" class=\"markedPic\" onclick='toggleMark($id)'>"; - if (sql_bool_to_bool($line["published"])) { - $published_pic = "<img src=\"images/pub_set.png\" - class=\"pubPic\" - alt=\"Unpublish article\" onclick='togglePub($id)'>"; - $class .= " published"; - } else { - $published_pic = "<img src=\"images/pub_unset.png\" - class=\"pubPic\" - alt=\"Publish article\" onclick='togglePub($id)'>"; - } - -# $content_link = "<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"".$line["link"]."\">" . -# $line["title"] . "</a>"; - -# $content_link = "<a -# href=\"" . htmlspecialchars($line["link"]) . "\" -# onclick=\"view($id,$feed_id);\">" . -# $line["title"] . "</a>"; - -# $content_link = "<a href=\"javascript:viewContentUrl('".$line["link"]."');\">" . -# $line["title"] . "</a>"; + $published_pic_src = $line["published"] ? "pub_set.png" : "pub_unset.png"; + $class .= $line["published"] ? " published" : ""; + $published_pic = "<img src=\"images/$published_pic_src\" class=\"pubPic\" onclick='togglePub($id)'>"; $updated_fmt = make_local_datetime($line["updated"], false, false, false, true); $date_entered_fmt = T_sprintf("Imported at %s", @@ -409,12 +389,8 @@ class Feeds extends Handler_Protected { $score_pic = "images/" . get_score_pic($score); -/* $score_title = __("(Click to change)"); - $score_pic = "<img class='hlScorePic' src=\"images/$score_pic\" - onclick=\"adjustArticleScore($id, $score)\" title=\"$score $score_title\">"; */ - $score_pic = "<img class='hlScorePic' score='$score' onclick='changeScore($id, this)' src=\"$score_pic\" - title=\"$score\">"; + title=\"$score\">"; if ($score > 500) { $hlc_suffix = "high"; @@ -430,7 +406,7 @@ class Feeds extends Handler_Protected { $entry_author = " — $entry_author"; } - $has_feed_icon = feed_has_icon($feed_id); + $has_feed_icon = feeds::feedHasIcon($feed_id); if ($has_feed_icon) { $feed_icon_img = "<img class=\"tinyFeedIcon\" src=\"".ICONS_URL."/$feed_id.ico\" alt=\"\">"; @@ -456,33 +432,27 @@ class Feeds extends Handler_Protected { if ($vfeed_group_enabled) { if ($feed_id != $vgroup_last_feed && $line["feed_title"]) { - $cur_feed_title = $line["feed_title"]; $vgroup_last_feed = $feed_id; - $cur_feed_title = htmlspecialchars($cur_feed_title); - $vf_catchup_link = "<a class='catchup' onclick='catchupFeedInGroup($feed_id);' href='#'>".__('mark feed as read')."</a>"; $reply['content'] .= "<div data-feed-id='$feed_id' id='FTITLE-$feed_id' class='cdmFeedTitle'>". "<div style='float : right'>$feed_icon_img</div>". "<a class='title' href=\"#\" onclick=\"viewfeed({feed:$feed_id})\">". $line["feed_title"]."</a> - $vf_catchup_link</div>"; + $vf_catchup_link</div>"; } } - $mouseover_attrs = "onmouseover='postMouseIn(event, $id)' - onmouseout='postMouseOut($id)'"; - $reply['content'] .= "<div class='hl hlMenuAttach $class' data-orig-feed-id='$feed_id' data-article-id='$id' id='RROW-$id' $mouseover_attrs>"; $reply['content'] .= "<div class='hlLeft'>"; $reply['content'] .= "<input dojoType=\"dijit.form.CheckBox\" - type=\"checkbox\" onclick=\"toggleSelectRow2(this)\" - class='rchk'>"; + type=\"checkbox\" onclick=\"toggleSelectRow2(this)\" + class='rchk'>"; $reply['content'] .= "$marked_pic"; $reply['content'] .= "$published_pic"; @@ -490,14 +460,14 @@ class Feeds extends Handler_Protected { $reply['content'] .= "</div>"; $reply['content'] .= "<div onclick='return hlClicked(event, $id)' - class=\"hlTitle\"><span class='hlContent $hlc_suffix'>"; + class=\"hlTitle\"><span class='hlContent $hlc_suffix'>"; $reply['content'] .= "<a id=\"RTITLE-$id\" class=\"title $hlc_suffix\" - href=\"" . htmlspecialchars($line["link"]) . "\" - onclick=\"\">" . + href=\"" . htmlspecialchars($line["link"]) . "\" + onclick=\"\">" . truncate_string($line["title"], 200); if (get_pref('SHOW_CONTENT_PREVIEW')) { - $reply['content'] .= "<span class=\"contentPreview\">" . $line["content_preview"] . "</span>"; + $reply['content'] .= "<span class=\"contentPreview\">" . $line["content_preview"] . "</span>"; } $reply['content'] .= "</a></span>"; @@ -519,7 +489,7 @@ class Feeds extends Handler_Protected { $reply['content'] .= "<span class=\"hlUpdated\">"; $reply['content'] .= "<div title='$date_entered_fmt'>$updated_fmt</div> - </span>"; + </span>"; $reply['content'] .= "<div class=\"hlRight\">"; @@ -528,9 +498,9 @@ class Feeds extends Handler_Protected { if ($line["feed_title"] && !$vfeed_group_enabled) { $reply['content'] .= "<span onclick=\"viewfeed({feed:$feed_id})\" - style=\"cursor : pointer\" - title=\"".htmlspecialchars($line['feed_title'])."\"> - $feed_icon_img</span>"; + style=\"cursor : pointer\" + title=\"".htmlspecialchars($line['feed_title'])."\"> + $feed_icon_img</span>"; } $reply['content'] .= "</div>"; @@ -544,7 +514,7 @@ class Feeds extends Handler_Protected { $tags = false; $line["content"] = sanitize($line["content"], - sql_bool_to_bool($line['hide_images']), false, $entry_site_url, $highlight_words, $line["id"]); + $line['hide_images'], false, $entry_site_url, $highlight_words, $line["id"]); foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_CDM) as $p) { $line = $p->hook_render_article_cdm($line); @@ -553,20 +523,12 @@ class Feeds extends Handler_Protected { if ($vfeed_group_enabled && $line["feed_title"]) { if ($feed_id != $vgroup_last_feed) { - $cur_feed_title = $line["feed_title"]; $vgroup_last_feed = $feed_id; - $cur_feed_title = htmlspecialchars($cur_feed_title); - $vf_catchup_link = "<a class='catchup' onclick='catchupFeedInGroup($feed_id);' href='#'>".__('mark feed as read')."</a>"; - $has_feed_icon = feed_has_icon($feed_id); - - if ($has_feed_icon) { - $feed_icon_img = "<img class=\"tinyFeedIcon\" src=\"".ICONS_URL."/$feed_id.ico\" alt=\"\">"; - } else { - //$feed_icon_img = "<img class=\"tinyFeedIcon\" src=\"images/blank_icon.gif\" alt=\"\">"; - } + $feed_icon_src = Feeds::getFeedIcon($feed_id); + $feed_icon_img = "<img class=\"tinyFeedIcon\" src=\"$feed_icon_src\">"; $reply['content'] .= "<div data-feed-id='$feed_id' id='FTITLE-$feed_id' class='cdmFeedTitle'>". "<div style=\"float : right\">$feed_icon_img</div>". @@ -576,27 +538,24 @@ class Feeds extends Handler_Protected { } } - $mouseover_attrs = "onmouseover='postMouseIn(event, $id)' - onmouseout='postMouseOut($id)'"; - $expanded_class = $expand_cdm ? "expanded" : "expandable"; - + $tmp_content = "<div class=\"cdm $hlc_suffix $expanded_class $class\" - id=\"RROW-$id\" data-article-id='$id' data-orig-feed-id='$feed_id' $mouseover_attrs>"; + id=\"RROW-$id\" data-article-id='$id' data-orig-feed-id='$feed_id' $mouseover_attrs>"; $tmp_content .= "<div class=\"cdmHeader\">"; $tmp_content .= "<div style=\"vertical-align : middle\">"; $tmp_content .= "<input dojoType=\"dijit.form.CheckBox\" - type=\"checkbox\" onclick=\"toggleSelectRow2(this, false, true)\" - class='rchk'>"; + type=\"checkbox\" onclick=\"toggleSelectRow2(this, false, true)\" + class='rchk'>"; $tmp_content .= "$marked_pic"; $tmp_content .= "$published_pic"; $tmp_content .= "</div>"; - if ($highlight_words && count($highlight_words > 0)) { + if ($highlight_words && count($highlight_words) > 0) { foreach ($highlight_words as $word) { $line["title"] = preg_replace("/(\Q$word\E)/i", "<span class=\"highlight\">$1</span>", $line["title"]); @@ -605,12 +564,12 @@ class Feeds extends Handler_Protected { // data-article-id included for context menu $tmp_content .= "<span id=\"RTITLE-$id\" - onclick=\"return cdmClicked(event, $id);\" - data-article-id=\"$id\" - class=\"titleWrap hlMenuAttach $hlc_suffix\"> - <a class=\"title $hlc_suffix\" - title=\"".htmlspecialchars($line["title"])."\" - target=\"_blank\" rel=\"noopener noreferrer\" href=\"". + onclick=\"return cdmClicked(event, $id);\" + data-article-id=\"$id\" + class=\"titleWrap hlMenuAttach $hlc_suffix\"> + <a class=\"title $hlc_suffix\" + title=\"".htmlspecialchars($line["title"])."\" + target=\"_blank\" rel=\"noopener noreferrer\" href=\"". htmlspecialchars($line["link"])."\">". $line["title"] . "</a> <span class=\"author\">$entry_author</span>"; @@ -618,8 +577,8 @@ class Feeds extends Handler_Protected { $tmp_content .= $labels_str; $tmp_content .= "<span class='collapseBtn' style='display : none'> - <img src=\"images/collapse.png\" onclick=\"cdmCollapseArticle(event, $id)\" - title=\"".__("Collapse article")."\"/></span>"; + <img src=\"images/collapse.png\" onclick=\"cdmCollapseArticle(event, $id)\" + title=\"".__("Collapse article")."\"/></span>"; if (!$expand_cdm) $content_hidden = "style=\"display : none\""; @@ -635,10 +594,10 @@ class Feeds extends Handler_Protected { $rgba = @$rgba_cache[$feed_id]; $tmp_content .= "<div class=\"hlFeed\"> - <a href=\"#\" style=\"background-color: rgba($rgba,0.3)\" - onclick=\"viewfeed({feed:$feed_id})\">". + <a href=\"#\" style=\"background-color: rgba($rgba,0.3)\" + onclick=\"viewfeed({feed:$feed_id})\">". truncate_string($line["feed_title"],30)."</a> - </div>"; + </div>"; } } @@ -649,16 +608,16 @@ class Feeds extends Handler_Protected { if (!get_pref("VFEED_GROUP_BY_FEED") && $line["feed_title"]) { $tmp_content .= "<span style=\"cursor : pointer\" - title=\"".htmlspecialchars($line["feed_title"])."\" - onclick=\"viewfeed({feed:$feed_id})\">$feed_icon_img</span>"; + title=\"".htmlspecialchars($line["feed_title"])."\" + onclick=\"viewfeed({feed:$feed_id})\">$feed_icon_img</span>"; } $tmp_content .= "</div>"; //scoreWrap $tmp_content .= "</div>"; //cdmHeader $tmp_content .= "<div class=\"cdmContent\" $content_hidden - onclick=\"return cdmClicked(event, $id, true);\" - id=\"CICD-$id\">"; + onclick=\"return cdmClicked(event, $id, true);\" + id=\"CICD-$id\">"; $tmp_content .= "<div id=\"POSTNOTE-$id\">"; if ($line['note']) { @@ -672,20 +631,19 @@ class Feeds extends Handler_Protected { if ($line["orig_feed_id"]) { - $tmp_result = $this->dbh->query("SELECT * FROM ttrss_archived_feeds - WHERE id = ".$line["orig_feed_id"] . " AND owner_uid = " . $_SESSION["uid"]); + $ofgh = $this->pdo->prepare("SELECT * FROM ttrss_archived_feeds + WHERE id = ? AND owner_uid = ?"); + $ofgh->execute([$line["orig_feed_id"], $_SESSION['uid']]); - if ($this->dbh->num_rows($tmp_result) != 0) { + if ($tmp_line = $ofgh->fetch()) { $tmp_content .= "<div clear='both'>"; $tmp_content .= __("Originally from:"); $tmp_content .= " "; - $tmp_line = $this->dbh->fetch_assoc($tmp_result); - $tmp_content .= "<a target='_blank' rel='noopener noreferrer' - href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" . + href=' " . htmlspecialchars($tmp_line['site_url']) . "'>" . $tmp_line['title'] . "</a>"; $tmp_content .= " "; @@ -707,8 +665,9 @@ class Feeds extends Handler_Protected { $tmp_content .= "<div class=\"cdmIntermediate\">"; - $always_display_enclosures = sql_bool_to_bool($line["always_display_enclosures"]); - $tmp_content .= Article::format_article_enclosures($id, $always_display_enclosures, $line["content"], sql_bool_to_bool($line["hide_images"])); + $always_display_enclosures = $line["always_display_enclosures"]; + $tmp_content .= Article::format_article_enclosures($id, $always_display_enclosures, + $line["content"], $line["hide_images"]); $tmp_content .= "</div>"; // cdmIntermediate @@ -723,9 +682,9 @@ class Feeds extends Handler_Protected { $tmp_content .= "<span class='left'>"; $tmp_content .= "<img src='images/tag.png' alt='Tags' title='Tags'> - <span id=\"ATSTR-$id\">$tags_str</span> - <a title=\"".__('Edit tags for this article')."\" - href=\"#\" onclick=\"editArticleTags($id)\">(+)</a>"; + <span id=\"ATSTR-$id\">$tags_str</span> + <a title=\"".__('Edit tags for this article')."\" + href=\"#\" onclick=\"editArticleTags($id)\">(+)</a>"; $num_comments = (int) $line["num_comments"]; $entry_comments = ""; @@ -737,7 +696,7 @@ class Feeds extends Handler_Protected { $comments_url = htmlspecialchars($line["link"]); } $entry_comments = "<a class=\"postComments\" - target='_blank' rel='noopener noreferrer' href=\"$comments_url\">$num_comments ". + target='_blank' rel='noopener noreferrer' href=\"$comments_url\">$num_comments ". _ngettext("comment", "comments", $num_comments)."</a>"; } else { @@ -751,9 +710,6 @@ class Feeds extends Handler_Protected { $tmp_content .= "</span>"; $tmp_content .= "<div>"; -// $tmp_content .= "$marked_pic"; -// $tmp_content .= "$published_pic"; - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) { $tmp_content .= $p->hook_article_button($line); } @@ -773,58 +729,64 @@ class Feeds extends Handler_Protected { ++$lnum; } + } - if ($_REQUEST["debug"]) $timing_info = print_checkpoint("PE", $timing_info); - - } else if (!is_numeric($result)) { - $message = ""; - - switch ($view_mode) { - case "unread": - $message = __("No unread articles found to display."); - break; - case "updated": - $message = __("No updated articles found to display."); - break; - case "marked": - $message = __("No starred articles found to display."); - break; - default: - if ($feed < LABEL_BASE_INDEX) { - $message = __("No articles found to display. You can assign articles to labels manually from article header context menu (applies to all selected articles) or use a filter."); - } else { - $message = __("No articles found to display."); - } - } + if ($_REQUEST["debug"]) $timing_info = print_checkpoint("PE", $timing_info); + + if (!$headlines_count) { + + if (!is_numeric($result)) { - if (!$offset && $message) { - $reply['content'] = "<div class='whiteBox'>$message"; + switch ($view_mode) { + case "unread": + $message = __("No unread articles found to display."); + break; + case "updated": + $message = __("No updated articles found to display."); + break; + case "marked": + $message = __("No starred articles found to display."); + break; + default: + if ($feed < LABEL_BASE_INDEX) { + $message = __("No articles found to display. You can assign articles to labels manually from article header context menu (applies to all selected articles) or use a filter."); + } else { + $message = __("No articles found to display."); + } + } - $reply['content'] .= "<p><span class=\"insensitive\">"; + if (!$offset && $message) { + $reply['content'] = "<div class='whiteBox'>$message"; - $result = $this->dbh->query("SELECT ".SUBSTRING_FOR_DATE."(MAX(last_updated), 1, 19) AS last_updated FROM ttrss_feeds - WHERE owner_uid = " . $_SESSION['uid']); + $reply['content'] .= "<p><span class=\"insensitive\">"; - $last_updated = $this->dbh->fetch_result($result, 0, "last_updated"); - $last_updated = make_local_datetime($last_updated, false); + $sth = $this->pdo->prepare("SELECT " . SUBSTRING_FOR_DATE . "(MAX(last_updated), 1, 19) AS last_updated FROM ttrss_feeds + WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + $row = $sth->fetch(); - $reply['content'] .= sprintf(__("Feeds last updated at %s"), $last_updated); + $last_updated = make_local_datetime($row["last_updated"], false); - $result = $this->dbh->query("SELECT COUNT(id) AS num_errors - FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ".$_SESSION["uid"]); + $reply['content'] .= sprintf(__("Feeds last updated at %s"), $last_updated); - $num_errors = $this->dbh->fetch_result($result, 0, "num_errors"); + $sth = $this->pdo->prepare("SELECT COUNT(id) AS num_errors + FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + $row = $sth->fetch(); - if ($num_errors > 0) { - $reply['content'] .= "<br/>"; - $reply['content'] .= "<a class=\"insensitive\" href=\"#\" onclick=\"showFeedsWithErrors()\">". - __('Some feeds have update errors (click for details)')."</a>"; - } - $reply['content'] .= "</span></p></div>"; + $num_errors = $row["num_errors"]; + if ($num_errors > 0) { + $reply['content'] .= "<br/>"; + $reply['content'] .= "<a class=\"insensitive\" href=\"#\" onclick=\"showFeedsWithErrors()\">" . + __('Some feeds have update errors (click for details)') . "</a>"; + } + $reply['content'] .= "</span></p></div>"; + + } + } else if (is_numeric($result) && $result == -1) { + $reply['first_id_changed'] = true; } - } else if (is_numeric($result) && $result == -1) { - $reply['first_id_changed'] = true; } if ($_REQUEST["debug"]) $timing_info = print_checkpoint("H2", $timing_info); @@ -834,8 +796,10 @@ class Feeds extends Handler_Protected { } function catchupAll() { - $this->dbh->query("UPDATE ttrss_user_entries SET - last_read = NOW(), unread = false WHERE unread = true AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET + last_read = NOW(), unread = false WHERE unread = true AND owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + CCache::zero_all($_SESSION["uid"]); } @@ -846,16 +810,16 @@ class Feeds extends Handler_Protected { if ($_REQUEST["debug"]) $timing_info = print_checkpoint("0", $timing_info); - $feed = $this->dbh->escape_string($_REQUEST["feed"]); - $method = $this->dbh->escape_string($_REQUEST["m"]); - $view_mode = $this->dbh->escape_string($_REQUEST["view_mode"]); + $feed = $_REQUEST["feed"]; + $method = $_REQUEST["m"]; + $view_mode = $_REQUEST["view_mode"]; $limit = 30; @$cat_view = $_REQUEST["cat"] == "true"; - @$next_unread_feed = $this->dbh->escape_string($_REQUEST["nuf"]); - @$offset = $this->dbh->escape_string($_REQUEST["skip"]); - @$vgroup_last_feed = $this->dbh->escape_string($_REQUEST["vgrlf"]); - $order_by = $this->dbh->escape_string($_REQUEST["order_by"]); - $check_first_id = $this->dbh->escape_string($_REQUEST["fid"]); + @$next_unread_feed = $_REQUEST["nuf"]; + @$offset = $_REQUEST["skip"]; + @$vgroup_last_feed = $_REQUEST["vgrlf"]; + $order_by = $_REQUEST["order_by"]; + $check_first_id = $_REQUEST["fid"]; if (is_numeric($feed)) $feed = (int) $feed; @@ -867,21 +831,30 @@ class Feeds extends Handler_Protected { return; } - $result = false; - + $sth = false; if ($feed < LABEL_BASE_INDEX) { + $label_feed = Labels::feed_to_label_id($feed); - $result = $this->dbh->query("SELECT id FROM ttrss_labels2 WHERE - id = '$label_feed' AND owner_uid = " . $_SESSION['uid']); + + $sth = $this->pdo->prepare("SELECT id FROM ttrss_labels2 WHERE + id = ? AND owner_uid = ?"); + $sth->execute([$label_feed, $_SESSION['uid']]); + } else if (!$cat_view && is_numeric($feed) && $feed > 0) { - $result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE - id = '$feed' AND owner_uid = " . $_SESSION['uid']); + + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE + id = ? AND owner_uid = ?"); + $sth->execute([$feed, $_SESSION['uid']]); + } else if ($cat_view && is_numeric($feed) && $feed > 0) { - $result = $this->dbh->query("SELECT id FROM ttrss_feed_categories WHERE - id = '$feed' AND owner_uid = " . $_SESSION['uid']); + + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feed_categories WHERE + id = ? AND owner_uid = ?"); + + $sth->execute([$feed, $_SESSION['uid']]); } - if ($result && $this->dbh->num_rows($result) == 0) { + if ($sth && !$sth->fetch()) { print json_encode($this->generate_error_feed(__("Feed not found."))); return; } @@ -898,14 +871,16 @@ class Feeds extends Handler_Protected { /* bump login timestamp if needed */ if (time() - $_SESSION["last_login_update"] > 3600) { - $this->dbh->query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " . - $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_users SET last_login = NOW() WHERE id = ?"); + $sth->execute([$_SESSION['uid']]); + $_SESSION["last_login_update"] = time(); } if (!$cat_view && is_numeric($feed) && $feed > 0) { - $this->dbh->query("UPDATE ttrss_feeds SET last_viewed = NOW() - WHERE id = '$feed' AND owner_uid = ".$_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET last_viewed = NOW() + WHERE id = ? AND owner_uid = ?"); + $sth->execute([$feed, $_SESSION['uid']]); } $reply['headlines'] = array(); @@ -976,18 +951,21 @@ class Feeds extends Handler_Protected { $reply['headlines']['content'] .= "<p><span class=\"insensitive\">"; - $result = $this->dbh->query("SELECT ".SUBSTRING_FOR_DATE."(MAX(last_updated), 1, 19) AS last_updated FROM ttrss_feeds - WHERE owner_uid = " . $_SESSION['uid']); + $sth = $this->pdo->prepare("SELECT ".SUBSTRING_FOR_DATE."(MAX(last_updated), 1, 19) AS last_updated FROM ttrss_feeds + WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + $row = $sth->fetch(); - $last_updated = $this->dbh->fetch_result($result, 0, "last_updated"); - $last_updated = make_local_datetime($last_updated, false); + $last_updated = make_local_datetime($row["last_updated"], false); $reply['headlines']['content'] .= sprintf(__("Feeds last updated at %s"), $last_updated); - $result = $this->dbh->query("SELECT COUNT(id) AS num_errors - FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ".$_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT COUNT(id) AS num_errors + FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + $row = $sth->fetch(); - $num_errors = $this->dbh->fetch_result($result, 0, "num_errors"); + $num_errors = $row["num_errors"]; if ($num_errors > 0) { $reply['headlines']['content'] .= "<br/>"; @@ -1104,7 +1082,7 @@ class Feeds extends Handler_Protected { function feedBrowser() { if (defined('_DISABLE_FEED_BROWSER') && _DISABLE_FEED_BROWSER) return; - $browser_search = $this->dbh->escape_string($_REQUEST["search"]); + $browser_search = $_REQUEST["search"]; print_hidden("op", "rpc"); print_hidden("method", "updateFeedBrowser"); @@ -1150,7 +1128,7 @@ class Feeds extends Handler_Protected { } function search() { - $this->params = explode(":", $this->dbh->escape_string($_REQUEST["param"]), 2); + $this->params = explode(":", $_REQUEST["param"], 2); $active_feed_id = sprintf("%d", $this->params[0]); $is_cat = $this->params[1] != "false"; @@ -1193,16 +1171,24 @@ class Feeds extends Handler_Protected { @$do_update = $_REQUEST["action"] == "do_update"; $csrf_token = $_REQUEST["csrf_token"]; + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE id = ? AND owner_uid = ?"); + $sth->execute([$feed_id, $_SESSION['uid']]); + + if (!$sth->fetch()) { + print "Access denied."; + return; + } + $refetch_checked = isset($_REQUEST["force_refetch"]) ? "checked" : ""; $rehash_checked = isset($_REQUEST["force_rehash"]) ? "checked" : ""; ?> <html> <head> - <link rel="stylesheet" type="text/css" href="css/utility.css"> + <?php echo stylesheet_tag("css/default.css") ?> <title>Feed Debugger</title> </head> - <body class="small_margins"> + <body class="small_margins ttrss_utility claro"> <h1>Feed Debugger: <?php echo "$feed_id: " . $this->getFeedTitle($feed_id) ?></h1> <form method="GET" action=""> <input type="hidden" name="op" value="feeds"> @@ -1237,9 +1223,10 @@ class Feeds extends Handler_Protected { if (!$owner_uid) $owner_uid = $_SESSION['uid']; + $pdo = Db::pdo(); + // Todo: all this interval stuff needs some generic generator function - $date_qpart = "false"; $search_qpart = is_array($search) && $search[0] ? search_to_sql($search[0], $search[1])[0] : 'true'; switch ($mode) { @@ -1284,50 +1271,55 @@ class Feeds extends Handler_Protected { $cat_qpart = "cat_id IS NULL"; } - db_query("UPDATE ttrss_user_entries + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false, last_read = NOW() WHERE ref_id IN (SELECT id FROM (SELECT DISTINCT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id - AND owner_uid = $owner_uid AND unread = true AND feed_id IN + AND owner_uid = ? AND unread = true AND feed_id IN (SELECT id FROM ttrss_feeds WHERE $cat_qpart) AND $date_qpart AND $search_qpart) as tmp)"); + $sth->execute([$owner_uid]); } else if ($feed == -2) { - db_query("UPDATE ttrss_user_entries + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false,last_read = NOW() WHERE (SELECT COUNT(*) FROM ttrss_user_labels2, ttrss_entries WHERE article_id = ref_id AND id = ref_id AND $date_qpart AND $search_qpart) > 0 - AND unread = true AND owner_uid = $owner_uid"); + AND unread = true AND owner_uid = ?"); + $sth->execute([$owner_uid]); } } else if ($feed > 0) { - db_query("UPDATE ttrss_user_entries + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false, last_read = NOW() WHERE ref_id IN (SELECT id FROM (SELECT DISTINCT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id - AND owner_uid = $owner_uid AND unread = true AND feed_id = $feed AND $date_qpart AND $search_qpart) as tmp)"); + AND owner_uid = ? AND unread = true AND feed_id = ? AND $date_qpart AND $search_qpart) as tmp)"); + $sth->execute([$owner_uid, $feed]); } else if ($feed < 0 && $feed > LABEL_BASE_INDEX) { // special, like starred if ($feed == -1) { - db_query("UPDATE ttrss_user_entries + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false, last_read = NOW() WHERE ref_id IN (SELECT id FROM (SELECT DISTINCT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id - AND owner_uid = $owner_uid AND unread = true AND marked = true AND $date_qpart AND $search_qpart) as tmp)"); + AND owner_uid = ? AND unread = true AND marked = true AND $date_qpart AND $search_qpart) as tmp)"); + $sth->execute([$owner_uid]); } if ($feed == -2) { - db_query("UPDATE ttrss_user_entries + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false, last_read = NOW() WHERE ref_id IN (SELECT id FROM (SELECT DISTINCT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id - AND owner_uid = $owner_uid AND unread = true AND published = true AND $date_qpart AND $search_qpart) as tmp)"); + AND owner_uid = ? AND unread = true AND published = true AND $date_qpart AND $search_qpart) as tmp)"); + $sth->execute([$owner_uid]); } if ($feed == -3) { - $intl = get_pref("FRESH_ARTICLE_MAX_AGE"); + $intl = (int) get_pref("FRESH_ARTICLE_MAX_AGE"); if (DB_TYPE == "pgsql") { $match_part = "date_entered > NOW() - INTERVAL '$intl hour' "; @@ -1336,43 +1328,47 @@ class Feeds extends Handler_Protected { INTERVAL $intl HOUR) "; } - db_query("UPDATE ttrss_user_entries + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false, last_read = NOW() WHERE ref_id IN (SELECT id FROM (SELECT DISTINCT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id - AND owner_uid = $owner_uid AND score >= 0 AND unread = true AND $date_qpart AND $match_part AND $search_qpart) as tmp)"); + AND owner_uid = ? AND score >= 0 AND unread = true AND $date_qpart AND $match_part AND $search_qpart) as tmp)"); + $sth->execute([$owner_uid]); } if ($feed == -4) { - db_query("UPDATE ttrss_user_entries + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false, last_read = NOW() WHERE ref_id IN (SELECT id FROM (SELECT DISTINCT id FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id - AND owner_uid = $owner_uid AND unread = true AND $date_qpart AND $search_qpart) as tmp)"); + AND owner_uid = ? AND unread = true AND $date_qpart AND $search_qpart) as tmp)"); + $sth->execute([$owner_uid]); } } else if ($feed < LABEL_BASE_INDEX) { // label $label_id = Labels::feed_to_label_id($feed); - db_query("UPDATE ttrss_user_entries + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false, last_read = NOW() WHERE ref_id IN (SELECT id FROM (SELECT DISTINCT ttrss_entries.id FROM ttrss_entries, ttrss_user_entries, ttrss_user_labels2 WHERE ref_id = id - AND label_id = '$label_id' AND ref_id = article_id - AND owner_uid = $owner_uid AND unread = true AND $date_qpart AND $search_qpart) as tmp)"); + AND label_id = ? AND ref_id = article_id + AND owner_uid = ? AND unread = true AND $date_qpart AND $search_qpart) as tmp)"); + $sth->execute([$label_id, $owner_uid]); } CCache::update($feed, $owner_uid, $cat_view); } else { // tag - db_query("UPDATE ttrss_user_entries + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET unread = false, last_read = NOW() WHERE ref_id IN (SELECT id FROM (SELECT DISTINCT ttrss_entries.id FROM ttrss_entries, ttrss_user_entries, ttrss_tags WHERE ref_id = ttrss_entries.id - AND post_int_id = int_id AND tag_name = '$feed' - AND ttrss_user_entries.owner_uid = $owner_uid AND unread = true AND $date_qpart AND $search_qpart) as tmp)"); + AND post_int_id = int_id AND tag_name = ? + AND ttrss_user_entries.owner_uid = ? AND unread = true AND $date_qpart AND $search_qpart) as tmp)"); + $sth->execute([$feed, $owner_uid]); } } @@ -1383,6 +1379,8 @@ class Feeds extends Handler_Protected { $n_feed = (int) $feed; $need_entries = false; + $pdo = Db::pdo(); + if (!$owner_uid) $owner_uid = $_SESSION["uid"]; if ($unread_only) { @@ -1391,19 +1389,23 @@ class Feeds extends Handler_Protected { $unread_qpart = "true"; } + $match_part = ""; + if ($is_cat) { return Feeds::getCategoryUnread($n_feed, $owner_uid); } else if ($n_feed == -6) { return 0; } else if ($feed != "0" && $n_feed == 0) { - $feed = db_escape_string($feed); - - $result = db_query("SELECT SUM((SELECT COUNT(int_id) + $sth = $pdo->prepare("SELECT SUM((SELECT COUNT(int_id) FROM ttrss_user_entries,ttrss_entries WHERE int_id = post_int_id AND ref_id = id AND $unread_qpart)) AS count FROM ttrss_tags - WHERE owner_uid = $owner_uid AND tag_name = '$feed'"); - return db_fetch_result($result, 0, "count"); + WHERE owner_uid = ? AND tag_name = ?"); + + $sth->execute([$owner_uid, $feed]); + $row = $sth->fetch(); + + return $row["count"]; } else if ($n_feed == -1) { $match_part = "marked = true"; @@ -1412,7 +1414,7 @@ class Feeds extends Handler_Protected { } else if ($n_feed == -3) { $match_part = "unread = true AND score >= 0"; - $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid); + $intl = (int) get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid); if (DB_TYPE == "pgsql") { $match_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' "; @@ -1437,7 +1439,6 @@ class Feeds extends Handler_Protected { $label_id = Labels::feed_to_label_id($feed); return Feeds::getLabelUnread($label_id, $owner_uid); - } if ($match_part) { @@ -1450,25 +1451,26 @@ class Feeds extends Handler_Protected { $from_where = ""; } - $query = "SELECT count(int_id) AS unread + $sth = $pdo->prepare("SELECT count(int_id) AS unread FROM $from_qpart WHERE - $unread_qpart AND $from_where ($match_part) AND ttrss_user_entries.owner_uid = $owner_uid"; + $unread_qpart AND $from_where ($match_part) AND ttrss_user_entries.owner_uid = ?"); + $sth->execute([$owner_uid]); + $row = $sth->fetch(); - //echo "[$feed/$query]\n"; - - $result = db_query($query); + return $row["unread"]; } else { - $result = db_query("SELECT COUNT(post_int_id) AS unread + $sth = $pdo->prepare("SELECT COUNT(post_int_id) AS unread FROM ttrss_tags,ttrss_user_entries,ttrss_entries - WHERE tag_name = '$feed' AND post_int_id = int_id AND ref_id = ttrss_entries.id - AND $unread_qpart AND ttrss_tags.owner_uid = " . $owner_uid); - } + WHERE tag_name = ? AND post_int_id = int_id AND ref_id = ttrss_entries.id + AND $unread_qpart AND ttrss_tags.owner_uid = ,"); - $unread = db_fetch_result($result, 0, "unread"); + $sth->execute([$feed, $owner_uid]); + $row = $sth->fetch(); - return $unread; + return $row["unread"]; + } } /** @@ -1490,6 +1492,8 @@ class Feeds extends Handler_Protected { global $fetch_last_error; global $fetch_last_error_content; + $pdo = Db::pdo(); + $url = fix_url($url); if (!$url || !validate_feed_url($url)) return array("code" => 2); @@ -1520,41 +1524,46 @@ class Feeds extends Handler_Protected { $url = key($feedUrls); } - if ($cat_id == "0" || !$cat_id) { - $cat_qpart = "NULL"; - } else { - $cat_qpart = "'$cat_id'"; - } - - $result = db_query( - "SELECT id FROM ttrss_feeds - WHERE feed_url = '$url' AND owner_uid = ".$_SESSION["uid"]); + if (!$cat_id) $cat_id = null; - $auth_pass = db_escape_string($auth_pass); + $sth = $pdo->prepare("SELECT id FROM ttrss_feeds + WHERE feed_url = ? AND owner_uid = ?"); + $sth->execute([$url, $_SESSION['uid']]); - if (db_num_rows($result) == 0) { - $result = db_query( + if ($row = $sth->fetch()) { + return array("code" => 0, "feed_id" => (int) $row["id"]); + } else { + $sth = $pdo->prepare( "INSERT INTO ttrss_feeds (owner_uid,feed_url,title,cat_id, auth_login,auth_pass,update_method,auth_pass_encrypted) - VALUES ('".$_SESSION["uid"]."', '$url', - '[Unknown]', $cat_qpart, '$auth_login', '$auth_pass', 0, false)"); + VALUES (?, ?, ?, ?, ?, ?, 0, false)"); - $result = db_query( - "SELECT id FROM ttrss_feeds WHERE feed_url = '$url' - AND owner_uid = " . $_SESSION["uid"]); + $sth->execute([$_SESSION['uid'], $url, "[Unknown]", $cat_id, $auth_login, $auth_pass]); - $feed_id = db_fetch_result($result, 0, "id"); + $sth = $pdo->prepare("SELECT id FROM ttrss_feeds WHERE feed_url = ? + AND owner_uid = ?"); + $sth->execute([$url, $_SESSION['uid']]); + $row = $sth->fetch(); + + $feed_id = $row["id"]; if ($feed_id) { RSSUtils::set_basic_feed_info($feed_id); } return array("code" => 1, "feed_id" => (int) $feed_id); - } else { - return array("code" => 0, "feed_id" => (int) db_fetch_result($result, 0, "id")); + } } + static function getIconFile($feed_id) { + return ICONS_DIR . "/$feed_id.ico"; + } + + static function feedHasIcon($id) { + return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0; + } + static function getFeedIcon($id) { switch ($id) { case 0: @@ -1579,8 +1588,11 @@ class Feeds extends Handler_Protected { if ($id < LABEL_BASE_INDEX) { return "images/label.png"; } else { - if (file_exists(ICONS_DIR . "/$id.ico")) - return ICONS_URL . "/$id.ico"; + $icon = self::getIconFile($id); + + if ($icon && file_exists($icon)) { + return ICONS_URL . "/" . basename($icon) . "?" . filemtime($icon); + } } break; } @@ -1589,6 +1601,8 @@ class Feeds extends Handler_Protected { } static function getFeedTitle($id, $cat = false) { + $pdo = Db::pdo(); + if ($cat) { return Feeds::getCategoryTitle($id); } else if ($id == -1) { @@ -1604,21 +1618,29 @@ class Feeds extends Handler_Protected { } else if ($id == -6) { return __("Recently read"); } else if ($id < LABEL_BASE_INDEX) { + $label_id = Labels::feed_to_label_id($id); - $result = db_query("SELECT caption FROM ttrss_labels2 WHERE id = '$label_id'"); - if (db_num_rows($result) == 1) { - return db_fetch_result($result, 0, "caption"); + + $sth = $pdo->prepare("SELECT caption FROM ttrss_labels2 WHERE id = ?"); + $sth->execute([$label_id]); + + if ($row = $sth->fetch()) { + return $row["caption"]; } else { return "Unknown label ($label_id)"; } } else if (is_numeric($id) && $id > 0) { - $result = db_query("SELECT title FROM ttrss_feeds WHERE id = '$id'"); - if (db_num_rows($result) == 1) { - return db_fetch_result($result, 0, "title"); + + $sth = $pdo->prepare("SELECT title FROM ttrss_feeds WHERE id = ?"); + $sth->execute([$id]); + + if ($row = $sth->fetch()) { + return $row["title"]; } else { return "Unknown feed ($id)"; } + } else { return $id; } @@ -1628,19 +1650,20 @@ class Feeds extends Handler_Protected { if (!$owner_uid) $owner_uid = $_SESSION["uid"]; + $pdo = Db::pdo(); + if ($cat >= 0) { - if ($cat != 0) { - $cat_query = "cat_id = '$cat'"; - } else { - $cat_query = "cat_id IS NULL"; - } + if (!$cat) $cat = null; - $result = db_query("SELECT id FROM ttrss_feeds WHERE $cat_query - AND owner_uid = " . $owner_uid); + $sth = $pdo->prepare("SELECT id FROM ttrss_feeds + WHERE (cat_id = :cat OR (:cat IS NULL AND cat_id IS NULL)) + AND owner_uid = :uid"); + + $sth->execute([":cat" => $cat, ":uid" => $owner_uid]); $cat_feeds = array(); - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { array_push($cat_feeds, "feed_id = " . $line["id"]); } @@ -1648,15 +1671,16 @@ class Feeds extends Handler_Protected { $match_part = implode(" OR ", $cat_feeds); - $result = db_query("SELECT COUNT(int_id) AS unread + $sth = $pdo->prepare("SELECT COUNT(int_id) AS unread FROM ttrss_user_entries WHERE unread = true AND ($match_part) - AND owner_uid = " . $owner_uid); + AND owner_uid = ?"); + $sth->execute([$owner_uid]); $unread = 0; # this needs to be rewritten - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { $unread += $line["unread"]; } @@ -1665,16 +1689,14 @@ class Feeds extends Handler_Protected { return getFeedUnread(-1) + getFeedUnread(-2) + getFeedUnread(-3) + getFeedUnread(0); } else if ($cat == -2) { - $result = db_query(" - SELECT COUNT(unread) AS unread FROM + $sth = $pdo->prepare("SELECT COUNT(unread) AS unread FROM ttrss_user_entries, ttrss_user_labels2 WHERE article_id = ref_id AND unread = true - AND ttrss_user_entries.owner_uid = '$owner_uid'"); - - $unread = db_fetch_result($result, 0, "unread"); - - return $unread; + AND ttrss_user_entries.owner_uid = ?"); + $sth->execute([$owner_uid]); + $row = $sth->fetch(); + return $row["unread"]; } } @@ -1682,12 +1704,15 @@ class Feeds extends Handler_Protected { static function getCategoryChildrenUnread($cat, $owner_uid = false) { if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - $result = db_query("SELECT id FROM ttrss_feed_categories WHERE parent_cat = '$cat' - AND owner_uid = $owner_uid"); + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT id FROM ttrss_feed_categories WHERE parent_cat = ? + AND owner_uid = ?"); + $sth->execute([$cat, $owner_uid]); $unread = 0; - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { $unread += Feeds::getCategoryUnread($line["id"], $owner_uid); $unread += Feeds::getCategoryChildrenUnread($line["id"], $owner_uid); } @@ -1697,16 +1722,16 @@ class Feeds extends Handler_Protected { static function getGlobalUnread($user_id = false) { - if (!$user_id) { - $user_id = $_SESSION["uid"]; - } + if (!$user_id) $user_id = $_SESSION["uid"]; - $result = db_query("SELECT SUM(value) AS c_id FROM ttrss_counters_cache - WHERE owner_uid = '$user_id' AND feed_id > 0"); + $pdo = Db::pdo(); - $c_id = db_fetch_result($result, 0, "c_id"); + $sth = $pdo->prepare("SELECT SUM(value) AS c_id FROM ttrss_counters_cache + WHERE owner_uid = ? AND feed_id > 0"); + $sth->execute([$user_id]); + $row = $sth->fetch(); - return $c_id; + return $row["c_id"]; } static function getCategoryTitle($cat_id) { @@ -1717,11 +1742,14 @@ class Feeds extends Handler_Protected { return __("Labels"); } else { - $result = db_query("SELECT title FROM ttrss_feed_categories WHERE - id = '$cat_id'"); + $pdo = Db::pdo(); - if (db_num_rows($result) == 1) { - return db_fetch_result($result, 0, "title"); + $sth = $pdo->prepare("SELECT title FROM ttrss_feed_categories WHERE + id = ?"); + $sth->execute([$cat_id]); + + if ($row = $sth->fetch()) { + return $row["title"]; } else { return __("Uncategorized"); } @@ -1731,11 +1759,15 @@ class Feeds extends Handler_Protected { static function getLabelUnread($label_id, $owner_uid = false) { if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - $result = db_query("SELECT COUNT(ref_id) AS unread FROM ttrss_user_entries, ttrss_user_labels2 - WHERE owner_uid = '$owner_uid' AND unread = true AND label_id = '$label_id' AND article_id = ref_id"); + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT COUNT(ref_id) AS unread FROM ttrss_user_entries, ttrss_user_labels2 + WHERE owner_uid = ? AND unread = true AND label_id = ? AND article_id = ref_id"); + + $sth->execute([$owner_uid, $label_id]); - if (db_num_rows($result) != 0) { - return db_fetch_result($result, 0, "unread"); + if ($row = $sth->fetch()) { + return $row["unread"]; } else { return 0; } @@ -1743,6 +1775,11 @@ class Feeds extends Handler_Protected { static function queryFeedHeadlines($params) { + $pdo = Db::pdo(); + + // WARNING: due to highly dynamic nature of this query its going to quote parameters + // right before adding them to SQL part + $feed = $params["feed"]; $limit = isset($params["limit"]) ? $params["limit"] : 30; $view_mode = $params["view_mode"]; @@ -1762,7 +1799,7 @@ class Feeds extends Handler_Protected { $skip_first_id_check = isset($params["skip_first_id_check"]) ? $params["skip_first_id_check"] : false; $ext_tables_part = ""; - $query_strategy_part = ""; + $limit_query_part = ""; $search_words = array(); @@ -1782,7 +1819,7 @@ class Feeds extends Handler_Protected { } if ($since_id) { - $since_id_part = "ttrss_entries.id > $since_id AND "; + $since_id_part = "ttrss_entries.id > ".$pdo->quote($since_id)." AND "; } else { $since_id_part = ""; } @@ -1822,7 +1859,7 @@ class Feeds extends Handler_Protected { } if ($limit > 0) { - $limit_query_part = "LIMIT " . $limit; + $limit_query_part = "LIMIT " . (int)$limit; } $allow_archived = false; @@ -1848,7 +1885,7 @@ class Feeds extends Handler_Protected { implode(",", $subcats).")"; } else { - $query_strategy_part = "cat_id = '$feed'"; + $query_strategy_part = "cat_id = " . $pdo->quote($feed); } } else { @@ -1858,7 +1895,7 @@ class Feeds extends Handler_Protected { $vfeed_query_part = "ttrss_feeds.title AS feed_title,"; } else { - $query_strategy_part = "feed_id = '$feed'"; + $query_strategy_part = "feed_id = " . $pdo->quote($feed); } } else if ($feed == 0 && !$cat_view) { // archive virtual feed $query_strategy_part = "feed_id IS NULL"; @@ -1913,7 +1950,7 @@ class Feeds extends Handler_Protected { } else if ($feed == -3) { // fresh virtual feed $query_strategy_part = "unread = true AND score >= 0"; - $intl = get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid); + $intl = (int) get_pref("FRESH_ARTICLE_MAX_AGE", $owner_uid); if (DB_TYPE == "pgsql") { $query_strategy_part .= " AND date_entered > NOW() - INTERVAL '$intl hour' "; @@ -1929,7 +1966,7 @@ class Feeds extends Handler_Protected { } else if ($feed <= LABEL_BASE_INDEX) { // labels $label_id = Labels::feed_to_label_id($feed); - $query_strategy_part = "label_id = '$label_id' AND + $query_strategy_part = "label_id = ".$pdo->quote($label_id)." AND ttrss_labels2.id = ttrss_user_labels2.label_id AND ttrss_user_labels2.article_id = ref_id"; @@ -1955,8 +1992,6 @@ class Feeds extends Handler_Protected { $vfeed_query_part = $override_vfeed; } - $feed_title = ""; - if ($search) { $feed_title = T_sprintf("Search results: %s", $search); } else { @@ -1964,24 +1999,25 @@ class Feeds extends Handler_Protected { $feed_title = Feeds::getCategoryTitle($feed); } else { if (is_numeric($feed) && $feed > 0) { - $result = db_query("SELECT title,site_url,last_error,last_updated - FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = $owner_uid"); - - $feed_title = db_fetch_result($result, 0, "title"); - $feed_site_url = db_fetch_result($result, 0, "site_url"); - $last_error = db_fetch_result($result, 0, "last_error"); - $last_updated = db_fetch_result($result, 0, "last_updated"); + $ssth = $pdo->prepare("SELECT title,site_url,last_error,last_updated + FROM ttrss_feeds WHERE id = ? AND owner_uid = ?"); + $ssth->execute([$feed, $owner_uid]); + $row = $ssth->fetch(); + + $feed_title = $row["title"]; + $feed_site_url = $row["site_url"]; + $last_error = $row["last_error"]; + $last_updated = $row["last_updated"]; } else { $feed_title = Feeds::getFeedTitle($feed); } } } - $content_query_part = "content, "; if ($limit_query_part) { - $offset_query_part = "OFFSET $offset"; + $offset_query_part = "OFFSET " . (int)$offset; } else { $offset_query_part = ""; } @@ -1990,9 +2026,9 @@ class Feeds extends Handler_Protected { // proper override_order applied above if ($vfeed_query_part && !$ignore_vfeed_group && get_pref('VFEED_GROUP_BY_FEED', $owner_uid)) { if (!$override_order) { - $order_by = "ttrss_feeds.title, $order_by"; + $order_by = "ttrss_feeds.title, ".$order_by; } else { - $order_by = "ttrss_feeds.title, $override_order"; + $order_by = "ttrss_feeds.title, ".$override_order; } } @@ -2045,7 +2081,7 @@ class Feeds extends Handler_Protected { $from_qpart WHERE $feed_check_qpart - ttrss_user_entries.owner_uid = '$owner_uid' AND + ttrss_user_entries.owner_uid = ".$pdo->quote($owner_uid)." AND $search_query_part $start_ts_query_part $since_id_part @@ -2056,9 +2092,10 @@ class Feeds extends Handler_Protected { print $query; } - $result = db_query($query); - if ($result && db_num_rows($result) > 0) { - $first_id = (int)db_fetch_result($result, 0, "id"); + $res = $pdo->query($query); + + if ($row = $res->fetch()) { + $first_id = (int)$row["id"]; if ($offset > 0 && $first_id && $check_first_id && $first_id != $check_first_id) { return array(-1, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id); @@ -2091,7 +2128,7 @@ class Feeds extends Handler_Protected { $from_qpart WHERE $feed_check_qpart - ttrss_user_entries.owner_uid = '$owner_uid' AND + ttrss_user_entries.owner_uid = ".$pdo->quote($owner_uid)." AND $search_query_part $start_ts_query_part $view_query_part @@ -2101,7 +2138,7 @@ class Feeds extends Handler_Protected { if ($_REQUEST["debug"]) print $query; - $result = db_query($query); + $res = $pdo->query($query); } else { // browsing by tag @@ -2135,9 +2172,9 @@ class Feeds extends Handler_Protected { FROM ttrss_entries, ttrss_user_entries, ttrss_tags WHERE ref_id = ttrss_entries.id AND - ttrss_user_entries.owner_uid = $owner_uid AND + ttrss_user_entries.owner_uid = ".$pdo->quote($owner_uid)." AND post_int_id = int_id AND - tag_name = '$feed' AND + tag_name = ".$pdo->quote($feed)." AND $view_query_part $search_query_part $query_strategy_part ORDER BY $order_by @@ -2145,20 +2182,23 @@ class Feeds extends Handler_Protected { if ($_REQUEST["debug"]) print $query; - $result = db_query($query); + $res = $pdo->query($query); } - return array($result, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id); + return array($res, $feed_title, $feed_site_url, $last_error, $last_updated, $search_words, $first_id); } static function getParentCategories($cat, $owner_uid) { $rv = array(); - $result = db_query("SELECT parent_cat FROM ttrss_feed_categories - WHERE id = '$cat' AND parent_cat IS NOT NULL AND owner_uid = $owner_uid"); + $pdo = Db::pdo(); - while ($line = db_fetch_assoc($result)) { + $sth = $pdo->prepare("SELECT parent_cat FROM ttrss_feed_categories + WHERE id = ? AND parent_cat IS NOT NULL AND owner_uid = ?"); + $sth->execute([$cat, $owner_uid]); + + while ($line = $sth->fetch()) { array_push($rv, $line["parent_cat"]); $rv = array_merge($rv, Feeds::getParentCategories($line["parent_cat"], $owner_uid)); } @@ -2169,10 +2209,13 @@ class Feeds extends Handler_Protected { static function getChildCategories($cat, $owner_uid) { $rv = array(); - $result = db_query("SELECT id FROM ttrss_feed_categories - WHERE parent_cat = '$cat' AND owner_uid = $owner_uid"); + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT id FROM ttrss_feed_categories + WHERE parent_cat = ? AND owner_uid = ?"); + $sth->execute([$cat, $owner_uid]); - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { array_push($rv, $line["id"]); $rv = array_merge($rv, Feeds::getChildCategories($line["id"], $owner_uid)); } @@ -2181,11 +2224,14 @@ class Feeds extends Handler_Protected { } static function getFeedCategory($feed) { - $result = db_query("SELECT cat_id FROM ttrss_feeds - WHERE id = '$feed'"); + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT cat_id FROM ttrss_feeds + WHERE id = ?"); + $sth->execute([$feed]); - if (db_num_rows($result) > 0) { - return db_fetch_result($result, 0, "cat_id"); + if ($row = $sth->fetch()) { + return $row["cat_id"]; } else { return false; } diff --git a/classes/handler.php b/classes/handler.php index 16c209607..5b1109492 100644 --- a/classes/handler.php +++ b/classes/handler.php @@ -1,10 +1,10 @@ <?php class Handler implements IHandler { - protected $dbh; + protected $pdo; protected $args; function __construct($args) { - $this->dbh = Db::get(); + $this->pdo = Db::pdo(); $this->args = $args; } diff --git a/classes/handler/public.php b/classes/handler/public.php index a98ea0193..8440bc355 100644 --- a/classes/handler/public.php +++ b/classes/handler/public.php @@ -15,14 +15,11 @@ class Handler_Public extends Handler { if (!$limit) $limit = 60; $date_sort_field = "date_entered DESC, updated DESC"; - $date_check_field = "date_entered"; if ($feed == -2 && !$is_cat) { $date_sort_field = "last_published DESC"; - $date_check_field = "last_published"; } else if ($feed == -1 && !$is_cat) { $date_sort_field = "last_marked DESC"; - $date_check_field = "last_marked"; } switch ($order) { @@ -36,39 +33,6 @@ class Handler_Public extends Handler { $date_sort_field = "updated DESC"; break; } - - $params = array( - "owner_uid" => $owner_uid, - "feed" => $feed, - "limit" => 1, - "view_mode" => $view_mode, - "cat_view" => $is_cat, - "search" => $search, - "override_order" => $date_sort_field, - "include_children" => true, - "ignore_vfeed_group" => true, - "offset" => $offset, - "start_ts" => $start_ts - ); - - $qfh_ret = Feeds::queryFeedHeadlines($params); - - $result = $qfh_ret[0]; - - if ($this->dbh->num_rows($result) != 0) { - - $ts = strtotime($this->dbh->fetch_result($result, 0, $date_check_field)); - - if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && - strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $ts) { - header('HTTP/1.0 304 Not Modified'); - return; - } - - $last_modified = gmdate("D, d M Y H:i:s", $ts) . " GMT"; - header("Last-Modified: $last_modified", true); - } - $params = array( "owner_uid" => $owner_uid, "feed" => $feed, @@ -106,7 +70,7 @@ class Handler_Public extends Handler { $tpl->setVariable('FEED_URL', htmlspecialchars($feed_self_url), true); $tpl->setVariable('SELF_URL', htmlspecialchars(get_self_url_prefix()), true); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $result->fetch()) { $line["content_preview"] = sanitize(truncate_string(strip_tags($line["content"]), 100, '...')); @@ -175,7 +139,7 @@ class Handler_Public extends Handler { $tpl->addBlock('feed'); $tpl->generateOutputToString($tmp); - if (@!$_REQUEST["noxml"]) { + if (@!clean($_REQUEST["noxml"])) { header("Content-Type: text/xml; charset=utf-8"); } else { header("Content-Type: text/plain; charset=utf-8"); @@ -194,7 +158,7 @@ class Handler_Public extends Handler { $feed['articles'] = array(); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $result->fetch()) { $line["content_preview"] = sanitize(truncate_string(strip_tags($line["content_preview"]), 100, '...')); @@ -255,13 +219,14 @@ class Handler_Public extends Handler { } function getUnread() { - $login = $this->dbh->escape_string($_REQUEST["login"]); - $fresh = $_REQUEST["fresh"] == "1"; + $login = clean($_REQUEST["login"]); + $fresh = clean($_REQUEST["fresh"]) == "1"; - $result = $this->dbh->query("SELECT id FROM ttrss_users WHERE login = '$login'"); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE login = ?"); + $sth->execute([$login]); - if ($this->dbh->num_rows($result) == 1) { - $uid = $this->dbh->fetch_result($result, 0, "id"); + if ($row = $sth->fetch()) { + $uid = $row["id"]; print Feeds::getGlobalUnread($uid); @@ -273,20 +238,20 @@ class Handler_Public extends Handler { } else { print "-1;User not found"; } - } function getProfiles() { - $login = $this->dbh->escape_string($_REQUEST["login"]); + $login = clean($_REQUEST["login"]); - $result = $this->dbh->query("SELECT ttrss_settings_profiles.* FROM ttrss_settings_profiles,ttrss_users - WHERE ttrss_users.id = ttrss_settings_profiles.owner_uid AND login = '$login' ORDER BY title"); + $sth = $this->pdo->prepare("SELECT ttrss_settings_profiles.* FROM ttrss_settings_profiles,ttrss_users + WHERE ttrss_users.id = ttrss_settings_profiles.owner_uid AND login = ? ORDER BY title"); + $sth->execute([$login]); print "<select dojoType='dijit.form.Select' style='width : 220px; margin : 0px' name='profile'>"; print "<option value='0'>" . __("Default profile") . "</option>"; - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { $id = $line["id"]; $title = $line["title"]; @@ -302,16 +267,17 @@ class Handler_Public extends Handler { } function share() { - $uuid = $this->dbh->escape_string($_REQUEST["key"]); + $uuid = clean($_REQUEST["key"]); - $result = $this->dbh->query("SELECT ref_id, owner_uid FROM ttrss_user_entries WHERE - uuid = '$uuid'"); + $sth = $this->pdo->prepare("SELECT ref_id, owner_uid FROM ttrss_user_entries WHERE + uuid = ?"); + $sth->execute([$uuid]); - if ($this->dbh->num_rows($result) != 0) { + if ($row = $sth->fetch()) { header("Content-Type: text/html"); - $id = $this->dbh->fetch_result($result, 0, "ref_id"); - $owner_uid = $this->dbh->fetch_result($result, 0, "owner_uid"); + $id = $row["ref_id"]; + $owner_uid = $row["owner_uid"]; $article = Article::format_article($id, false, true, $owner_uid); @@ -324,19 +290,19 @@ class Handler_Public extends Handler { } function rss() { - $feed = $this->dbh->escape_string($_REQUEST["id"]); - $key = $this->dbh->escape_string($_REQUEST["key"]); - $is_cat = sql_bool_to_bool($_REQUEST["is_cat"]); - $limit = (int)$this->dbh->escape_string($_REQUEST["limit"]); - $offset = (int)$this->dbh->escape_string($_REQUEST["offset"]); + $feed = clean($_REQUEST["id"]); + $key = clean($_REQUEST["key"]); + $is_cat = clean($_REQUEST["is_cat"]); + $limit = (int)clean($_REQUEST["limit"]); + $offset = (int)clean($_REQUEST["offset"]); - $search = $this->dbh->escape_string($_REQUEST["q"]); - $view_mode = $this->dbh->escape_string($_REQUEST["view-mode"]); - $order = $this->dbh->escape_string($_REQUEST["order"]); - $start_ts = $this->dbh->escape_string($_REQUEST["ts"]); + $search = clean($_REQUEST["q"]); + $view_mode = clean($_REQUEST["view-mode"]); + $order = clean($_REQUEST["order"]); + $start_ts = clean($_REQUEST["ts"]); - $format = $this->dbh->escape_string($_REQUEST['format']); - $orig_guid = sql_bool_to_bool($_REQUEST["orig_guid"]); + $format = clean($_REQUEST['format']); + $orig_guid = clean($_REQUEST["orig_guid"]); if (!$format) $format = 'atom'; @@ -347,11 +313,12 @@ class Handler_Public extends Handler { $owner_id = false; if ($key) { - $result = $this->dbh->query("SELECT owner_uid FROM - ttrss_access_keys WHERE access_key = '$key' AND feed_id = '$feed'"); + $sth = $this->pdo->prepare("SELECT owner_uid FROM + ttrss_access_keys WHERE access_key = ? AND feed_id = ?"); + $sth->execute([$key, $feed]); - if ($this->dbh->num_rows($result) == 1) - $owner_id = $this->dbh->fetch_result($result, 0, "owner_uid"); + if ($row = $sth->fetch()) + $owner_id = $row["owner_uid"]; } if ($owner_id) { @@ -371,7 +338,7 @@ class Handler_Public extends Handler { } function globalUpdateFeeds() { - RPC::updaterandomfeed_real($this->dbh); + RPC::updaterandomfeed_real(); PluginHost::getInstance()->run_hooks(PluginHost::HOOK_UPDATE_TASK, "hook_update_task", false); } @@ -386,23 +353,22 @@ class Handler_Public extends Handler { <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\"> <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">"; - echo stylesheet_tag("css/utility.css"); echo stylesheet_tag("css/default.css"); echo javascript_tag("lib/prototype.js"); echo javascript_tag("lib/scriptaculous/scriptaculous.js?load=effects,controls"); print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/> - </head><body id='sharepopup'>"; + </head><body id='sharepopup' class='ttrss_utility'>"; - $action = $_REQUEST["action"]; + $action = clean($_REQUEST["action"]); if ($_SESSION["uid"]) { if ($action == 'share') { - $title = $this->dbh->escape_string(strip_tags($_REQUEST["title"])); - $url = $this->dbh->escape_string(strip_tags($_REQUEST["url"])); - $content = $this->dbh->escape_string(strip_tags($_REQUEST["content"])); - $labels = $this->dbh->escape_string(strip_tags($_REQUEST["labels"])); + $title = strip_tags(clean($_REQUEST["title"])); + $url = strip_tags(clean($_REQUEST["url"])); + $content = strip_tags(clean($_REQUEST["content"])); + $labels = strip_tags(clean($_REQUEST["labels"])); Article::create_published_article($title, $url, $content, $labels, $_SESSION["uid"]); @@ -412,8 +378,8 @@ class Handler_Public extends Handler { print "</script>"; } else { - $title = htmlspecialchars($_REQUEST["title"]); - $url = htmlspecialchars($_REQUEST["url"]); + $title = htmlspecialchars(clean($_REQUEST["title"])); + $url = htmlspecialchars(clean($_REQUEST["url"])); ?> @@ -500,9 +466,9 @@ class Handler_Public extends Handler { function login() { if (!SINGLE_USER_MODE) { - $login = $this->dbh->escape_string($_POST["login"]); - $password = $_POST["password"]; - $remember_me = $_POST["remember_me"]; + $login = clean($_POST["login"]); + $password = clean($_POST["password"]); + $remember_me = clean($_POST["remember_me"]); if ($remember_me) { session_set_cookie_params(SESSION_COOKIE_LIFETIME); @@ -520,16 +486,17 @@ class Handler_Public extends Handler { } $_SESSION["ref_schema_version"] = get_schema_version(true); - $_SESSION["bw_limit"] = !!$_POST["bw_limit"]; + $_SESSION["bw_limit"] = !!clean($_POST["bw_limit"]); - if ($_POST["profile"]) { + if (clean($_POST["profile"])) { - $profile = $this->dbh->escape_string($_POST["profile"]); + $profile = clean($_POST["profile"]); - $result = $this->dbh->query("SELECT id FROM ttrss_settings_profiles - WHERE id = '$profile' AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_settings_profiles + WHERE id = ? AND owner_uid = ?"); + $sth->execute([$profile, $_SESSION['uid']]); - if ($this->dbh->num_rows($result) != 0) { + if ($sth->fetch()) { $_SESSION["profile"] = $profile; } } @@ -538,8 +505,8 @@ class Handler_Public extends Handler { user_error("Failed login attempt for $login from {$_SERVER['REMOTE_ADDR']}", E_USER_WARNING); } - if ($_REQUEST['return']) { - header("Location: " . $_REQUEST['return']); + if (clean($_REQUEST['return'])) { + header("Location: " . clean($_REQUEST['return'])); } else { header("Location: " . get_self_url_prefix()); } @@ -549,7 +516,7 @@ class Handler_Public extends Handler { /* function subtest() { header("Content-type: text/plain; charset=utf-8"); - $url = $_REQUEST["url"]; + $url = clean($_REQUEST["url"]); print "$url\n\n"; @@ -565,19 +532,20 @@ class Handler_Public extends Handler { if ($_SESSION["uid"]) { - $feed_url = $this->dbh->escape_string(trim($_REQUEST["feed_url"])); + $feed_url = trim(clean($_REQUEST["feed_url"])); header('Content-Type: text/html; charset=utf-8'); print "<html> <head> - <title>Tiny Tiny RSS</title> - <link rel=\"stylesheet\" type=\"text/css\" href=\"css/utility.css\"> - <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/> - <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\"> - <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\"> + <title>Tiny Tiny RSS</title>"; + print stylesheet_tag("css/default.css"); + + print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/> + <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\"> + <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\"> </head> - <body> + <body class='claro ttrss_utility'> <img class=\"floatingLogo\" src=\"images/logo_small.png\" alt=\"Tiny Tiny RSS\"/> <h1>".__("Subscribe to feed...")."</h1><div class='content'>"; @@ -630,10 +598,12 @@ class Handler_Public extends Handler { $tt_uri = get_self_url_prefix(); if ($rc['code'] <= 2){ - $result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE - feed_url = '$feed_url' AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE + feed_url = ? AND owner_uid = ?"); + $sth->execute([$feed_url, $_SESSION['uid']]); + $row = $sth->fetch(); - $feed_id = $this->dbh->fetch_result($result, 0, "id"); + $feed_id = $row["id"]; } else { $feed_id = 0; } @@ -668,43 +638,46 @@ class Handler_Public extends Handler { function forgotpass() { startup_gettext(); - @$hash = $_REQUEST["hash"]; + @$hash = clean($_REQUEST["hash"]); header('Content-Type: text/html; charset=utf-8'); print "<html><head><title>Tiny Tiny RSS</title> <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\"> <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">"; - echo stylesheet_tag("css/utility.css"); + echo stylesheet_tag("lib/dijit/themes/claro/claro.css"); + echo stylesheet_tag("css/default.css"); echo javascript_tag("lib/prototype.js"); print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/> - </head><body id='forgotpass'>"; + </head><body class='claro ttrss_utility'>"; print '<div class="floatingLogo"><img src="images/logo_small.png"></div>'; print "<h1>".__("Password recovery")."</h1>"; print "<div class='content'>"; - @$method = $_POST['method']; + @$method = clean($_POST['method']); if ($hash) { - $login = $this->dbh->escape_string($_REQUEST["login"]); + $login = clean($_REQUEST["login"]); if ($login) { - $result = $this->dbh->query("SELECT id, resetpass_token FROM ttrss_users - WHERE login = '$login'"); + $sth = $this->pdo->prepare("SELECT id, resetpass_token FROM ttrss_users + WHERE login = ?"); + $sth->execute([$login]); - if ($this->dbh->num_rows($result) != 0) { - $id = $this->dbh->fetch_result($result, 0, "id"); - $resetpass_token_full = $this->dbh->fetch_result($result, 0, "resetpass_token"); + if ($row = $sth->fetch()) { + $id = $row["id"]; + $resetpass_token_full = $row["resetpass_token"]; list($timestamp, $resetpass_token) = explode(":", $resetpass_token_full); if ($timestamp && $resetpass_token && $timestamp >= time() - 15*60*60 && $resetpass_token == $hash) { - $result = $this->dbh->query("UPDATE ttrss_users SET resetpass_token = NULL - WHERE id = $id"); + $sth = $this->pdo->prepare("UPDATE ttrss_users SET resetpass_token = NULL + WHERE id = ?"); + $sth->execute([$id]); Pref_Users::resetUserPassword($id, true); @@ -733,17 +706,17 @@ class Handler_Public extends Handler { print "<fieldset>"; print "<label>".__("Login:")."</label>"; - print "<input type='text' name='login' value='' required>"; + print "<input class='input input-text' type='text' name='login' value='' required>"; print "</fieldset>"; print "<fieldset>"; print "<label>".__("Email:")."</label>"; - print "<input type='email' name='email' value='' required>"; + print "<input class='input input-text' type='email' name='email' value='' required>"; print "</fieldset>"; print "<fieldset>"; print "<label>".__("How much is two plus two:")."</label>"; - print "<input type='text' name='test' value='' required>"; + print "<input class='input input-text' type='text' name='test' value='' required>"; print "</fieldset>"; print "<p/>"; @@ -752,9 +725,9 @@ class Handler_Public extends Handler { print "</form>"; } else if ($method == 'do') { - $login = $this->dbh->escape_string($_POST["login"]); - $email = $this->dbh->escape_string($_POST["email"]); - $test = $this->dbh->escape_string($_POST["test"]); + $login = clean($_POST["login"]); + $email = clean($_POST["email"]); + $test = clean($_POST["test"]); if (($test != 4 && $test != 'four') || !$email || !$login) { print_error(__('Some of the required form parameters are missing or incorrect.')); @@ -768,11 +741,12 @@ class Handler_Public extends Handler { print_notice("Password reset instructions are being sent to your email address."); - $result = $this->dbh->query("SELECT id FROM ttrss_users - WHERE login = '$login' AND email = '$email'"); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_users + WHERE login = ? AND email = ?"); + $sth->execute([$login, $email]); - if ($this->dbh->num_rows($result) != 0) { - $id = $this->dbh->fetch_result($result, 0, "id"); + if ($row = $sth->fetch()) { + $id = $row["id"]; if ($id) { $resetpass_token = sha1(get_random_bytes(128)); @@ -803,11 +777,13 @@ class Handler_Public extends Handler { if (!$rc) print_error($mail->ErrorInfo); - $resetpass_token_full = $this->dbh->escape_string(time() . ":" . $resetpass_token); + $resetpass_token_full = time() . ":" . $resetpass_token; + + $sth = $this->pdo->prepare("UPDATE ttrss_users + SET resetpass_token = ? + WHERE login = ? AND email = ?"); - $result = $this->dbh->query("UPDATE ttrss_users - SET resetpass_token = '$resetpass_token_full' - WHERE login = '$login' AND email = '$email'"); + $sth->execute([$resetpass_token_full, $login, $email]); //Pref_Users::resetUserPassword($id, false); @@ -854,7 +830,7 @@ class Handler_Public extends Handler { <head> <title>Database Updater</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> - <link rel="stylesheet" type="text/css" href="css/utility.css"/> + <link rel="stylesheet" type="text/css" href="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\"> </head> @@ -862,7 +838,7 @@ class Handler_Public extends Handler { span.ok { color : #009000; font-weight : bold; } span.err { color : #ff0000; font-weight : bold; } </style> - <body> + <body class="claro ttrss_utility"> <script type='text/javascript'> function confirmOP() { return confirm("Update the database?"); @@ -876,8 +852,8 @@ class Handler_Public extends Handler { <div class="content"> <?php - @$op = $_REQUEST["subop"]; - $updater = new DbUpdater(Db::get(), DB_TYPE, SCHEMA_VERSION); + @$op = clean($_REQUEST["subop"]); + $updater = new DbUpdater(Db::pdo(), DB_TYPE, SCHEMA_VERSION); if ($op == "performupdate") { if ($updater->isUpdateRequired()) { @@ -992,8 +968,8 @@ class Handler_Public extends Handler { public function pluginhandler() { $host = new PluginHost(); - $plugin = basename($_REQUEST["plugin"]); - $method = $_REQUEST["pmethod"]; + $plugin = basename(clean($_REQUEST["plugin"])); + $method = clean($_REQUEST["pmethod"]); $host->load($plugin, PluginHost::KIND_USER, 0); $host->load_data(); diff --git a/classes/labels.php b/classes/labels.php index c46f70c94..4061de57e 100644 --- a/classes/labels.php +++ b/classes/labels.php @@ -10,24 +10,28 @@ class Labels } static function find_id($label, $owner_uid) { - $result = db_query( - "SELECT id FROM ttrss_labels2 WHERE caption = '$label' - AND owner_uid = '$owner_uid' LIMIT 1"); + $pdo = Db::pdo(); - if (db_num_rows($result) == 1) { - return db_fetch_result($result, 0, "id"); + $sth = $pdo->prepare("SELECT id FROM ttrss_labels2 WHERE caption = ? + AND owner_uid = ? LIMIT 1"); + $sth->execute([$label, $owner_uid]); + + if ($row = $sth->fetch()) { + return $row['id']; } else { return 0; } } static function find_caption($label, $owner_uid) { - $result = db_query( - "SELECT caption FROM ttrss_labels2 WHERE id = '$label' - AND owner_uid = '$owner_uid' LIMIT 1"); + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT caption FROM ttrss_labels2 WHERE id = ? + AND owner_uid = ? LIMIT 1"); + $sth->execute([$label, $owner_uid]); - if (db_num_rows($result) == 1) { - return db_fetch_result($result, 0, "caption"); + if ($row = $sth->fetch()) { + return $row['caption']; } else { return ""; } @@ -36,9 +40,13 @@ class Labels static function get_all_labels($owner_uid) { $rv = array(); - $result = db_query("SELECT id, fg_color, bg_color, caption FROM ttrss_labels2 WHERE owner_uid = '$owner_uid' ORDER BY caption"); + $pdo = Db::pdo(); - while ($line = db_fetch_assoc($result)) { + $sth = $pdo->prepare("SELECT id, fg_color, bg_color, caption FROM ttrss_labels2 + WHERE owner_uid = ? ORDER BY caption"); + $sth->execute([$owner_uid]); + + while ($line = $sth->fetch()) { array_push($rv, $line); } @@ -46,6 +54,7 @@ class Labels } static function update_cache($owner_uid, $id, $labels = false, $force = false) { + $pdo = Db::pdo(); if ($force) Labels::clear_cache($id); @@ -53,17 +62,21 @@ class Labels if (!$labels) $labels = Article::get_article_labels($id); - $labels = db_escape_string(json_encode($labels)); + $labels = json_encode($labels); - db_query("UPDATE ttrss_user_entries SET - label_cache = '$labels' WHERE ref_id = '$id' AND owner_uid = '$owner_uid'"); + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET + label_cache = ? WHERE ref_id = ? AND owner_uid = ?"); + $sth->execute([$labels, $id, $owner_uid]); } static function clear_cache($id) { - db_query("UPDATE ttrss_user_entries SET - label_cache = '' WHERE ref_id = '$id'"); + $pdo = Db::pdo(); + + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET + label_cache = '' WHERE ref_id = ?"); + $sth->execute([$id]); } @@ -73,11 +86,14 @@ class Labels if (!$label_id) return; - db_query( - "DELETE FROM ttrss_user_labels2 + $pdo = Db::pdo(); + + $sth = $pdo->prepare("DELETE FROM ttrss_user_labels2 WHERE - label_id = '$label_id' AND - article_id = '$id'"); + label_id = ? AND + article_id = ?"); + + $sth->execute([$label_id, $id]); Labels::clear_cache($id); } @@ -88,18 +104,23 @@ class Labels if (!$label_id) return; - $result = db_query( - "SELECT + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT article_id FROM ttrss_labels2, ttrss_user_labels2 WHERE label_id = id AND - label_id = '$label_id' AND - article_id = '$id' AND owner_uid = '$owner_uid' + label_id = ? AND + article_id = ? AND owner_uid = ? LIMIT 1"); - if (db_num_rows($result) == 0) { - db_query("INSERT INTO ttrss_user_labels2 - (label_id, article_id) VALUES ('$label_id', '$id')"); + $sth->execute([$label_id, $id, $owner_uid]); + + if (!$sth->fetch()) { + $sth = $pdo->prepare("INSERT INTO ttrss_user_labels2 + (label_id, article_id) VALUES (?, ?)"); + + $sth->execute([$label_id, $id]); } Labels::clear_cache($id); @@ -109,53 +130,75 @@ class Labels static function remove($id, $owner_uid) { if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - db_query("BEGIN"); + $pdo = Db::pdo(); + $tr_in_progress = false; + + try { + $pdo->beginTransaction(); + } catch (Exception $e) { + $tr_in_progress = true; + } - $result = db_query("SELECT caption FROM ttrss_labels2 - WHERE id = '$id'"); + $sth = $pdo->prepare("SELECT caption FROM ttrss_labels2 + WHERE id = ?"); + $sth->execute([$id]); - $caption = db_fetch_result($result, 0, "caption"); + $row = $sth->fetch(); + $caption = $row['caption']; - $result = db_query("DELETE FROM ttrss_labels2 WHERE id = '$id' - AND owner_uid = " . $owner_uid); + $sth = $pdo->prepare("DELETE FROM ttrss_labels2 WHERE id = ? + AND owner_uid = ?"); + $sth->execute([$id, $owner_uid]); - if (db_affected_rows($result) != 0 && $caption) { + if ($sth->rowCount() != 0 && $caption) { /* Remove access key for the label */ $ext_id = LABEL_BASE_INDEX - 1 - $id; - db_query("DELETE FROM ttrss_access_keys WHERE - feed_id = '$ext_id' AND owner_uid = $owner_uid"); + $sth = $pdo->prepare("DELETE FROM ttrss_access_keys WHERE + feed_id = ? AND owner_uid = ?"); + $sth->execute([$ext_id, $owner_uid]); /* Remove cached data */ - db_query("UPDATE ttrss_user_entries SET label_cache = '' - WHERE label_cache LIKE '%$caption%' AND owner_uid = " . $owner_uid); + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET label_cache = '' + WHERE label_cache LIKE ? AND owner_uid = ?"); + $sth->execute(["%$caption%", $owner_uid]); } - db_query("COMMIT"); + if (!$tr_in_progress) $pdo->commit(); } static function create($caption, $fg_color = '', $bg_color = '', $owner_uid = false) { if (!$owner_uid) $owner_uid = $_SESSION['uid']; - db_query("BEGIN"); + $pdo = Db::pdo(); + + $tr_in_progress = false; + + try { + $pdo->beginTransaction(); + } catch (Exception $e) { + $tr_in_progress = true; + } + + $sth = $pdo->prepare("SELECT id FROM ttrss_labels2 + WHERE caption = ? AND owner_uid = ?"); + $sth->execute([$caption, $owner_uid]); - $result = db_query("SELECT id FROM ttrss_labels2 - WHERE caption = '$caption' AND owner_uid = $owner_uid"); + if (!$sth->fetch()) { + $sth = $pdo->prepare("INSERT INTO ttrss_labels2 + (caption,owner_uid,fg_color,bg_color) VALUES (?, ?, ?, ?)"); - if (db_num_rows($result) == 0) { - $result = db_query( - "INSERT INTO ttrss_labels2 (caption,owner_uid,fg_color,bg_color) - VALUES ('$caption', '$owner_uid', '$fg_color', '$bg_color')"); + $sth->execute([$caption, $owner_uid, $fg_color, $bg_color]); - $result = db_affected_rows($result) != 0; + $result = $sth->rowCount(); } - db_query("COMMIT"); + if (!$tr_in_progress) $pdo->commit(); return $result; } diff --git a/classes/logger/sql.php b/classes/logger/sql.php index bd192ae78..120584014 100644 --- a/classes/logger/sql.php +++ b/classes/logger/sql.php @@ -2,22 +2,25 @@ class Logger_SQL { function log_error($errno, $errstr, $file, $line, $context) { - if (Db::get() && get_schema_version() > 117) { + + $pdo = Db::pdo(); + + if ($pdo && get_schema_version() > 117) { - $errno = Db::get()->escape_string($errno); - $errstr = Db::get()->escape_string($errstr); - $file = Db::get()->escape_string($file); - $line = Db::get()->escape_string($line); - $context = DB::get()->escape_string($context); + try { + $pdo->rollBack(); + } catch (Exception $e) { + // + } - $owner_uid = $_SESSION["uid"] ? $_SESSION["uid"] : "NULL"; + $owner_uid = $_SESSION["uid"] ? $_SESSION["uid"] : null; - $result = Db::get()->query( - "INSERT INTO ttrss_error_log + $sth = $pdo->prepare("INSERT INTO ttrss_error_log (errno, errstr, filename, lineno, context, owner_uid, created_at) VALUES - ($errno, '$errstr', '$file', '$line', '$context', $owner_uid, NOW())"); + (?, ?, ?, ?, ?, ?, NOW())"); + $sth->execute([$errno, $errstr, $file, $line, $context, $owner_uid]); - return Db::get()->affected_rows($result) != 0; + return $sth->rowCount(); } return false; diff --git a/classes/opml.php b/classes/opml.php index 2c2c2ac0e..ac9e30f3d 100644 --- a/classes/opml.php +++ b/classes/opml.php @@ -14,7 +14,10 @@ class Opml extends Handler_Protected { $show_settings = $_REQUEST["settings"]; $owner_uid = $_SESSION["uid"]; - return $this->opml_export($output_name, $owner_uid, false, ($show_settings == 1)); + + $rc = $this->opml_export($output_name, $owner_uid, false, ($show_settings == 1)); + + return $rc; } function import() { @@ -24,17 +27,18 @@ class Opml extends Handler_Protected { print "<html> <head> - <link rel=\"stylesheet\" href=\"css/utility.css\" type=\"text/css\"> + ".stylesheet_tag("css/default.css")." <title>".__("OPML Utility")."</title> <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/> </head> - <body> + <body class='claro ttrss_utility'> <div class=\"floatingLogo\"><img src=\"images/logo_small.png\"></div> <h1>".__('OPML Utility')."</h1><div class='content'>"; add_feed_category("Imported feeds"); $this->opml_notice(__("Importing OPML...")); + $this->opml_import($owner_uid); print "<br><form method=\"GET\" action=\"prefs.php\"> @@ -50,13 +54,7 @@ class Opml extends Handler_Protected { private function opml_export_category($owner_uid, $cat_id, $hide_private_feeds=false) { - if ($cat_id) { - $cat_qpart = "parent_cat = '$cat_id'"; - $feed_cat_qpart = "cat_id = '$cat_id'"; - } else { - $cat_qpart = "parent_cat IS NULL"; - $feed_cat_qpart = "cat_id IS NULL"; - } + $cat_id = (int) $cat_id; if ($hide_private_feeds) $hide_qpart = "(private IS false AND auth_login = '' AND auth_pass = '')"; @@ -66,27 +64,34 @@ class Opml extends Handler_Protected { $out = ""; if ($cat_id) { - $result = $this->dbh->query("SELECT title FROM ttrss_feed_categories WHERE id = '$cat_id' - AND owner_uid = '$owner_uid'"); - $cat_title = htmlspecialchars($this->dbh->fetch_result($result, 0, "title")); + $sth = $this->pdo->prepare("SELECT title FROM ttrss_feed_categories WHERE id = ? + AND owner_uid = ?"); + $sth->execute([$cat_id, $owner_uid]); + $row = $sth->fetch(); + $cat_title = htmlspecialchars($row['title']); } if ($cat_title) $out .= "<outline text=\"$cat_title\">\n"; - $result = $this->dbh->query("SELECT id,title + $sth = $this->pdo->prepare("SELECT id,title FROM ttrss_feed_categories WHERE - $cat_qpart AND owner_uid = '$owner_uid' ORDER BY order_id, title"); + (parent_cat = :cat OR (:cat = 0 AND parent_cat IS NULL)) AND + owner_uid = :uid ORDER BY order_id, title"); - while ($line = $this->dbh->fetch_assoc($result)) { - $title = htmlspecialchars($line["title"]); + $sth->execute([':cat' => $cat_id, ':uid' => $owner_uid]); + + while ($line = $sth->fetch()) { $out .= $this->opml_export_category($owner_uid, $line["id"], $hide_private_feeds); } - $feeds_result = $this->dbh->query("select title, feed_url, site_url - from ttrss_feeds where $feed_cat_qpart AND owner_uid = '$owner_uid' AND $hide_qpart - order by order_id, title"); + $fsth = $this->pdo->prepare("select title, feed_url, site_url + FROM ttrss_feeds WHERE + (cat_id = :cat OR (:cat = 0 AND cat_id IS NULL)) AND owner_uid = :uid AND $hide_qpart + ORDER BY order_id, title"); + + $fsth->execute([':cat' => $cat_id, ':uid' => $owner_uid]); - while ($fline = $this->dbh->fetch_assoc($feeds_result)) { + while ($fline = $fsth->fetch()) { $title = htmlspecialchars($fline["title"]); $url = htmlspecialchars($fline["feed_url"]); $site_url = htmlspecialchars($fline["site_url"]); @@ -124,17 +129,18 @@ class Opml extends Handler_Protected { </head>"; $out .= "<body>"; - $out .= $this->opml_export_category($owner_uid, false, $hide_private_feeds); + $out .= $this->opml_export_category($owner_uid, 0, $hide_private_feeds); # export tt-rss settings if ($include_settings) { $out .= "<outline text=\"tt-rss-prefs\" schema-version=\"".SCHEMA_VERSION."\">"; - $result = $this->dbh->query("SELECT pref_name, value FROM ttrss_user_prefs WHERE - profile IS NULL AND owner_uid = " . $_SESSION["uid"] . " ORDER BY pref_name"); + $sth = $this->pdo->prepare("SELECT pref_name, value FROM ttrss_user_prefs WHERE + profile IS NULL AND owner_uid = ? ORDER BY pref_name"); + $sth->execute([$owner_uid]); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { $name = $line["pref_name"]; $value = htmlspecialchars($line["value"]); @@ -145,10 +151,11 @@ class Opml extends Handler_Protected { $out .= "<outline text=\"tt-rss-labels\" schema-version=\"".SCHEMA_VERSION."\">"; - $result = $this->dbh->query("SELECT * FROM ttrss_labels2 WHERE - owner_uid = " . $_SESSION['uid']); + $sth = $this->pdo->prepare("SELECT * FROM ttrss_labels2 WHERE + owner_uid = ?"); + $sth->execute([$owner_uid]); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { $name = htmlspecialchars($line['caption']); $fg_color = htmlspecialchars($line['fg_color']); $bg_color = htmlspecialchars($line['bg_color']); @@ -161,25 +168,23 @@ class Opml extends Handler_Protected { $out .= "<outline text=\"tt-rss-filters\" schema-version=\"".SCHEMA_VERSION."\">"; - $result = $this->dbh->query("SELECT * FROM ttrss_filters2 - WHERE owner_uid = ".$_SESSION["uid"]." ORDER BY id"); - - while ($line = $this->dbh->fetch_assoc($result)) { - foreach (array('enabled', 'match_any_rule', 'inverse') as $b) { - $line[$b] = sql_bool_to_bool($line[$b]); - } + $sth = $this->pdo->prepare("SELECT * FROM ttrss_filters2 + WHERE owner_uid = ? ORDER BY id"); + $sth->execute([$owner_uid]); + while ($line = $sth->fetch()) { $line["rules"] = array(); $line["actions"] = array(); - $tmp_result = $this->dbh->query("SELECT * FROM ttrss_filters2_rules - WHERE filter_id = ".$line["id"]); + $tmph = $this->pdo->prepare("SELECT * FROM ttrss_filters2_rules + WHERE filter_id = ?"); + $tmph->execute([$line['id']]); - while ($tmp_line = $this->dbh->fetch_assoc($tmp_result)) { + while ($tmp_line = $tmph->fetch()) { unset($tmp_line["id"]); unset($tmp_line["filter_id"]); - $cat_filter = sql_bool_to_bool($tmp_line["cat_filter"]); + $cat_filter = $tmp_line["cat_filter"]; if (!$tmp_line["match_on"]) { if ($cat_filter && $tmp_line["cat_id"] || $tmp_line["feed_id"]) { @@ -213,19 +218,17 @@ class Opml extends Handler_Protected { unset($tmp_line["match_on"]); } - $tmp_line["cat_filter"] = sql_bool_to_bool($tmp_line["cat_filter"]); - $tmp_line["inverse"] = sql_bool_to_bool($tmp_line["inverse"]); - unset($tmp_line["feed_id"]); unset($tmp_line["cat_id"]); array_push($line["rules"], $tmp_line); } - $tmp_result = $this->dbh->query("SELECT * FROM ttrss_filters2_actions - WHERE filter_id = ".$line["id"]); + $tmph = $this->pdo->prepare("SELECT * FROM ttrss_filters2_actions + WHERE filter_id = ?"); + $tmph->execute([$line['id']]); - while ($tmp_line = $this->dbh->fetch_assoc($tmp_result)) { + while ($tmp_line = $tmph->fetch()) { unset($tmp_line["id"]); unset($tmp_line["filter_id"]); @@ -278,29 +281,30 @@ class Opml extends Handler_Protected { private function opml_import_feed($node, $cat_id, $owner_uid) { $attrs = $node->attributes; - $feed_title = $this->dbh->escape_string(mb_substr($attrs->getNamedItem('text')->nodeValue, 0, 250)); - if (!$feed_title) $feed_title = $this->dbh->escape_string(mb_substr($attrs->getNamedItem('title')->nodeValue, 0, 250)); + $feed_title = mb_substr($attrs->getNamedItem('text')->nodeValue, 0, 250); + if (!$feed_title) $feed_title = mb_substr($attrs->getNamedItem('title')->nodeValue, 0, 250); - $feed_url = $this->dbh->escape_string($attrs->getNamedItem('xmlUrl')->nodeValue); - if (!$feed_url) $feed_url = $this->dbh->escape_string($attrs->getNamedItem('xmlURL')->nodeValue); + $feed_url = $attrs->getNamedItem('xmlUrl')->nodeValue; + if (!$feed_url) $feed_url = $attrs->getNamedItem('xmlURL')->nodeValue; - $site_url = $this->dbh->escape_string(mb_substr($attrs->getNamedItem('htmlUrl')->nodeValue, 0, 250)); + $site_url = mb_substr($attrs->getNamedItem('htmlUrl')->nodeValue, 0, 250); if ($feed_url && $feed_title) { - $result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE - feed_url = '$feed_url' AND owner_uid = '$owner_uid'"); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE + feed_url = ? AND owner_uid = ?"); + $sth->execute([$feed_url, $owner_uid]); - if ($this->dbh->num_rows($result) == 0) { + if (!$sth->fetch()) { #$this->opml_notice("[FEED] [$feed_title/$feed_url] dst_CAT=$cat_id"); $this->opml_notice(T_sprintf("Adding feed: %s", $feed_title)); - if (!$cat_id) $cat_id = 'NULL'; + if (!$cat_id) $cat_id = null; - $query = "INSERT INTO ttrss_feeds + $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds (title, feed_url, owner_uid, cat_id, site_url, order_id) VALUES - ('$feed_title', '$feed_url', '$owner_uid', - $cat_id, '$site_url', 0)"; - $this->dbh->query($query); + (?, ?, ?, ?, ?, 0)"); + + $sth->execute([$feed_title, $feed_url, $owner_uid, $cat_id, $site_url]); } else { $this->opml_notice(T_sprintf("Duplicate feed: %s", $feed_title)); @@ -310,11 +314,11 @@ class Opml extends Handler_Protected { private function opml_import_label($node, $owner_uid) { $attrs = $node->attributes; - $label_name = $this->dbh->escape_string($attrs->getNamedItem('label-name')->nodeValue); + $label_name = $attrs->getNamedItem('label-name')->nodeValue; if ($label_name) { - $fg_color = $this->dbh->escape_string($attrs->getNamedItem('label-fg-color')->nodeValue); - $bg_color = $this->dbh->escape_string($attrs->getNamedItem('label-bg-color')->nodeValue); + $fg_color = $attrs->getNamedItem('label-fg-color')->nodeValue; + $bg_color = $attrs->getNamedItem('label-bg-color')->nodeValue; if (!Labels::find_id($label_name, $_SESSION['uid'])) { $this->opml_notice(T_sprintf("Adding label %s", htmlspecialchars($label_name))); @@ -327,10 +331,10 @@ class Opml extends Handler_Protected { private function opml_import_preference($node) { $attrs = $node->attributes; - $pref_name = $this->dbh->escape_string($attrs->getNamedItem('pref-name')->nodeValue); + $pref_name = $attrs->getNamedItem('pref-name')->nodeValue; if ($pref_name) { - $pref_value = $this->dbh->escape_string($attrs->getNamedItem('value')->nodeValue); + $pref_value = $attrs->getNamedItem('value')->nodeValue; $this->opml_notice(T_sprintf("Setting preference key %s to %s", $pref_name, $pref_value)); @@ -342,7 +346,7 @@ class Opml extends Handler_Protected { private function opml_import_filter($node) { $attrs = $node->attributes; - $filter_type = $this->dbh->escape_string($attrs->getNamedItem('filter-type')->nodeValue); + $filter_type = $attrs->getNamedItem('filter-type')->nodeValue; if ($filter_type == '2') { $filter = json_decode($node->nodeValue, true); @@ -351,24 +355,28 @@ class Opml extends Handler_Protected { $match_any_rule = bool_to_sql_bool($filter["match_any_rule"]); $enabled = bool_to_sql_bool($filter["enabled"]); $inverse = bool_to_sql_bool($filter["inverse"]); - $title = db_escape_string($filter["title"]); + $title = $filter["title"]; - $this->dbh->query("BEGIN"); + //print "F: $title, $inverse, $enabled, $match_any_rule"; - $this->dbh->query("INSERT INTO ttrss_filters2 (match_any_rule,enabled,inverse,title,owner_uid) - VALUES ($match_any_rule, $enabled, $inverse, '$title', - ".$_SESSION["uid"].")"); + $sth = $this->pdo->prepare("INSERT INTO ttrss_filters2 (match_any_rule,enabled,inverse,title,owner_uid) + VALUES (?, ?, ?, ?, ?)"); - $result = $this->dbh->query("SELECT MAX(id) AS id FROM ttrss_filters2 WHERE - owner_uid = ".$_SESSION["uid"]); - $filter_id = $this->dbh->fetch_result($result, 0, "id"); + $sth->execute([$match_any_rule, $enabled, $inverse, $title, $_SESSION['uid']]); + + $sth = $this->pdo->prepare("SELECT MAX(id) AS id FROM ttrss_filters2 WHERE + owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + + $row = $sth->fetch(); + $filter_id = $row['id']; if ($filter_id) { $this->opml_notice(T_sprintf("Adding filter...")); foreach ($filter["rules"] as $rule) { - $feed_id = "NULL"; - $cat_id = "NULL"; + $feed_id = null; + $cat_id = null; if ($rule["match"]) { @@ -384,70 +392,86 @@ class Opml extends Handler_Protected { $match_id = false; if (!$is_cat) { - $tmp_result = $this->dbh->query("SELECT id FROM ttrss_feeds - WHERE title = '" . $this->dbh->escape_string($name) . "' AND owner_uid = " . $_SESSION["uid"]); - if ($this->dbh->num_rows($tmp_result) > 0) { - $match_id = $this->dbh->fetch_result($tmp_result, 0, "id"); + $tsth = $this->pdo->prepare("SELECT id FROM ttrss_feeds + WHERE title = ? AND owner_uid = ?"); + + $tsth->execute([$name, $_SESSION['uid']]); + + if ($row = $tsth->fetch()) { + $match_id = $row['id']; } } else { - $tmp_result = $this->dbh->query("SELECT id FROM ttrss_feed_categories - WHERE title = '" . $this->dbh->escape_string($name) . "' AND owner_uid = " . $_SESSION["uid"]); + $tsth = $this->pdo->prepare("SELECT id FROM ttrss_feed_categories + WHERE title = ? AND owner_uid = ?"); + $tsth->execute([$name, $_SESSION['uid']]); - if ($this->dbh->num_rows($tmp_result) > 0) { - $match_id = 'CAT:' . $this->dbh->fetch_result($tmp_result, 0, "id"); - } + if ($row = $tsth->fetch()) { + $match_id = $row['id']; + } } if ($match_id) array_push($match_on, $match_id); } } - $reg_exp = $this->dbh->escape_string($rule["reg_exp"]); + $reg_exp = $rule["reg_exp"]; $filter_type = (int)$rule["filter_type"]; $inverse = bool_to_sql_bool($rule["inverse"]); - $match_on = $this->dbh->escape_string(json_encode($match_on)); + $match_on = json_encode($match_on); - $this->dbh->query("INSERT INTO ttrss_filters2_rules (feed_id,cat_id,match_on,filter_id,filter_type,reg_exp,cat_filter,inverse) - VALUES (NULL, NULL, '$match_on',$filter_id, $filter_type, '$reg_exp', false, $inverse)"); + $usth = $this->pdo->prepare("INSERT INTO ttrss_filters2_rules + (feed_id,cat_id,match_on,filter_id,filter_type,reg_exp,cat_filter,inverse) + VALUES + (NULL, NULL, ?, ?, ?, ?, false, ?)"); + $usth->execute([$match_on, $filter_id, $filter_type, $reg_exp, $inverse]); } else { if (!$rule["cat_filter"]) { - $tmp_result = $this->dbh->query("SELECT id FROM ttrss_feeds - WHERE title = '" . $this->dbh->escape_string($rule["feed"]) . "' AND owner_uid = " . $_SESSION["uid"]); - if ($this->dbh->num_rows($tmp_result) > 0) { - $feed_id = $this->dbh->fetch_result($tmp_result, 0, "id"); + $tsth = $this->pdo->prepare("SELECT id FROM ttrss_feeds + WHERE title = ? AND owner_uid = ?"); + + $tsth->execute([$rule['feed'], $_SESSION['uid']]); + + if ($row = $tsth->fetch()) { + $feed_id = $row['id']; } } else { - $tmp_result = $this->dbh->query("SELECT id FROM ttrss_feed_categories - WHERE title = '" . $this->dbh->escape_string($rule["feed"]) . "' AND owner_uid = " . $_SESSION["uid"]); + $tsth = $this->pdo->prepare("SELECT id FROM ttrss_feed_categories + WHERE title = ? AND owner_uid = ?"); - if ($this->dbh->num_rows($tmp_result) > 0) { - $cat_id = $this->dbh->fetch_result($tmp_result, 0, "id"); - } + $tsth->execute([$rule['feed'], $_SESSION['uid']]); + + if ($row = $tsth->fetch()) { + $feed_id = $row['id']; + } } $cat_filter = bool_to_sql_bool($rule["cat_filter"]); - $reg_exp = $this->dbh->escape_string($rule["reg_exp"]); + $reg_exp = $rule["reg_exp"]; $filter_type = (int)$rule["filter_type"]; $inverse = bool_to_sql_bool($rule["inverse"]); - $this->dbh->query("INSERT INTO ttrss_filters2_rules (feed_id,cat_id,filter_id,filter_type,reg_exp,cat_filter,inverse) - VALUES ($feed_id, $cat_id, $filter_id, $filter_type, '$reg_exp', $cat_filter,$inverse)"); + $usth = $this->pdo->prepare("INSERT INTO ttrss_filters2_rules + (feed_id,cat_id,filter_id,filter_type,reg_exp,cat_filter,inverse) + VALUES + (?, ?, ?, ?, ?, ?, ?)"); + $usth->execute([$feed_id, $cat_id, $filter_id, $filter_type, $reg_exp, $cat_filter, $inverse]); } } foreach ($filter["actions"] as $action) { $action_id = (int)$action["action_id"]; - $action_param = $this->dbh->escape_string($action["action_param"]); + $action_param = $action["action_param"]; - $this->dbh->query("INSERT INTO ttrss_filters2_actions (filter_id,action_id,action_param) - VALUES ($filter_id, $action_id, '$action_param')"); + $usth = $this->pdo->prepare("INSERT INTO ttrss_filters2_actions + (filter_id,action_id,action_param) + VALUES + (?, ?, ?)"); + $usth->execute([$filter_id, $action_id, $action_param]); } } - - $this->dbh->query("COMMIT"); } } } @@ -456,19 +480,19 @@ class Opml extends Handler_Protected { $default_cat_id = (int) $this->get_feed_category('Imported feeds', false); if ($root_node) { - $cat_title = $this->dbh->escape_string(mb_substr($root_node->attributes->getNamedItem('text')->nodeValue, 0, 250)); + $cat_title = mb_substr($root_node->attributes->getNamedItem('text')->nodeValue, 0, 250); if (!$cat_title) - $cat_title = $this->dbh->escape_string(mb_substr($root_node->attributes->getNamedItem('title')->nodeValue, 0, 250)); + $cat_title = mb_substr($root_node->attributes->getNamedItem('title')->nodeValue, 0, 250); if (!in_array($cat_title, array("tt-rss-filters", "tt-rss-labels", "tt-rss-prefs"))) { $cat_id = $this->get_feed_category($cat_title, $parent_id); - $this->dbh->query("BEGIN"); + if ($cat_id === false) { add_feed_category($cat_title, $parent_id); $cat_id = $this->get_feed_category($cat_title, $parent_id); } - $this->dbh->query("COMMIT"); + } else { $cat_id = 0; } @@ -488,12 +512,12 @@ class Opml extends Handler_Protected { foreach ($outlines as $node) { if ($node->hasAttributes() && strtolower($node->tagName) == "outline") { $attrs = $node->attributes; - $node_cat_title = $this->dbh->escape_string($attrs->getNamedItem('text')->nodeValue); + $node_cat_title = $attrs->getNamedItem('text')->nodeValue; if (!$node_cat_title) - $node_cat_title = $this->dbh->escape_string($attrs->getNamedItem('title')->nodeValue); + $node_cat_title = $attrs->getNamedItem('title')->nodeValue; - $node_feed_url = $this->dbh->escape_string($attrs->getNamedItem('xmlUrl')->nodeValue); + $node_feed_url = $attrs->getNamedItem('xmlUrl')->nodeValue; if ($node_cat_title && !$node_feed_url) { $this->opml_import_category($doc, $node, $owner_uid, $cat_id); @@ -561,7 +585,9 @@ class Opml extends Handler_Protected { } if ($doc) { + $this->pdo->beginTransaction(); $this->opml_import_category($doc, false, $owner_uid, false); + $this->pdo->commit(); } else { print_error(__('Error while parsing document.')); } @@ -581,22 +607,20 @@ class Opml extends Handler_Protected { } function get_feed_category($feed_cat, $parent_cat_id = false) { - if ($parent_cat_id) { - $parent_qpart = "parent_cat = '$parent_cat_id'"; - $parent_insert = "'$parent_cat_id'"; - } else { - $parent_qpart = "parent_cat IS NULL"; - $parent_insert = "NULL"; - } - $result = db_query( - "SELECT id FROM ttrss_feed_categories - WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]); + $parent_cat_id = (int) $parent_cat_id; - if (db_num_rows($result) == 0) { - return false; + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feed_categories + WHERE title = :title + AND (parent_cat = :parent OR (:parent = 0 AND parent_cat IS NULL)) + AND owner_uid = :uid"); + + $sth->execute([':title' => $feed_cat, ':parent' => $parent_cat_id, ':uid' => $_SESSION['uid']]); + + if ($row = $sth->fetch()) { + return $row['id']; } else { - return db_fetch_result($result, 0, "id"); + return false; } } diff --git a/classes/plugin.php b/classes/plugin.php index fcf329ca1..b90c603b7 100644 --- a/classes/plugin.php +++ b/classes/plugin.php @@ -2,11 +2,19 @@ abstract class Plugin { const API_VERSION_COMPAT = 1; + /** @var PDO */ + protected $pdo; + + /* @var PluginHost $host */ abstract function init($host); abstract function about(); // return array(1.0, "plugin", "No description", "No author", false); + function __construct() { + $this->pdo = Db::pdo(); + } + function flags() { /* associative array, possible keys: needs_curl = boolean diff --git a/classes/pluginhandler.php b/classes/pluginhandler.php index 1c9e7aef6..d10343e09 100644 --- a/classes/pluginhandler.php +++ b/classes/pluginhandler.php @@ -5,7 +5,7 @@ class PluginHandler extends Handler_Protected { } function catchall($method) { - $plugin = PluginHost::getInstance()->get_plugin($_REQUEST["plugin"]); + $plugin = PluginHost::getInstance()->get_plugin(clean($_REQUEST["plugin"])); if ($plugin) { if (method_exists($plugin, $method)) { diff --git a/classes/pluginhost.php b/classes/pluginhost.php index f56343c5f..4224893c2 100644 --- a/classes/pluginhost.php +++ b/classes/pluginhost.php @@ -1,6 +1,6 @@ <?php class PluginHost { - private $dbh; + private $pdo; private $hooks = array(); private $plugins = array(); private $handlers = array(); @@ -63,7 +63,7 @@ class PluginHost { const KIND_USER = 3; function __construct() { - $this->dbh = Db::get(); + $this->pdo = Db::pdo(); $this->storage = array(); } @@ -90,9 +90,13 @@ class PluginHost { } function get_dbh() { - return $this->dbh; + return Db::get(); } + function get_pdo() { + return $this->pdo; + } + function get_plugin_names() { $names = array(); @@ -276,8 +280,6 @@ class PluginHost { } else { return false; } - - return false; } function get_commands() { @@ -295,10 +297,11 @@ class PluginHost { function load_data() { if ($this->owner_uid) { - $result = $this->dbh->query("SELECT name, content FROM ttrss_plugin_storage - WHERE owner_uid = '".$this->owner_uid."'"); + $sth = $this->pdo->prepare("SELECT name, content FROM ttrss_plugin_storage + WHERE owner_uid = ?"); + $sth->execute([$this->owner_uid]); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { $this->storage[$line["name"]] = unserialize($line["content"]); } } @@ -306,30 +309,30 @@ class PluginHost { private function save_data($plugin) { if ($this->owner_uid) { - $plugin = $this->dbh->escape_string($plugin); - - $this->dbh->query("BEGIN"); + $this->pdo->beginTransaction(); - $result = $this->dbh->query("SELECT id FROM ttrss_plugin_storage WHERE - owner_uid= '".$this->owner_uid."' AND name = '$plugin'"); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_plugin_storage WHERE + owner_uid= ? AND name = ?"); + $sth->execute([$this->owner_uid, $plugin]); if (!isset($this->storage[$plugin])) $this->storage[$plugin] = array(); - $content = $this->dbh->escape_string(serialize($this->storage[$plugin]), - false); + $content = serialize($this->storage[$plugin]); - if ($this->dbh->num_rows($result) != 0) { - $this->dbh->query("UPDATE ttrss_plugin_storage SET content = '$content' - WHERE owner_uid= '".$this->owner_uid."' AND name = '$plugin'"); + if ($sth->fetch()) { + $sth = $this->pdo->prepare("UPDATE ttrss_plugin_storage SET content = ? + WHERE owner_uid= ? AND name = ?"); + $sth->execute([(string)$content, $this->owner_uid, $plugin]); } else { - $this->dbh->query("INSERT INTO ttrss_plugin_storage + $sth = $this->pdo->prepare("INSERT INTO ttrss_plugin_storage (name,owner_uid,content) VALUES - ('$plugin','".$this->owner_uid."','$content')"); + (?, ?, ?)"); + $sth->execute([$plugin, $this->owner_uid, (string)$content]); } - $this->dbh->query("COMMIT"); + $this->pdo->commit(); } } @@ -366,8 +369,9 @@ class PluginHost { unset($this->storage[$idx]); - $this->dbh->query("DELETE FROM ttrss_plugin_storage WHERE name = '$idx' - AND owner_uid = " . $this->owner_uid); + $sth = $this->pdo->prepare("DELETE FROM ttrss_plugin_storage WHERE name = ? + AND owner_uid = ?"); + $sth->execute([$idx, $this->owner_uid]); } } diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php index 2a9c57e79..846e814ee 100644 --- a/classes/pref/feeds.php +++ b/classes/pref/feeds.php @@ -17,35 +17,34 @@ class Pref_Feeds extends Handler_Protected { } function renamecat() { - $title = $this->dbh->escape_string($_REQUEST['title']); - $id = $this->dbh->escape_string($_REQUEST['id']); + $title = clean($_REQUEST['title']); + $id = clean($_REQUEST['id']); if ($title) { - $this->dbh->query("UPDATE ttrss_feed_categories SET - title = '$title' WHERE id = '$id' AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_feed_categories SET + title = ? WHERE id = ? AND owner_uid = ?"); + $sth->execute([$title, $id, $_SESSION['uid']]); } - return; } private function get_category_items($cat_id) { - if ($_REQUEST['mode'] != 2) + if (clean($_REQUEST['mode']) != 2) $search = $_SESSION["prefs_feed_search"]; else $search = ""; - if ($search) $search_qpart = " AND (LOWER(title) LIKE LOWER('%$search%') OR LOWER(feed_url) LIKE LOWER('%$search%'))"; - // first one is set by API - $show_empty_cats = $_REQUEST['force_show_empty'] || - ($_REQUEST['mode'] != 2 && !$search); + $show_empty_cats = clean($_REQUEST['force_show_empty']) || + (clean($_REQUEST['mode']) != 2 && !$search); $items = array(); - $result = $this->dbh->query("SELECT id, title FROM ttrss_feed_categories - WHERE owner_uid = " . $_SESSION["uid"] . " AND parent_cat = '$cat_id' ORDER BY order_id, title"); + $sth = $this->pdo->prepare("SELECT id, title FROM ttrss_feed_categories + WHERE owner_uid = ? AND parent_cat = ? ORDER BY order_id, title"); + $sth->execute([$_SESSION['uid'], $cat_id]); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { $cat = array(); $cat['id'] = 'CAT:' . $line['id']; @@ -69,13 +68,17 @@ class Pref_Feeds extends Handler_Protected { } - $feed_result = $this->dbh->query("SELECT id, title, last_error, + $fsth = $this->pdo->prepare("SELECT id, title, last_error, ".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated FROM ttrss_feeds - WHERE cat_id = '$cat_id' AND owner_uid = ".$_SESSION["uid"]. - "$search_qpart ORDER BY order_id, title"); + WHERE cat_id = :cat AND + owner_uid = :uid AND + (:search = '' OR (LOWER(title) LIKE :search OR LOWER(feed_url) LIKE :search)) + ORDER BY order_id, title"); - while ($feed_line = $this->dbh->fetch_assoc($feed_result)) { + $fsth->execute([":cat" => $cat_id, ":uid" => $_SESSION['uid'], ":search" => $search ? "%$search%" : ""]); + + while ($feed_line = $fsth->fetch()) { $feed = array(); $feed['id'] = 'FEED:' . $feed_line['id']; $feed['bare_id'] = (int)$feed_line['id']; @@ -100,13 +103,11 @@ class Pref_Feeds extends Handler_Protected { function makefeedtree() { - if ($_REQUEST['mode'] != 2) + if (clean($_REQUEST['mode']) != 2) $search = $_SESSION["prefs_feed_search"]; else $search = ""; - if ($search) $search_qpart = " AND LOWER(title) LIKE LOWER('%$search%')"; - $root = array(); $root['id'] = 'root'; $root['name'] = __('Feeds'); @@ -115,7 +116,7 @@ class Pref_Feeds extends Handler_Protected { $enable_cats = get_pref('ENABLE_FEED_CATS'); - if ($_REQUEST['mode'] == 2) { + if (clean($_REQUEST['mode']) == 2) { if ($enable_cats) { $cat = $this->feedlist_init_cat(-1); @@ -158,29 +159,31 @@ class Pref_Feeds extends Handler_Protected { $root['items'] = array_merge($root['items'], $cat['items']); } - $result = $this->dbh->query("SELECT * FROM - ttrss_labels2 WHERE owner_uid = ".$_SESSION['uid']." ORDER by caption"); + $sth = $this->pdo->prepare("SELECT * FROM + ttrss_labels2 WHERE owner_uid = ? ORDER by caption"); + $sth->execute([$_SESSION['uid']]); - if ($this->dbh->num_rows($result) > 0) { - - if (get_pref('ENABLE_FEED_CATS')) { - $cat = $this->feedlist_init_cat(-2); - } else { - $cat['items'] = array(); - } + if (get_pref('ENABLE_FEED_CATS')) { + $cat = $this->feedlist_init_cat(-2); + } else { + $cat['items'] = array(); + } - while ($line = $this->dbh->fetch_assoc($result)) { + $num_labels = 0; + while ($line = $sth->fetch()) { + ++$num_labels; - $label_id = Labels::label_to_feed_id($line['id']); + $label_id = Labels::label_to_feed_id($line['id']); - $feed = $this->feedlist_init_feed($label_id, false, 0); + $feed = $this->feedlist_init_feed($label_id, false, 0); - $feed['fg_color'] = $line['fg_color']; - $feed['bg_color'] = $line['bg_color']; + $feed['fg_color'] = $line['fg_color']; + $feed['bg_color'] = $line['bg_color']; - array_push($cat['items'], $feed); - } + array_push($cat['items'], $feed); + } + if ($num_labels) { if ($enable_cats) { array_push($root['items'], $cat); } else { @@ -190,13 +193,14 @@ class Pref_Feeds extends Handler_Protected { } if ($enable_cats) { - $show_empty_cats = $_REQUEST['force_show_empty'] || - ($_REQUEST['mode'] != 2 && !$search); + $show_empty_cats = clean($_REQUEST['force_show_empty']) || + (clean($_REQUEST['mode']) != 2 && !$search); - $result = $this->dbh->query("SELECT id, title FROM ttrss_feed_categories - WHERE owner_uid = " . $_SESSION["uid"] . " AND parent_cat IS NULL ORDER BY order_id, title"); + $sth = $this->pdo->prepare("SELECT id, title FROM ttrss_feed_categories + WHERE owner_uid = ? AND parent_cat IS NULL ORDER BY order_id, title"); + $sth->execute([$_SESSION['uid']]); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { $cat = array(); $cat['id'] = 'CAT:' . $line['id']; $cat['bare_id'] = (int)$line['id']; @@ -232,13 +236,16 @@ class Pref_Feeds extends Handler_Protected { $cat['unread'] = 0; $cat['child_unread'] = 0; - $feed_result = $this->dbh->query("SELECT id, title,last_error, + $fsth = $this->pdo->prepare("SELECT id, title,last_error, ".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated FROM ttrss_feeds - WHERE cat_id IS NULL AND owner_uid = ".$_SESSION["uid"]. - "$search_qpart ORDER BY order_id, title"); + WHERE cat_id IS NULL AND + owner_uid = :uid AND + (:search = '' OR (LOWER(title) LIKE :search OR LOWER(feed_url) LIKE :search)) + ORDER BY order_id, title"); + $fsth->execute([":uid" => $_SESSION['uid'], ":search" => $search ? "%$search%" : ""]); - while ($feed_line = $this->dbh->fetch_assoc($feed_result)) { + while ($feed_line = $fsth->fetch()) { $feed = array(); $feed['id'] = 'FEED:' . $feed_line['id']; $feed['bare_id'] = (int)$feed_line['id']; @@ -264,13 +271,15 @@ class Pref_Feeds extends Handler_Protected { $root['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', (int) $num_children), $num_children); } else { - $feed_result = $this->dbh->query("SELECT id, title, last_error, + $fsth = $this->pdo->prepare("SELECT id, title, last_error, ".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated FROM ttrss_feeds - WHERE owner_uid = ".$_SESSION["uid"]. - "$search_qpart ORDER BY order_id, title"); + WHERE owner_uid = :uid AND + (:search = '' OR (LOWER(title) LIKE :search OR LOWER(feed_url) LIKE :search)) + ORDER BY order_id, title"); + $fsth->execute([":uid" => $_SESSION['uid'], ":search" => $search ? "%$search%" : ""]); - while ($feed_line = $this->dbh->fetch_assoc($feed_result)) { + while ($feed_line = $fsth->fetch()) { $feed = array(); $feed['id'] = 'FEED:' . $feed_line['id']; $feed['bare_id'] = (int)$feed_line['id']; @@ -294,7 +303,7 @@ class Pref_Feeds extends Handler_Protected { $fl['identifier'] = 'id'; $fl['label'] = 'name'; - if ($_REQUEST['mode'] != 2) { + if (clean($_REQUEST['mode']) != 2) { $fl['items'] = array($root); } else { $fl['items'] = $root['items']; @@ -304,15 +313,15 @@ class Pref_Feeds extends Handler_Protected { } function catsortreset() { - $this->dbh->query("UPDATE ttrss_feed_categories - SET order_id = 0 WHERE owner_uid = " . $_SESSION["uid"]); - return; + $sth = $this->pdo->prepare("UPDATE ttrss_feed_categories + SET order_id = 0 WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); } function feedsortreset() { - $this->dbh->query("UPDATE ttrss_feeds - SET order_id = 0 WHERE owner_uid = " . $_SESSION["uid"]); - return; + $sth = $this->pdo->prepare("UPDATE ttrss_feeds + SET order_id = 0 WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); } private function process_category_order(&$data_map, $item_id, $parent_id = false, $nest_level = 0) { @@ -324,19 +333,20 @@ class Pref_Feeds extends Handler_Protected { if ($debug) _debug("$prefix C: $item_id P: $parent_id"); - $bare_item_id = $this->dbh->escape_string(substr($item_id, strpos($item_id, ':')+1)); + $bare_item_id = substr($item_id, strpos($item_id, ':')+1); if ($item_id != 'root') { if ($parent_id && $parent_id != 'root') { $parent_bare_id = substr($parent_id, strpos($parent_id, ':')+1); - $parent_qpart = $this->dbh->escape_string($parent_bare_id); + $parent_qpart = $parent_bare_id; } else { - $parent_qpart = 'NULL'; + $parent_qpart = null; } - $this->dbh->query("UPDATE ttrss_feed_categories - SET parent_cat = $parent_qpart WHERE id = '$bare_item_id' AND - owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_feed_categories + SET parent_cat = ? WHERE id = ? AND + owner_uid = ?"); + $sth->execute([$parent_qpart, $bare_item_id, $_SESSION['uid']]); } $order_id = 1; @@ -346,7 +356,7 @@ class Pref_Feeds extends Handler_Protected { if ($cat && is_array($cat)) { foreach ($cat as $item) { $id = $item['_reference']; - $bare_id = $this->dbh->escape_string(substr($id, strpos($id, ':')+1)); + $bare_id = substr($id, strpos($id, ':')+1); if ($debug) _debug("$prefix [$order_id] $id/$bare_id"); @@ -354,30 +364,22 @@ class Pref_Feeds extends Handler_Protected { if (strpos($id, "FEED") === 0) { - $cat_id = ($item_id != "root") ? - $this->dbh->escape_string($bare_item_id) : "NULL"; + $cat_id = ($item_id != "root") ? $bare_item_id : null; - $cat_qpart = ($cat_id != 0) ? "cat_id = '$cat_id'" : - "cat_id = NULL"; + $sth = $this->pdo->prepare("UPDATE ttrss_feeds + SET order_id = ?, cat_id = ? + WHERE id = ? AND owner_uid = ?"); - $this->dbh->query("UPDATE ttrss_feeds - SET order_id = $order_id, $cat_qpart - WHERE id = '$bare_id' AND - owner_uid = " . $_SESSION["uid"]); + $sth->execute([$order_id, $cat_id ? $cat_id : null, $bare_id, $_SESSION['uid']]); } else if (strpos($id, "CAT:") === 0) { $this->process_category_order($data_map, $item['_reference'], $item_id, $nest_level+1); - if ($item_id != 'root') { - $parent_qpart = $this->dbh->escape_string($bare_id); - } else { - $parent_qpart = 'NULL'; - } - - $this->dbh->query("UPDATE ttrss_feed_categories - SET order_id = '$order_id' WHERE id = '$bare_id' AND - owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_feed_categories + SET order_id = ? WHERE id = ? AND + owner_uid = ?"); + $sth->execute([$order_id, $bare_id, $_SESSION['uid']]); } } @@ -387,9 +389,9 @@ class Pref_Feeds extends Handler_Protected { } function savefeedorder() { - $data = json_decode($_POST['payload'], true); + $data = json_decode(clean($_POST['payload']), true); - #file_put_contents("/tmp/saveorder.json", $_POST['payload']); + #file_put_contents("/tmp/saveorder.json", clean($_POST['payload'])); #$data = json_decode(file_get_contents("/tmp/saveorder.json"), true); if (!is_array($data['items'])) @@ -419,67 +421,28 @@ class Pref_Feeds extends Handler_Protected { } $this->process_category_order($data_map, $root_item); - - /* foreach ($data['items'][0]['items'] as $item) { - $id = $item['_reference']; - $bare_id = substr($id, strpos($id, ':')+1); - - ++$cat_order_id; - - if ($bare_id > 0) { - $this->dbh->query("UPDATE ttrss_feed_categories - SET order_id = '$cat_order_id' WHERE id = '$bare_id' AND - owner_uid = " . $_SESSION["uid"]); - } - - $feed_order_id = 0; - - if (is_array($data_map[$id])) { - foreach ($data_map[$id] as $feed) { - $id = $feed['_reference']; - $feed_id = substr($id, strpos($id, ':')+1); - - if ($bare_id != 0) - $cat_query = "cat_id = '$bare_id'"; - else - $cat_query = "cat_id = NULL"; - - $this->dbh->query("UPDATE ttrss_feeds - SET order_id = '$feed_order_id', - $cat_query - WHERE id = '$feed_id' AND - owner_uid = " . $_SESSION["uid"]); - - ++$feed_order_id; - } - } - } */ } - - return; } function removeicon() { - $feed_id = $this->dbh->escape_string($_REQUEST["feed_id"]); + $feed_id = clean($_REQUEST["feed_id"]); - $result = $this->dbh->query("SELECT id FROM ttrss_feeds - WHERE id = '$feed_id' AND owner_uid = ". $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds + WHERE id = ? AND owner_uid = ?"); + $sth->execute([$feed_id, $_SESSION['uid']]); - if ($this->dbh->num_rows($result) != 0) { + if ($row = $sth->fetch()) { @unlink(ICONS_DIR . "/$feed_id.ico"); - $this->dbh->query("UPDATE ttrss_feeds SET favicon_avg_color = NULL - where id = '$feed_id'"); + $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET favicon_avg_color = NULL + where id = ?"); + $sth->execute([$feed_id]); } - - return; } function uploadicon() { header("Content-type: text/html"); - $tmp_file = false; - if (is_uploaded_file($_FILES['icon_file']['tmp_name'])) { $tmp_file = tempnam(CACHE_DIR . '/upload', 'icon'); @@ -494,20 +457,23 @@ class Pref_Feeds extends Handler_Protected { } $icon_file = $tmp_file; - $feed_id = $this->dbh->escape_string($_REQUEST["feed_id"]); + $feed_id = clean($_REQUEST["feed_id"]); if (is_file($icon_file) && $feed_id) { if (filesize($icon_file) < 65535) { - $result = $this->dbh->query("SELECT id FROM ttrss_feeds - WHERE id = '$feed_id' AND owner_uid = ". $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds + WHERE id = ? AND owner_uid = ?"); + $sth->execute([$feed_id, $_SESSION['uid']]); - if ($this->dbh->num_rows($result) != 0) { + if ($row = $sth->fetch()) { @unlink(ICONS_DIR . "/$feed_id.ico"); if (rename($icon_file, ICONS_DIR . "/$feed_id.ico")) { - $this->dbh->query("UPDATE ttrss_feeds SET + + $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET favicon_avg_color = '' - WHERE id = '$feed_id'"); + WHERE id = ?"); + $sth->execute([$feed_id]); $rc = 0; } @@ -533,283 +499,283 @@ class Pref_Feeds extends Handler_Protected { global $purge_intervals; global $update_intervals; - print '<div dojoType="dijit.layout.TabContainer" style="height : 450px"> - <div dojoType="dijit.layout.ContentPane" title="'.__('General').'">'; - $feed_id = $this->dbh->escape_string($_REQUEST["id"]); + $feed_id = clean($_REQUEST["id"]); - $result = $this->dbh->query( - "SELECT * FROM ttrss_feeds WHERE id = '$feed_id' AND - owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT * FROM ttrss_feeds WHERE id = ? AND + owner_uid = ?"); + $sth->execute([$feed_id, $_SESSION['uid']]); - $auth_pass_encrypted = sql_bool_to_bool($this->dbh->fetch_result($result, 0, - "auth_pass_encrypted")); + if ($row = $sth->fetch()) { + print '<div dojoType="dijit.layout.TabContainer" style="height : 450px"> + <div dojoType="dijit.layout.ContentPane" title="'.__('General').'">'; - $title = htmlspecialchars($this->dbh->fetch_result($result, - 0, "title")); + $auth_pass_encrypted = $row["auth_pass_encrypted"]; - print_hidden("id", "$feed_id"); - print_hidden("op", "pref-feeds"); - print_hidden("method", "editSave"); + $title = htmlspecialchars($row["title"]); - print "<div class=\"dlgSec\">".__("Feed")."</div>"; - print "<div class=\"dlgSecCont\">"; + print_hidden("id", "$feed_id"); + print_hidden("op", "pref-feeds"); + print_hidden("method", "editSave"); + + print "<div class=\"dlgSec\">".__("Feed")."</div>"; + print "<div class=\"dlgSecCont\">"; - /* Title */ + /* Title */ - print "<input dojoType=\"dijit.form.ValidationTextBox\" required=\"1\" + print "<input dojoType=\"dijit.form.ValidationTextBox\" required=\"1\" placeHolder=\"".__("Feed Title")."\" style=\"font-size : 16px; width: 20em\" name=\"title\" value=\"$title\">"; - /* Feed URL */ + /* Feed URL */ - $feed_url = $this->dbh->fetch_result($result, 0, "feed_url"); - $feed_url = htmlspecialchars($this->dbh->fetch_result($result, - 0, "feed_url")); + $feed_url = htmlspecialchars($row["feed_url"]); - print "<hr/>"; + print "<hr/>"; - print __('URL:') . " "; - print "<input dojoType=\"dijit.form.ValidationTextBox\" required=\"1\" + print __('URL:') . " "; + print "<input dojoType=\"dijit.form.ValidationTextBox\" required=\"1\" placeHolder=\"".__("Feed URL")."\" regExp='^(http|https)://.*' style=\"width : 20em\" name=\"feed_url\" value=\"$feed_url\">"; - $last_error = $this->dbh->fetch_result($result, 0, "last_error"); + $last_error = $row["last_error"]; - if ($last_error) { - print " <img src=\"images/error.png\" alt=\"(error)\" + if ($last_error) { + print " <img src=\"images/error.png\" alt=\"(error)\" style=\"vertical-align : middle\" title=\"".htmlspecialchars($last_error)."\">"; - } + } - /* Category */ + /* Category */ - if (get_pref('ENABLE_FEED_CATS')) { + if (get_pref('ENABLE_FEED_CATS')) { - $cat_id = $this->dbh->fetch_result($result, 0, "cat_id"); + $cat_id = $row["cat_id"]; - print "<hr/>"; + print "<hr/>"; - print __('Place in category:') . " "; + print __('Place in category:') . " "; - print_feed_cat_select("cat_id", $cat_id, - 'dojoType="dijit.form.Select"'); - } + print_feed_cat_select("cat_id", $cat_id, + 'dojoType="dijit.form.Select"'); + } - /* FTS Stemming Language */ + /* FTS Stemming Language */ - if (DB_TYPE == "pgsql") { - $feed_language = $this->dbh->fetch_result($result, 0, "feed_language"); + if (DB_TYPE == "pgsql") { + $feed_language = $row["feed_language"]; - print "<hr/>"; + print "<hr/>"; - print __('Language:') . " "; - print_select("feed_language", $feed_language, $this::$feed_languages, - 'dojoType="dijit.form.Select"'); - } + print __('Language:') . " "; + print_select("feed_language", $feed_language, $this::$feed_languages, + 'dojoType="dijit.form.Select"'); + } - print "</div>"; + print "</div>"; - print "<div class=\"dlgSec\">".__("Update")."</div>"; - print "<div class=\"dlgSecCont\">"; + print "<div class=\"dlgSec\">".__("Update")."</div>"; + print "<div class=\"dlgSecCont\">"; - /* Update Interval */ + /* Update Interval */ - $update_interval = $this->dbh->fetch_result($result, 0, "update_interval"); + $update_interval = $row["update_interval"]; - print_select_hash("update_interval", $update_interval, $update_intervals, - 'dojoType="dijit.form.Select"'); + print_select_hash("update_interval", $update_interval, $update_intervals, + 'dojoType="dijit.form.Select"'); - /* Purge intl */ + /* Purge intl */ - $purge_interval = $this->dbh->fetch_result($result, 0, "purge_interval"); + $purge_interval = $row["purge_interval"]; - print "<hr/>"; - print __('Article purging:') . " "; + print "<hr/>"; + print __('Article purging:') . " "; - print_select_hash("purge_interval", $purge_interval, $purge_intervals, - 'dojoType="dijit.form.Select" ' . + print_select_hash("purge_interval", $purge_interval, $purge_intervals, + 'dojoType="dijit.form.Select" ' . ((FORCE_ARTICLE_PURGE == 0) ? "" : 'disabled="1"')); - print "</div>"; + print "</div>"; - $auth_login = htmlspecialchars($this->dbh->fetch_result($result, 0, "auth_login")); - $auth_pass = $this->dbh->fetch_result($result, 0, "auth_pass"); + $auth_login = htmlspecialchars($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); - } + if ($auth_pass_encrypted && function_exists("mcrypt_decrypt")) { + require_once "crypt.php"; + $auth_pass = decrypt_string($auth_pass); + } - $auth_pass = htmlspecialchars($auth_pass); - $auth_enabled = $auth_login !== '' || $auth_pass !== ''; + $auth_pass = htmlspecialchars($auth_pass); + $auth_enabled = $auth_login !== '' || $auth_pass !== ''; - $auth_style = $auth_enabled ? '' : 'display: none'; - print "<div id='feedEditDlg_loginContainer' style='$auth_style'>"; - print "<div class=\"dlgSec\">".__("Authentication")."</div>"; - print "<div class=\"dlgSecCont\">"; + $auth_style = $auth_enabled ? '' : 'display: none'; + print "<div id='feedEditDlg_loginContainer' style='$auth_style'>"; + print "<div class=\"dlgSec\">".__("Authentication")."</div>"; + print "<div class=\"dlgSecCont\">"; - print "<input dojoType=\"dijit.form.TextBox\" id=\"feedEditDlg_login\" + print "<input dojoType=\"dijit.form.TextBox\" id=\"feedEditDlg_login\" placeHolder=\"".__("Login")."\" autocomplete=\"new-password\" name=\"auth_login\" value=\"$auth_login\"><hr/>"; - print "<input dojoType=\"dijit.form.TextBox\" type=\"password\" name=\"auth_pass\" + print "<input dojoType=\"dijit.form.TextBox\" type=\"password\" name=\"auth_pass\" autocomplete=\"new-password\" placeHolder=\"".__("Password")."\" value=\"$auth_pass\">"; - print "<div dojoType=\"dijit.Tooltip\" connectId=\"feedEditDlg_login\" position=\"below\"> + print "<div dojoType=\"dijit.Tooltip\" connectId=\"feedEditDlg_login\" position=\"below\"> ".__('<b>Hint:</b> you need to fill in your login information if your feed requires authentication, except for Twitter feeds.')." </div>"; - print "</div></div>"; + print "</div></div>"; - $auth_checked = $auth_enabled ? 'checked' : ''; - print "<div style=\"clear : both\"> + $auth_checked = $auth_enabled ? 'checked' : ''; + print "<div style=\"clear : both\"> <input type=\"checkbox\" $auth_checked name=\"need_auth\" dojoType=\"dijit.form.CheckBox\" id=\"feedEditDlg_loginCheck\" onclick='checkboxToggleElement(this, \"feedEditDlg_loginContainer\")'> <label for=\"feedEditDlg_loginCheck\">". - __('This feed requires authentication.')."</div>"; + __('This feed requires authentication.')."</div>"; - print '</div><div dojoType="dijit.layout.ContentPane" title="'.__('Options').'">'; + print '</div><div dojoType="dijit.layout.ContentPane" title="'.__('Options').'">'; - //print "<div class=\"dlgSec\">".__("Options")."</div>"; - print "<div class=\"dlgSecSimple\">"; + //print "<div class=\"dlgSec\">".__("Options")."</div>"; + print "<div class=\"dlgSecSimple\">"; - $private = sql_bool_to_bool($this->dbh->fetch_result($result, 0, "private")); + $private = $row["private"]; - if ($private) { - $checked = "checked=\"1\""; - } else { - $checked = ""; - } + if ($private) { + $checked = "checked=\"1\""; + } else { + $checked = ""; + } - print "<input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" name=\"private\" id=\"private\" + print "<input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" name=\"private\" id=\"private\" $checked> <label for=\"private\">".__('Hide from Popular feeds')."</label>"; - $include_in_digest = sql_bool_to_bool($this->dbh->fetch_result($result, 0, "include_in_digest")); + $include_in_digest = $row["include_in_digest"]; - if ($include_in_digest) { - $checked = "checked=\"1\""; - } else { - $checked = ""; - } + if ($include_in_digest) { + $checked = "checked=\"1\""; + } else { + $checked = ""; + } - print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"include_in_digest\" + print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"include_in_digest\" name=\"include_in_digest\" $checked> <label for=\"include_in_digest\">".__('Include in e-mail digest')."</label>"; - $always_display_enclosures = sql_bool_to_bool($this->dbh->fetch_result($result, 0, "always_display_enclosures")); + $always_display_enclosures = $row["always_display_enclosures"]; - if ($always_display_enclosures) { - $checked = "checked"; - } else { - $checked = ""; - } + if ($always_display_enclosures) { + $checked = "checked"; + } else { + $checked = ""; + } - print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"always_display_enclosures\" + print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"always_display_enclosures\" name=\"always_display_enclosures\" $checked> <label for=\"always_display_enclosures\">".__('Always display image attachments')."</label>"; - $hide_images = sql_bool_to_bool($this->dbh->fetch_result($result, 0, "hide_images")); + $hide_images = $row["hide_images"]; - if ($hide_images) { - $checked = "checked=\"1\""; - } else { - $checked = ""; - } + if ($hide_images) { + $checked = "checked=\"1\""; + } else { + $checked = ""; + } - print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"hide_images\" + print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"hide_images\" name=\"hide_images\" $checked> <label for=\"hide_images\">". - __('Do not embed images')."</label>"; + __('Do not embed images')."</label>"; - $cache_images = sql_bool_to_bool($this->dbh->fetch_result($result, 0, "cache_images")); + $cache_images = $row["cache_images"]; - if ($cache_images) { - $checked = "checked=\"1\""; - } else { - $checked = ""; - } + if ($cache_images) { + $checked = "checked=\"1\""; + } else { + $checked = ""; + } - print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"cache_images\" + print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"cache_images\" name=\"cache_images\" $checked> <label for=\"cache_images\">". - __('Cache media')."</label>"; + __('Cache media')."</label>"; - $mark_unread_on_update = sql_bool_to_bool($this->dbh->fetch_result($result, 0, "mark_unread_on_update")); + $mark_unread_on_update = $row["mark_unread_on_update"]; - if ($mark_unread_on_update) { - $checked = "checked"; - } else { - $checked = ""; - } + if ($mark_unread_on_update) { + $checked = "checked"; + } else { + $checked = ""; + } - print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"mark_unread_on_update\" + print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"mark_unread_on_update\" name=\"mark_unread_on_update\" $checked> <label for=\"mark_unread_on_update\">".__('Mark updated articles as unread')."</label>"; - print "</div>"; + print "</div>"; - print '</div><div dojoType="dijit.layout.ContentPane" title="'.__('Icon').'">'; + print '</div><div dojoType="dijit.layout.ContentPane" title="'.__('Icon').'">'; - /* Icon */ + /* Icon */ - print "<div class=\"dlgSecSimple\">"; + print "<div class=\"dlgSecSimple\">"; - print "<iframe name=\"icon_upload_iframe\" - style=\"width: 400px; height: 100px; display: none;\"></iframe>"; + print "<img class=\"feedIcon\" src=\"".Feeds::getFeedIcon($feed_id)."\">"; - print "<form style='display : block' target=\"icon_upload_iframe\" + print "<iframe name=\"icon_upload_iframe\" + style=\"width: 400px; height: 100px; display: none;\"></iframe>"; + + print "<form style='display : block' target=\"icon_upload_iframe\" enctype=\"multipart/form-data\" method=\"POST\" action=\"backend.php\"> - <input id=\"icon_file\" size=\"10\" name=\"icon_file\" type=\"file\"> + <label class=\"dijitButton\">".__("Choose file...")." + <input style=\"display: none\" id=\"icon_file\" size=\"10\" name=\"icon_file\" type=\"file\"> + </label> <input type=\"hidden\" name=\"op\" value=\"pref-feeds\"> <input type=\"hidden\" name=\"feed_id\" value=\"$feed_id\"> - <input type=\"hidden\" name=\"method\" value=\"uploadicon\"><p> + <input type=\"hidden\" name=\"method\" value=\"uploadicon\"> <button class=\"\" dojoType=\"dijit.form.Button\" onclick=\"return uploadFeedIcon();\" type=\"submit\">".__('Replace')."</button> - <button class=\"\" dojoType=\"dijit.form.Button\" onclick=\"return removeFeedIcon($feed_id);\" + <button class=\"danger\" dojoType=\"dijit.form.Button\" onclick=\"return removeFeedIcon($feed_id);\" type=\"submit\">".__('Remove')."</button> </form>"; - print "</div>"; + print "</div>"; - print '</div><div dojoType="dijit.layout.ContentPane" title="'.__('Plugins').'">'; + print '</div><div dojoType="dijit.layout.ContentPane" title="'.__('Plugins').'">'; - PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_EDIT_FEED, - "hook_prefs_edit_feed", $feed_id); + PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_EDIT_FEED, + "hook_prefs_edit_feed", $feed_id); - print "</div></div>"; + print "</div></div>"; - $title = htmlspecialchars($title, ENT_QUOTES); + $title = htmlspecialchars($title, ENT_QUOTES); - print "<div class='dlgButtons'> + print "<div class='dlgButtons'> <div style=\"float : left\"> <button class=\"danger\" dojoType=\"dijit.form.Button\" onclick='return unsubscribeFeed($feed_id, \"$title\")'>". __('Unsubscribe')."</button>"; - print "</div>"; + print "</div>"; - print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('feedEditDlg').execute()\">".__('Save')."</button> - <button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('feedEditDlg').hide()\">".__('Cancel')."</button> - </div>"; - - - return; + print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('feedEditDlg').execute()\">".__('Save')."</button> + <button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('feedEditDlg').hide()\">".__('Cancel')."</button> + </div>"; + } } function editfeeds() { global $purge_intervals; global $update_intervals; - $feed_ids = $this->dbh->escape_string($_REQUEST["ids"]); + $feed_ids = clean($_REQUEST["ids"]); print_notice("Enable the options you wish to apply using checkboxes on the right:"); @@ -958,72 +924,77 @@ class Pref_Feeds extends Handler_Protected { function editsaveops($batch) { - $feed_title = $this->dbh->escape_string(trim($_POST["title"])); - $feed_link = $this->dbh->escape_string(trim($_POST["feed_url"])); - $upd_intl = (int) $this->dbh->escape_string($_POST["update_interval"]); - $purge_intl = (int) $this->dbh->escape_string($_POST["purge_interval"]); - $feed_id = (int) $this->dbh->escape_string($_POST["id"]); /* editSave */ - $feed_ids = $this->dbh->escape_string($_POST["ids"]); /* batchEditSave */ - $cat_id = (int) $this->dbh->escape_string($_POST["cat_id"]); - $auth_login = $this->dbh->escape_string(trim($_POST["auth_login"])); - $auth_pass = trim($_POST["auth_pass"]); - $private = checkbox_to_sql_bool($this->dbh->escape_string($_POST["private"])); + $feed_title = trim(clean($_POST["title"])); + $feed_url = trim(clean($_POST["feed_url"])); + $upd_intl = (int) clean($_POST["update_interval"]); + $purge_intl = (int) clean($_POST["purge_interval"]); + $feed_id = (int) clean($_POST["id"]); /* editSave */ + $feed_ids = explode(",", clean($_POST["ids"])); /* batchEditSave */ + $cat_id = (int) clean($_POST["cat_id"]); + $auth_login = trim(clean($_POST["auth_login"])); + $auth_pass = trim(clean($_POST["auth_pass"])); + $private = checkbox_to_sql_bool(clean($_POST["private"])); $include_in_digest = checkbox_to_sql_bool( - $this->dbh->escape_string($_POST["include_in_digest"])); + clean($_POST["include_in_digest"])); $cache_images = checkbox_to_sql_bool( - $this->dbh->escape_string($_POST["cache_images"])); + clean($_POST["cache_images"])); $hide_images = checkbox_to_sql_bool( - $this->dbh->escape_string($_POST["hide_images"])); + clean($_POST["hide_images"])); $always_display_enclosures = checkbox_to_sql_bool( - $this->dbh->escape_string($_POST["always_display_enclosures"])); + clean($_POST["always_display_enclosures"])); $mark_unread_on_update = checkbox_to_sql_bool( - $this->dbh->escape_string($_POST["mark_unread_on_update"])); + clean($_POST["mark_unread_on_update"])); - $feed_language = $this->dbh->escape_string(trim($_POST["feed_language"])); - - $auth_pass = $this->dbh->escape_string($auth_pass); - - if (get_pref('ENABLE_FEED_CATS')) { - if ($cat_id && $cat_id != 0) { - $category_qpart = "cat_id = '$cat_id',"; - $category_qpart_nocomma = "cat_id = '$cat_id'"; - } else { - $category_qpart = 'cat_id = NULL,'; - $category_qpart_nocomma = 'cat_id = NULL'; - } - } else { - $category_qpart = ""; - $category_qpart_nocomma = ""; - } + $feed_language = trim(clean($_POST["feed_language"])); if (!$batch) { - if ($_POST["need_auth"] !== 'on') { + if (clean($_POST["need_auth"]) !== 'on') { $auth_login = ''; $auth_pass = ''; } - $result = db_query("SELECT feed_url FROM ttrss_feeds WHERE id = " . $feed_id); - $orig_feed_url = db_fetch_result($result, 0, "feed_url"); - - $reset_basic_info = $orig_feed_url != $feed_link; - - $this->dbh->query("UPDATE ttrss_feeds SET - $category_qpart - title = '$feed_title', feed_url = '$feed_link', - update_interval = '$upd_intl', - purge_interval = '$purge_intl', - auth_login = '$auth_login', - auth_pass = '$auth_pass', + $sth = $this->pdo->prepare("SELECT feed_url FROM ttrss_feeds WHERE id = ?"); + $sth->execute([$feed_id]); + $row = $sth->fetch(); + $orig_feed_url = $row["feed_url"]; + + $reset_basic_info = $orig_feed_url != $feed_url; + + $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET + cat_id = :cat_id, + title = :title, + feed_url = :feed_url, + update_interval = :upd_intl, + purge_interval = :purge_intl, + auth_login = :auth_login, + auth_pass = :auth_pass, auth_pass_encrypted = false, - private = $private, - cache_images = $cache_images, - hide_images = $hide_images, - include_in_digest = $include_in_digest, - always_display_enclosures = $always_display_enclosures, - mark_unread_on_update = $mark_unread_on_update, - feed_language = '$feed_language' - WHERE id = '$feed_id' AND owner_uid = " . $_SESSION["uid"]); + private = :private, + cache_images = :cache_images, + hide_images = :hide_images, + include_in_digest = :include_in_digest, + always_display_enclosures = :always_display_enclosures, + mark_unread_on_update = :mark_unread_on_update, + feed_language = :feed_language + WHERE id = :id AND owner_uid = :uid"); + + $sth->execute([":title" => $feed_title, + ":cat_id" => $cat_id ? $cat_id : null, + ":feed_url" => $feed_url, + ":upd_intl" => $upd_intl, + ":purge_intl" => $purge_intl, + ":auth_login" => $auth_login, + ":auth_pass" => $auth_pass, + ":private" => (int)$private, + ":cache_images" => (int)$cache_images, + ":hide_images" => (int)$hide_images, + ":include_in_digest" => (int)$include_in_digest, + ":always_display_enclosures" => (int)$always_display_enclosures, + ":mark_unread_on_update" => (int)$mark_unread_on_update, + ":feed_language" => $feed_language, + ":id" => $feed_id, + ":uid" => $_SESSION['uid']]); if ($reset_basic_info) { RSSUtils::set_basic_feed_info($feed_id); @@ -1037,11 +1008,13 @@ class Pref_Feeds extends Handler_Protected { foreach (array_keys($_POST) as $k) { if ($k != "op" && $k != "method" && $k != "ids") { - $feed_data[$k] = $_POST[$k]; + $feed_data[$k] = clean($_POST[$k]); } } - $this->dbh->query("BEGIN"); + $this->pdo->beginTransaction(); + + $feed_ids_qmarks = arr_qmarks($feed_ids); foreach (array_keys($feed_data) as $k) { @@ -1049,79 +1022,87 @@ class Pref_Feeds extends Handler_Protected { switch ($k) { case "title": - $qpart = "title = '$feed_title'"; + $qpart = "title = " . $this->pdo->quote($feed_title); break; case "feed_url": - $qpart = "feed_url = '$feed_link'"; + $qpart = "feed_url = " . $this->pdo->quote($feed_url); break; case "update_interval": - $qpart = "update_interval = '$upd_intl'"; + $qpart = "update_interval = " . $this->pdo->quote($upd_intl); break; case "purge_interval": - $qpart = "purge_interval = '$purge_intl'"; + $qpart = "purge_interval =" . $this->pdo->quote($purge_intl); break; case "auth_login": - $qpart = "auth_login = '$auth_login'"; + $qpart = "auth_login = " . $this->pdo->quote($auth_login); break; case "auth_pass": - $qpart = "auth_pass = '$auth_pass', auth_pass_encrypted = false"; + $qpart = "auth_pass =" . $this->pdo->quote($auth_pass). ", auth_pass_encrypted = false"; break; case "private": - $qpart = "private = $private"; + $qpart = "private = " . $this->pdo->quote($private); break; case "include_in_digest": - $qpart = "include_in_digest = $include_in_digest"; + $qpart = "include_in_digest = " . $this->pdo->quote($include_in_digest); break; case "always_display_enclosures": - $qpart = "always_display_enclosures = $always_display_enclosures"; + $qpart = "always_display_enclosures = " . $this->pdo->quote($always_display_enclosures); break; case "mark_unread_on_update": - $qpart = "mark_unread_on_update = $mark_unread_on_update"; + $qpart = "mark_unread_on_update = " . $this->pdo->quote($mark_unread_on_update); break; case "cache_images": - $qpart = "cache_images = $cache_images"; + $qpart = "cache_images = " . $this->pdo->quote($cache_images); break; case "hide_images": - $qpart = "hide_images = $hide_images"; + $qpart = "hide_images = " . $this->pdo->quote($hide_images); break; case "cat_id": - $qpart = $category_qpart_nocomma; + if (get_pref('ENABLE_FEED_CATS')) { + if ($cat_id) { + $qpart = "cat_id = " . $this->pdo->quote($cat_id); + } else { + $qpart = 'cat_id = NULL'; + } + } else { + $qpart = ""; + } + break; case "feed_language": - $qpart = "feed_language = '$feed_language'"; + $qpart = "feed_language = " . $this->pdo->quote($feed_language); break; } if ($qpart) { - $this->dbh->query( - "UPDATE ttrss_feeds SET $qpart WHERE id IN ($feed_ids) - AND owner_uid = " . $_SESSION["uid"]); - print "<br/>"; + $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET $qpart WHERE id IN ($feed_ids_qmarks) + AND owner_uid = ?"); + $sth->execute(array_merge($feed_ids, [$_SESSION['uid']])); } } - $this->dbh->query("COMMIT"); + $this->pdo->commit(); } return; } function remove() { - $ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"])); + $ids = explode(",", clean($_REQUEST["ids"])); foreach ($ids as $id) { Pref_Feeds::remove_feed($id, $_SESSION["uid"]); @@ -1130,150 +1111,15 @@ class Pref_Feeds extends Handler_Protected { return; } - function clear() { - $id = $this->dbh->escape_string($_REQUEST["id"]); - $this->clear_feed_articles($id); - } - - function rescore() { - $ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"])); - - foreach ($ids as $id) { - - $filters = load_filters($id, $_SESSION["uid"], 6); - - $result = $this->dbh->query("SELECT - title, content, link, ref_id, author,". - SUBSTRING_FOR_DATE."(updated, 1, 19) AS updated - FROM - ttrss_user_entries, ttrss_entries - WHERE ref_id = id AND feed_id = '$id' AND - owner_uid = " .$_SESSION['uid']." - "); - - $scores = array(); - - while ($line = $this->dbh->fetch_assoc($result)) { - - $tags = Article::get_article_tags($line["ref_id"]); - - $article_filters = RSSUtils::get_article_filters($filters, $line['title'], - $line['content'], $line['link'], strtotime($line['updated']), - $line['author'], $tags); - - $new_score = RSSUtils::calculate_article_score($article_filters); - - if (!$scores[$new_score]) $scores[$new_score] = array(); - - array_push($scores[$new_score], $line['ref_id']); - } - - foreach (array_keys($scores) as $s) { - if ($s > 1000) { - $this->dbh->query("UPDATE ttrss_user_entries SET score = '$s', - marked = true WHERE - ref_id IN (" . join(',', $scores[$s]) . ")"); - } else if ($s < -500) { - $this->dbh->query("UPDATE ttrss_user_entries SET score = '$s', - unread = false WHERE - ref_id IN (" . join(',', $scores[$s]) . ")"); - } else { - $this->dbh->query("UPDATE ttrss_user_entries SET score = '$s' WHERE - ref_id IN (" . join(',', $scores[$s]) . ")"); - } - } - } - - print __("All done."); - - } - - function rescoreAll() { - - $result = $this->dbh->query( - "SELECT id FROM ttrss_feeds WHERE owner_uid = " . $_SESSION['uid']); - - while ($feed_line = $this->dbh->fetch_assoc($result)) { - - $id = $feed_line["id"]; - - $filters = load_filters($id, $_SESSION["uid"], 6); - - $tmp_result = $this->dbh->query("SELECT - title, content, link, ref_id, author,". - SUBSTRING_FOR_DATE."(updated, 1, 19) AS updated - FROM - ttrss_user_entries, ttrss_entries - WHERE ref_id = id AND feed_id = '$id' AND - owner_uid = " .$_SESSION['uid']." - "); - - $scores = array(); - - while ($line = $this->dbh->fetch_assoc($tmp_result)) { - - $tags = Article::get_article_tags($line["ref_id"]); - - $article_filters = RSSUtils::get_article_filters($filters, $line['title'], - $line['content'], $line['link'], strtotime($line['updated']), - $line['author'], $tags); - - $new_score = RSSUtils::calculate_article_score($article_filters); - - if (!$scores[$new_score]) $scores[$new_score] = array(); - - array_push($scores[$new_score], $line['ref_id']); - } - - foreach (array_keys($scores) as $s) { - if ($s > 1000) { - $this->dbh->query("UPDATE ttrss_user_entries SET score = '$s', - marked = true WHERE - ref_id IN (" . join(',', $scores[$s]) . ")"); - } else { - $this->dbh->query("UPDATE ttrss_user_entries SET score = '$s' WHERE - ref_id IN (" . join(',', $scores[$s]) . ")"); - } - } - } - - print __("All done."); - - } - - function categorize() { - $ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"])); - - $cat_id = $this->dbh->escape_string($_REQUEST["cat_id"]); - - if ($cat_id == 0) { - $cat_id_qpart = 'NULL'; - } else { - $cat_id_qpart = "'$cat_id'"; - } - - $this->dbh->query("BEGIN"); - - foreach ($ids as $id) { - - $this->dbh->query("UPDATE ttrss_feeds SET cat_id = $cat_id_qpart - WHERE id = '$id' - AND owner_uid = " . $_SESSION["uid"]); - - } - - $this->dbh->query("COMMIT"); - } - function removeCat() { - $ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"])); + $ids = explode(",", clean($_REQUEST["ids"])); foreach ($ids as $id) { $this->remove_feed_category($id, $_SESSION["uid"]); } } function addCat() { - $feed_cat = $this->dbh->escape_string(trim($_REQUEST["cat"])); + $feed_cat = trim(clean($_REQUEST["cat"])); add_feed_category($feed_cat); } @@ -1283,10 +1129,15 @@ class Pref_Feeds extends Handler_Protected { print "<div dojoType=\"dijit.layout.AccordionContainer\" region=\"center\">"; print "<div id=\"pref-feeds-feeds\" dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Feeds')."\">"; - $result = $this->dbh->query("SELECT COUNT(id) AS num_errors - FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ".$_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT COUNT(id) AS num_errors + FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); - $num_errors = $this->dbh->fetch_result($result, 0, "num_errors"); + if ($row = $sth->fetch()) { + $num_errors = $row["num_errors"]; + } else { + $num_errors = 0; + } if ($num_errors > 0) { @@ -1301,7 +1152,7 @@ class Pref_Feeds extends Handler_Protected { onclick=\"showInactiveFeeds()\">" . __("Inactive feeds") . "</button>"; - $feed_search = $this->dbh->escape_string($_REQUEST["search"]); + $feed_search = clean($_REQUEST["search"]); if (array_key_exists("search", $_REQUEST)) { $_SESSION["prefs_feed_search"] = $feed_search; @@ -1361,24 +1212,6 @@ class Pref_Feeds extends Handler_Protected { print $error_button; print $inactive_button; - if (defined('_ENABLE_FEED_DEBUGGING')) { - - print "<select id=\"feedActionChooser\" onchange=\"feedActionChange()\"> - <option value=\"facDefault\" selected>".__('More actions...')."</option>"; - - if (FORCE_ARTICLE_PURGE == 0) { - print - "<option value=\"facPurge\">".__('Manual purge')."</option>"; - } - - print " - <option value=\"facClear\">".__('Clear feed data')."</option> - <option value=\"facRescore\">".__('Rescore articles')."</option>"; - - print "</select>"; - - } - print "</div>"; # toolbar //print '</div>'; @@ -1438,7 +1271,9 @@ class Pref_Feeds extends Handler_Protected { print "<form name=\"opml_form\" style='display : block' target=\"upload_iframe\" enctype=\"multipart/form-data\" method=\"POST\" action=\"backend.php\"> - <input id=\"opml_file\" name=\"opml_file\" type=\"file\"> + <label class=\"dijitButton\">".__("Choose file...")." + <input style=\"display : none\" id=\"opml_file\" name=\"opml_file\" type=\"file\"> + </label> <input type=\"hidden\" name=\"op\" value=\"dlg\"> <input type=\"hidden\" name=\"method\" value=\"importOpml\"> <button dojoType=\"dijit.form.Button\" onclick=\"return opmlImport();\" type=\"submit\">" . @@ -1449,7 +1284,7 @@ class Pref_Feeds extends Handler_Protected { $opml_export_filename = "TinyTinyRSS_".date("Y-m-d").".opml"; print "<p>" . __('Filename:') . - " <input type=\"text\" id=\"filename\" value=\"$opml_export_filename\" /> " . + " <input class=\"input input-text\" type=\"text\" id=\"filename\" value=\"$opml_export_filename\" /> " . __('Include settings') . "<input type=\"checkbox\" id=\"settings\" checked=\"1\"/>"; print "</p><button dojoType=\"dijit.form.Button\" @@ -1568,17 +1403,18 @@ class Pref_Feeds extends Handler_Protected { $interval_qpart = "DATE_SUB(NOW(), INTERVAL 3 MONTH)"; } - $result = $this->dbh->query("SELECT ttrss_feeds.title, ttrss_feeds.site_url, + $sth = $this->pdo->prepare("SELECT ttrss_feeds.title, ttrss_feeds.site_url, ttrss_feeds.feed_url, ttrss_feeds.id, MAX(updated) AS last_article FROM ttrss_feeds, ttrss_entries, ttrss_user_entries WHERE (SELECT MAX(updated) FROM ttrss_entries, ttrss_user_entries WHERE ttrss_entries.id = ref_id AND ttrss_user_entries.feed_id = ttrss_feeds.id) < $interval_qpart - AND ttrss_feeds.owner_uid = ".$_SESSION["uid"]." AND + AND ttrss_feeds.owner_uid = ? AND ttrss_user_entries.feed_id = ttrss_feeds.id AND ttrss_entries.id = ref_id GROUP BY ttrss_feeds.title, ttrss_feeds.id, ttrss_feeds.site_url, ttrss_feeds.feed_url ORDER BY last_article"); + $sth->execute([$_SESSION['uid']]); print "<p" .__("These feeds have not been updated with new content for 3 months (oldest first):") . "</p>"; @@ -1599,7 +1435,7 @@ class Pref_Feeds extends Handler_Protected { $lnum = 1; - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { $feed_id = $line["id"]; $this_row_id = "id=\"FUPDD-$feed_id\""; @@ -1643,8 +1479,9 @@ class Pref_Feeds extends Handler_Protected { } function feedsWithErrors() { - $result = $this->dbh->query("SELECT id,title,feed_url,last_error,site_url - FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ".$_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT id,title,feed_url,last_error,site_url + FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); print "<div dojoType=\"dijit.Toolbar\">"; print "<div dojoType=\"dijit.form.DropDownButton\">". @@ -1663,7 +1500,7 @@ class Pref_Feeds extends Handler_Protected { $lnum = 1; - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { $feed_id = $line["id"]; $this_row_id = "id=\"FERDD-$feed_id\""; @@ -1707,33 +1544,11 @@ class Pref_Feeds extends Handler_Protected { print "</div>"; } - /** - * Purge a feed contents, marked articles excepted. - * - * @param mixed $link The database connection. - * @param integer $id The id of the feed to purge. - * @return void - */ - private function clear_feed_articles($id) { - - if ($id != 0) { - $result = $this->dbh->query("DELETE FROM ttrss_user_entries - WHERE feed_id = '$id' AND marked = false AND owner_uid = " . $_SESSION["uid"]); - } else { - $result = $this->dbh->query("DELETE FROM ttrss_user_entries - WHERE feed_id IS NULL AND marked = false AND owner_uid = " . $_SESSION["uid"]); - } - - $result = $this->dbh->query("DELETE FROM ttrss_entries WHERE - (SELECT COUNT(int_id) FROM ttrss_user_entries WHERE ref_id = id) = 0"); - - CCache::update($id, $_SESSION['uid']); - } // function clear_feed_articles - private function remove_feed_category($id, $owner_uid) { - $this->dbh->query("DELETE FROM ttrss_feed_categories - WHERE id = '$id' AND owner_uid = $owner_uid"); + $sth = $this->pdo->prepare("DELETE FROM ttrss_feed_categories + WHERE id = ? AND owner_uid = ?"); + $sth->execute([$id, $owner_uid]); CCache::remove($id, $owner_uid, true); } @@ -1748,51 +1563,63 @@ class Pref_Feeds extends Handler_Protected { } } + $pdo = Db::pdo(); + if ($id > 0) { + $pdo->beginTransaction(); /* save starred articles in Archived feed */ - db_query("BEGIN"); - /* prepare feed if necessary */ - $result = db_query("SELECT feed_url FROM ttrss_feeds WHERE id = $id - AND owner_uid = $owner_uid"); + $sth = $pdo->prepare("SELECT feed_url FROM ttrss_feeds WHERE id = ? + AND owner_uid = ?"); + $sth->execute([$id, $owner_uid]); - $feed_url = db_escape_string(db_fetch_result($result, 0, "feed_url")); + if ($row = $sth->fetch()) { + $feed_url = $row["feed_url"]; - $result = db_query("SELECT id FROM ttrss_archived_feeds - WHERE feed_url = '$feed_url' AND owner_uid = $owner_uid"); + $sth = $pdo->prepare("SELECT id FROM ttrss_archived_feeds + WHERE feed_url = ? AND owner_uid = ?"); + $sth->execute([$feed_url, $owner_uid]); - if (db_num_rows($result) == 0) { - $result = db_query("SELECT MAX(id) AS id FROM ttrss_archived_feeds"); - $new_feed_id = (int)db_fetch_result($result, 0, "id") + 1; + if ($row = $sth->fetch()) { + $archive_id = $row["id"]; + } else { + $res = $pdo->query("SELECT MAX(id) AS id FROM ttrss_archived_feeds"); + $row = $res->fetch(); - db_query("INSERT INTO ttrss_archived_feeds - (id, owner_uid, title, feed_url, site_url) - SELECT $new_feed_id, owner_uid, title, feed_url, site_url from ttrss_feeds - WHERE id = '$id'"); + $new_feed_id = (int)$row['id'] + 1; - $archive_id = $new_feed_id; - } else { - $archive_id = db_fetch_result($result, 0, "id"); - } + $sth = $pdo->prepare("INSERT INTO ttrss_archived_feeds + (id, owner_uid, title, feed_url, site_url) + SELECT ?, owner_uid, title, feed_url, site_url from ttrss_feeds + WHERE id = ?"); + $sth->execute([$new_feed_id, $id]); - db_query("UPDATE ttrss_user_entries SET feed_id = NULL, - orig_feed_id = '$archive_id' WHERE feed_id = '$id' AND - marked = true AND owner_uid = $owner_uid"); + $archive_id = $new_feed_id; + } + + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET feed_id = NULL, + orig_feed_id = ? WHERE feed_id = ? AND + marked = true AND owner_uid = ?"); - /* Remove access key for the feed */ + $sth->execute([$archive_id, $id, $owner_uid]); - db_query("DELETE FROM ttrss_access_keys WHERE - feed_id = '$id' AND owner_uid = $owner_uid"); + /* Remove access key for the feed */ - /* remove the feed */ + $sth = $pdo->prepare("DELETE FROM ttrss_access_keys WHERE + feed_id = ? AND owner_uid = ?"); + $sth->execute([$id, $owner_uid]); - db_query("DELETE FROM ttrss_feeds - WHERE id = '$id' AND owner_uid = $owner_uid"); + /* remove the feed */ - db_query("COMMIT"); + $sth = $pdo->prepare("DELETE FROM ttrss_feeds + WHERE id = ? AND owner_uid = ?"); + $sth->execute([$id, $owner_uid]); + } + + $pdo->commit(); if (file_exists(ICONS_DIR . "/$id.ico")) { unlink(ICONS_DIR . "/$id.ico"); @@ -1856,39 +1683,31 @@ class Pref_Feeds extends Handler_Protected { } function batchAddFeeds() { - $cat_id = $this->dbh->escape_string($_REQUEST['cat']); - $feeds = explode("\n", $_REQUEST['feeds']); - $login = $this->dbh->escape_string($_REQUEST['login']); - $pass = trim($_REQUEST['pass']); + $cat_id = clean($_REQUEST['cat']); + $feeds = explode("\n", clean($_REQUEST['feeds'])); + $login = clean($_REQUEST['login']); + $pass = trim(clean($_REQUEST['pass'])); foreach ($feeds as $feed) { - $feed = $this->dbh->escape_string(trim($feed)); + $feed = trim($feed); if (validate_feed_url($feed)) { - $this->dbh->query("BEGIN"); - - if ($cat_id == "0" || !$cat_id) { - $cat_qpart = "NULL"; - } else { - $cat_qpart = "'$cat_id'"; - } + $this->pdo->beginTransaction(); - $result = $this->dbh->query( - "SELECT id FROM ttrss_feeds - WHERE feed_url = '$feed' AND owner_uid = ".$_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds + WHERE feed_url = ? AND owner_uid = ?"); + $sth->execute([$feed, $_SESSION['uid']]); - $pass = $this->dbh->escape_string($pass); - - if ($this->dbh->num_rows($result) == 0) { - $result = $this->dbh->query( - "INSERT INTO ttrss_feeds + if (!$sth->fetch()) { + $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds (owner_uid,feed_url,title,cat_id,auth_login,auth_pass,update_method,auth_pass_encrypted) - VALUES ('".$_SESSION["uid"]."', '$feed', - '[Unknown]', $cat_qpart, '$login', '$pass', 0, false)"); + VALUES (?, ?, '[Unknown]', ?, ?, ?, 0, false)"); + + $sth->execute([$_SESSION['uid'], $feed, $cat_id ? $cat_id : null, $login, $pass]); } - $this->dbh->query("COMMIT"); + $this->pdo->commit(); } } } @@ -1903,8 +1722,8 @@ class Pref_Feeds extends Handler_Protected { } function regenFeedKey() { - $feed_id = $this->dbh->escape_string($_REQUEST['id']); - $is_cat = $this->dbh->escape_string($_REQUEST['is_cat']) == "true"; + $feed_id = clean($_REQUEST['id']); + $is_cat = clean($_REQUEST['is_cat']) == "true"; $new_key = $this->update_feed_access_key($feed_id, $is_cat); @@ -1915,30 +1734,19 @@ class Pref_Feeds extends Handler_Protected { private function update_feed_access_key($feed_id, $is_cat, $owner_uid = false) { if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - $sql_is_cat = bool_to_sql_bool($is_cat); - - $result = $this->dbh->query("SELECT access_key FROM ttrss_access_keys - WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat - AND owner_uid = " . $owner_uid); + // clear old value and generate new one + $sth = $this->pdo->prepare("DELETE FROM ttrss_access_keys + WHERE feed_id = ? AND is_cat = ? AND owner_uid = ?"); + $sth->execute([$feed_id, $is_cat, $owner_uid]); - if ($this->dbh->num_rows($result) == 1) { - $key = $this->dbh->escape_string(uniqid_short()); - - $this->dbh->query("UPDATE ttrss_access_keys SET access_key = '$key' - WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat - AND owner_uid = " . $owner_uid); - - return $key; - - } else { - return get_feed_access_key($feed_id, $is_cat, $owner_uid); - } + return get_feed_access_key($feed_id, $is_cat, $owner_uid); } // Silent function clearKeys() { - $this->dbh->query("DELETE FROM ttrss_access_keys WHERE - owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("DELETE FROM ttrss_access_keys WHERE + owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); } private function calculate_children_count($cat) { @@ -1962,13 +1770,16 @@ class Pref_Feeds extends Handler_Protected { $interval_qpart = "DATE_SUB(NOW(), INTERVAL 3 MONTH)"; } - $result = $this->dbh->query("SELECT COUNT(*) AS num_inactive FROM ttrss_feeds WHERE + $sth = $this->pdo->prepare("SELECT COUNT(id) AS num_inactive FROM ttrss_feeds WHERE (SELECT MAX(updated) FROM ttrss_entries, ttrss_user_entries WHERE ttrss_entries.id = ref_id AND ttrss_user_entries.feed_id = ttrss_feeds.id) < $interval_qpart AND - ttrss_feeds.owner_uid = ".$_SESSION["uid"]); + ttrss_feeds.owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); - print (int) $this->dbh->fetch_result($result, 0, "num_inactive"); + if ($row = $sth->fetch()) { + print (int)$row["num_inactive"]; + } } static function subscribe_to_feed_url() { diff --git a/classes/pref/filters.php b/classes/pref/filters.php index 6ea233cb6..74aecd309 100755 --- a/classes/pref/filters.php +++ b/classes/pref/filters.php @@ -9,15 +9,16 @@ class Pref_Filters extends Handler_Protected { } function filtersortreset() { - $this->dbh->query("UPDATE ttrss_filters2 - SET order_id = 0 WHERE owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_filters2 + SET order_id = 0 WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); return; } function savefilterorder() { - $data = json_decode($_POST['payload'], true); + $data = json_decode(clean($_POST['payload']), true); - #file_put_contents("/tmp/saveorder.json", $_POST['payload']); + #file_put_contents("/tmp/saveorder.json", clean($_POST['payload'])); #$data = json_decode(file_get_contents("/tmp/saveorder.json"), true); if (!is_array($data['items'])) @@ -26,15 +27,16 @@ class Pref_Filters extends Handler_Protected { $index = 0; if (is_array($data) && is_array($data['items'])) { + + $sth = $this->pdo->prepare("UPDATE ttrss_filters2 SET + order_id = ? WHERE id = ? AND + owner_uid = ?"); + foreach ($data['items'][0]['items'] as $item) { $filter_id = (int) str_replace("FILTER:", "", $item['_reference']); if ($filter_id > 0) { - - $this->dbh->query("UPDATE ttrss_filters2 SET - order_id = $index WHERE id = '$filter_id' AND - owner_uid = " .$_SESSION["uid"]); - + $sth->execute([$index, $filter_id, $_SESSION['uid']]); ++$index; } } @@ -44,31 +46,29 @@ class Pref_Filters extends Handler_Protected { } function testFilterDo() { - $offset = (int) db_escape_string($_REQUEST["offset"]); - $limit = (int) db_escape_string($_REQUEST["limit"]); + $offset = (int) clean($_REQUEST["offset"]); + $limit = (int) clean($_REQUEST["limit"]); $filter = array(); $filter["enabled"] = true; - $filter["match_any_rule"] = sql_bool_to_bool( - checkbox_to_sql_bool($this->dbh->escape_string($_REQUEST["match_any_rule"]))); - $filter["inverse"] = sql_bool_to_bool( - checkbox_to_sql_bool($this->dbh->escape_string($_REQUEST["inverse"]))); + $filter["match_any_rule"] = checkbox_to_sql_bool(clean($_REQUEST["match_any_rule"])); + $filter["inverse"] = checkbox_to_sql_bool(clean($_REQUEST["inverse"])); $filter["rules"] = array(); $filter["actions"] = array("dummy-action"); - $result = $this->dbh->query("SELECT id,name FROM ttrss_filter_types"); + $res = $this->pdo->query("SELECT id,name FROM ttrss_filter_types"); $filter_types = array(); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $res->fetch()) { $filter_types[$line["id"]] = $line["name"]; } $scope_qparts = array(); $rctr = 0; - foreach ($_REQUEST["rule"] AS $r) { + foreach (clean($_REQUEST["rule"]) AS $r) { $rule = json_decode($r, true); if ($rule && $rctr < 5) { @@ -80,9 +80,9 @@ class Pref_Filters extends Handler_Protected { if (strpos($feed_id, "CAT:") === 0) { $cat_id = (int) substr($feed_id, 4); - array_push($scope_inner_qparts, "cat_id = " . $cat_id); + array_push($scope_inner_qparts, "cat_id = " . $this->pdo->quote($cat_id)); } else if ($feed_id > 0) { - array_push($scope_inner_qparts, "feed_id = " . $feed_id); + array_push($scope_inner_qparts, "feed_id = " . $this->pdo->quote($feed_id)); } } @@ -109,74 +109,76 @@ class Pref_Filters extends Handler_Protected { //while ($found < $limit && $offset < $limit * 1000 && time() - $started < ini_get("max_execution_time") * 0.7) { - $result = db_query("SELECT ttrss_entries.id, - ttrss_entries.title, - ttrss_feeds.id AS feed_id, - ttrss_feeds.title AS feed_title, - ttrss_feed_categories.id AS cat_id, - content, - date_entered, - link, - author, - tag_cache - FROM - ttrss_entries, ttrss_user_entries - LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id) - LEFT JOIN ttrss_feed_categories ON (ttrss_feeds.cat_id = ttrss_feed_categories.id) - WHERE - ref_id = ttrss_entries.id AND - ($scope_qpart) AND - ttrss_user_entries.owner_uid = " . $_SESSION["uid"] . " - ORDER BY date_entered DESC LIMIT $limit OFFSET $offset"); - - while ($line = db_fetch_assoc($result)) { - - $rc = RSSUtils::get_article_filters(array($filter), $line['title'], $line['content'], $line['link'], - $line['author'], explode(",", $line['tag_cache'])); - - if (count($rc) > 0) { - - $line["content_preview"] = truncate_string(strip_tags($line["content"]), 200, '…'); - - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) { - $line = $p->hook_query_headlines($line, 100); - } + $sth = $this->pdo->prepare("SELECT ttrss_entries.id, + ttrss_entries.title, + ttrss_feeds.id AS feed_id, + ttrss_feeds.title AS feed_title, + ttrss_feed_categories.id AS cat_id, + content, + date_entered, + link, + author, + tag_cache + FROM + ttrss_entries, ttrss_user_entries + LEFT JOIN ttrss_feeds ON (feed_id = ttrss_feeds.id) + LEFT JOIN ttrss_feed_categories ON (ttrss_feeds.cat_id = ttrss_feed_categories.id) + WHERE + ref_id = ttrss_entries.id AND + ($scope_qpart) AND + ttrss_user_entries.owner_uid = ? + ORDER BY date_entered DESC LIMIT $limit OFFSET $offset"); - $content_preview = $line["content_preview"]; + $sth->execute([$_SESSION['uid']]); - $tmp = "<tr style='margin-top : 5px'>"; + while ($line = $sth->fetch()) { - #$tmp .= "<td width='5%' align='center'><input dojoType=\"dijit.form.CheckBox\" - # checked=\"1\" disabled=\"1\" type=\"checkbox\"></td>"; + $rc = RSSUtils::get_article_filters(array($filter), $line['title'], $line['content'], $line['link'], + $line['author'], explode(",", $line['tag_cache'])); - $id = $line['id']; - $tmp .= "<td width='5%' align='center'><img style='cursor : pointer' title='".__("Preview article")."' - src='images/information.png' onclick='openArticlePopup($id)'></td><td>"; + if (count($rc) > 0) { - /*foreach ($filter['rules'] as $rule) { - $reg_exp = str_replace('/', '\/', $rule["reg_exp"]); + $line["content_preview"] = truncate_string(strip_tags($line["content"]), 200, '…'); - $line["title"] = preg_replace("/($reg_exp)/i", - "<span class=\"highlight\">$1</span>", $line["title"]); + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) { + $line = $p->hook_query_headlines($line, 100); + } - $content_preview = preg_replace("/($reg_exp)/i", - "<span class=\"highlight\">$1</span>", $content_preview); - }*/ + $content_preview = $line["content_preview"]; - $tmp .= "<strong>" . $line["title"] . "</strong><br/>"; - $tmp .= $line['feed_title'] . ", " . mb_substr($line["date_entered"], 0, 16); - $tmp .= "<div class='insensitive'>" . $content_preview . "</div>"; - $tmp .= "</td></tr>"; + $tmp = "<tr style='margin-top : 5px'>"; - array_push($rv, $tmp); + #$tmp .= "<td width='5%' align='center'><input dojoType=\"dijit.form.CheckBox\" + # checked=\"1\" disabled=\"1\" type=\"checkbox\"></td>"; - /*array_push($rv, array("title" => $line["title"], - "content" => $content_preview, - "date" => $line["date_entered"], - "feed" => $line["feed_title"])); */ + $id = $line['id']; + $tmp .= "<td width='5%' align='center'><img style='cursor : pointer' title='".__("Preview article")."' + src='images/information.png' onclick='openArticlePopup($id)'></td><td>"; + + /*foreach ($filter['rules'] as $rule) { + $reg_exp = str_replace('/', '\/', $rule["reg_exp"]); + + $line["title"] = preg_replace("/($reg_exp)/i", + "<span class=\"highlight\">$1</span>", $line["title"]); + + $content_preview = preg_replace("/($reg_exp)/i", + "<span class=\"highlight\">$1</span>", $content_preview); + }*/ + + $tmp .= "<strong>" . $line["title"] . "</strong><br/>"; + $tmp .= $line['feed_title'] . ", " . mb_substr($line["date_entered"], 0, 16); + $tmp .= "<div class='insensitive'>" . $content_preview . "</div>"; + $tmp .= "</td></tr>"; + + array_push($rv, $tmp); + + /*array_push($rv, array("title" => $line["title"], + "content" => $content_preview, + "date" => $line["date_entered"], + "feed" => $line["feed_title"])); */ - } } + } //$offset += $limit; //} @@ -209,7 +211,7 @@ class Pref_Filters extends Handler_Protected { } private function getfilterrules_concise($filter_id) { - $result = $this->dbh->query("SELECT reg_exp, + $sth = $this->pdo->prepare("SELECT reg_exp, inverse, match_on, feed_id, @@ -219,12 +221,13 @@ class Pref_Filters extends Handler_Protected { FROM ttrss_filters2_rules, ttrss_filter_types WHERE - filter_id = '$filter_id' AND filter_type = ttrss_filter_types.id + filter_id = ? AND filter_type = ttrss_filter_types.id ORDER BY reg_exp"); + $sth->execute([$filter_id]); $rv = ""; - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { if ($line["match_on"]) { $feeds = json_decode($line["match_on"], true); @@ -247,7 +250,7 @@ class Pref_Filters extends Handler_Protected { } else { - $where = sql_bool_to_bool($line["cat_filter"]) ? + $where = $line["cat_filter"] ? Feeds::getCategoryTitle($line["cat_id"]) : ($line["feed_id"] ? Feeds::getFeedTitle($line["feed_id"]) : __("All feeds")); @@ -255,13 +258,13 @@ class Pref_Filters extends Handler_Protected { # $where = $line["cat_id"] . "/" . $line["feed_id"]; - $inverse = sql_bool_to_bool($line["inverse"]) ? "inverse" : ""; + $inverse = $line["inverse"] ? "inverse" : ""; $rv .= "<span class='$inverse'>" . T_sprintf("%s on %s in %s %s", htmlspecialchars($line["reg_exp"]), $line["field"], $where, - sql_bool_to_bool($line["inverse"]) ? __("(inverse)") : "") . "</span>"; + $line["inverse"] ? __("(inverse)") : "") . "</span>"; } return $rv; @@ -275,7 +278,7 @@ class Pref_Filters extends Handler_Protected { $filter_search = $_SESSION["prefs_filter_search"]; - $result = $this->dbh->query("SELECT *, + $sth = $this->pdo->prepare("SELECT *, (SELECT action_param FROM ttrss_filters2_actions WHERE filter_id = ttrss_filters2.id ORDER BY id LIMIT 1) AS action_param, (SELECT action_id FROM ttrss_filters2_actions @@ -286,22 +289,23 @@ class Pref_Filters extends Handler_Protected { (SELECT reg_exp FROM ttrss_filters2_rules WHERE filter_id = ttrss_filters2.id ORDER BY id LIMIT 1) AS reg_exp FROM ttrss_filters2 WHERE - owner_uid = ".$_SESSION["uid"]." ORDER BY order_id, title"); - + owner_uid = ? ORDER BY order_id, title"); + $sth->execute([$_SESSION['uid']]); $folder = array(); $folder['items'] = array(); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { $name = $this->getFilterName($line["id"]); $match_ok = false; if ($filter_search) { - $rules_result = $this->dbh->query( - "SELECT reg_exp FROM ttrss_filters2_rules WHERE filter_id = ".$line["id"]); + $rules_sth = $this->pdo->prepare("SELECT reg_exp + FROM ttrss_filters2_rules WHERE filter_id = ?"); + $rules_sth->execute([$line['id']]); - while ($rule_line = $this->dbh->fetch_assoc($rules_result)) { + while ($rule_line = $rules_sth->fetch()) { if (mb_strpos($rule_line['reg_exp'], $filter_search) !== false) { $match_ok = true; break; @@ -310,13 +314,14 @@ class Pref_Filters extends Handler_Protected { } if ($line['action_id'] == 7) { - $label_result = $this->dbh->query("SELECT fg_color, bg_color - FROM ttrss_labels2 WHERE caption = '".$this->dbh->escape_string($line['action_param'])."' AND - owner_uid = " . $_SESSION["uid"]); + $label_sth = $this->pdo->prepare("SELECT fg_color, bg_color + FROM ttrss_labels2 WHERE caption = ? AND + owner_uid = ?"); + $label_sth->execute([$line['action_param'], $_SESSION['uid']]); - if ($this->dbh->num_rows($label_result) > 0) { - $fg_color = $this->dbh->fetch_result($label_result, 0, "fg_color"); - $bg_color = $this->dbh->fetch_result($label_result, 0, "bg_color"); + if ($label_row = $label_sth->fetch()) { + $fg_color = $label_row["fg_color"]; + $bg_color = $label_row["bg_color"]; $name[1] = "<span class=\"labelColorIndicator\" id=\"label-editor-indicator\" style='color : $fg_color; background-color : $bg_color; margin-right : 4px'>α</span>" . $name[1]; } @@ -328,7 +333,7 @@ class Pref_Filters extends Handler_Protected { $filter['name'] = $name[0]; $filter['param'] = $name[1]; $filter['checkbox'] = false; - $filter['enabled'] = sql_bool_to_bool($line["enabled"]); + $filter['enabled'] = $line["enabled"]; $filter['rules'] = $this->getfilterrules_concise($line['id']); if (!$filter_search || $match_ok) { @@ -336,10 +341,6 @@ class Pref_Filters extends Handler_Protected { } } - /* if (count($folder['items']) > 0) { - array_push($root['items'], $folder); - } */ - $root['items'] = $folder['items']; $fl = array(); @@ -353,179 +354,186 @@ class Pref_Filters extends Handler_Protected { function edit() { - $filter_id = $this->dbh->escape_string($_REQUEST["id"]); + $filter_id = clean($_REQUEST["id"]); - $result = $this->dbh->query( - "SELECT * FROM ttrss_filters2 WHERE id = '$filter_id' AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT * FROM ttrss_filters2 + WHERE id = ? AND owner_uid = ?"); + $sth->execute([$filter_id, $_SESSION['uid']]); - $enabled = sql_bool_to_bool($this->dbh->fetch_result($result, 0, "enabled")); - $match_any_rule = sql_bool_to_bool($this->dbh->fetch_result($result, 0, "match_any_rule")); - $inverse = sql_bool_to_bool($this->dbh->fetch_result($result, 0, "inverse")); - $title = htmlspecialchars($this->dbh->fetch_result($result, 0, "title")); + if ($row = $sth->fetch()) { - print "<form id=\"filter_edit_form\" onsubmit='return false'>"; + $enabled = $row["enabled"]; + $match_any_rule = $row["match_any_rule"]; + $inverse = $row["inverse"]; + $title = htmlspecialchars($row["title"]); - print_hidden("op", "pref-filters"); - print_hidden("id", "$filter_id"); - print_hidden("method", "editSave"); - print_hidden("csrf_token", $_SESSION['csrf_token']); + print "<form id=\"filter_edit_form\" onsubmit='return false'>"; - print "<div class=\"dlgSec\">".__("Caption")."</div>"; + print_hidden("op", "pref-filters"); + print_hidden("id", "$filter_id"); + print_hidden("method", "editSave"); + print_hidden("csrf_token", $_SESSION['csrf_token']); - print "<input required=\"true\" dojoType=\"dijit.form.ValidationTextBox\" style=\"width : 20em;\" name=\"title\" value=\"$title\">"; + print "<div class=\"dlgSec\">".__("Caption")."</div>"; - print "</div>"; + print "<input required=\"true\" dojoType=\"dijit.form.ValidationTextBox\" style=\"width : 20em;\" name=\"title\" value=\"$title\">"; - print "<div class=\"dlgSec\">".__("Match")."</div>"; + print "</div>"; - print "<div dojoType=\"dijit.Toolbar\">"; + print "<div class=\"dlgSec\">".__("Match")."</div>"; - print "<div dojoType=\"dijit.form.DropDownButton\">". + print "<div dojoType=\"dijit.Toolbar\">"; + + print "<div dojoType=\"dijit.form.DropDownButton\">". "<span>" . __('Select')."</span>"; - print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">"; - print "<div onclick=\"dijit.byId('filterEditDlg').selectRules(true)\" + print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">"; + print "<div onclick=\"dijit.byId('filterEditDlg').selectRules(true)\" dojoType=\"dijit.MenuItem\">".__('All')."</div>"; - print "<div onclick=\"dijit.byId('filterEditDlg').selectRules(false)\" + print "<div onclick=\"dijit.byId('filterEditDlg').selectRules(false)\" dojoType=\"dijit.MenuItem\">".__('None')."</div>"; - print "</div></div>"; + print "</div></div>"; - print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').addRule()\">". - __('Add')."</button> "; + print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').addRule()\">". + __('Add')."</button> "; - print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').deleteRule()\">". - __('Delete')."</button> "; + print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').deleteRule()\">". + __('Delete')."</button> "; - print "</div>"; + print "</div>"; - print "<ul id='filterDlg_Matches'>"; + print "<ul id='filterDlg_Matches'>"; - $rules_result = $this->dbh->query("SELECT * FROM ttrss_filters2_rules - WHERE filter_id = '$filter_id' ORDER BY reg_exp, id"); + $rules_sth = $this->pdo->prepare("SELECT * FROM ttrss_filters2_rules + WHERE filter_id = ? ORDER BY reg_exp, id"); + $rules_sth->execute([$filter_id]); - while ($line = $this->dbh->fetch_assoc($rules_result)) { - if ($line["match_on"]) { - $line["feed_id"] = json_decode($line["match_on"], true); - } else { - if (sql_bool_to_bool($line["cat_filter"])) { - $feed_id = "CAT:" . (int)$line["cat_id"]; - } else { - $feed_id = (int)$line["feed_id"]; - } + while ($line = $rules_sth->fetch()) { + if ($line["match_on"]) { + $line["feed_id"] = json_decode($line["match_on"], true); + } else { + if ($line["cat_filter"]) { + $feed_id = "CAT:" . (int)$line["cat_id"]; + } else { + $feed_id = (int)$line["feed_id"]; + } - $line["feed_id"] = ["" . $feed_id]; // set item type to string for in_array() - } + $line["feed_id"] = ["" . $feed_id]; // set item type to string for in_array() + } - unset($line["cat_filter"]); - unset($line["cat_id"]); - unset($line["filter_id"]); - unset($line["id"]); - if (!sql_bool_to_bool($line["inverse"])) unset($line["inverse"]); - unset($line["match_on"]); + unset($line["cat_filter"]); + unset($line["cat_id"]); + unset($line["filter_id"]); + unset($line["id"]); + if (!$line["inverse"]) unset($line["inverse"]); + unset($line["match_on"]); - $data = htmlspecialchars(json_encode($line)); + $data = htmlspecialchars(json_encode($line)); - print "<li><input dojoType='dijit.form.CheckBox' type='checkbox' onclick='toggleSelectListRow2(this)'>". - "<span onclick=\"dijit.byId('filterEditDlg').editRule(this)\">".$this->getRuleName($line)."</span>". - "<input type='hidden' name='rule[]' value=\"$data\"/></li>"; - } + print "<li><input dojoType='dijit.form.CheckBox' type='checkbox' onclick='toggleSelectListRow2(this)'>". + "<span onclick=\"dijit.byId('filterEditDlg').editRule(this)\">".$this->getRuleName($line)."</span>". + "<input type='hidden' name='rule[]' value=\"$data\"/></li>"; + } - print "</ul>"; + print "</ul>"; - print "</div>"; + print "</div>"; - print "<div class=\"dlgSec\">".__("Apply actions")."</div>"; + print "<div class=\"dlgSec\">".__("Apply actions")."</div>"; - print "<div dojoType=\"dijit.Toolbar\">"; + print "<div dojoType=\"dijit.Toolbar\">"; - print "<div dojoType=\"dijit.form.DropDownButton\">". + print "<div dojoType=\"dijit.form.DropDownButton\">". "<span>" . __('Select')."</span>"; - print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">"; - print "<div onclick=\"dijit.byId('filterEditDlg').selectActions(true)\" + print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">"; + print "<div onclick=\"dijit.byId('filterEditDlg').selectActions(true)\" dojoType=\"dijit.MenuItem\">".__('All')."</div>"; - print "<div onclick=\"dijit.byId('filterEditDlg').selectActions(false)\" + print "<div onclick=\"dijit.byId('filterEditDlg').selectActions(false)\" dojoType=\"dijit.MenuItem\">".__('None')."</div>"; - print "</div></div>"; + print "</div></div>"; - print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').addAction()\">". - __('Add')."</button> "; + print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').addAction()\">". + __('Add')."</button> "; - print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').deleteAction()\">". - __('Delete')."</button> "; + print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').deleteAction()\">". + __('Delete')."</button> "; - print "</div>"; + print "</div>"; - print "<ul id='filterDlg_Actions'>"; + print "<ul id='filterDlg_Actions'>"; - $actions_result = $this->dbh->query("SELECT * FROM ttrss_filters2_actions - WHERE filter_id = '$filter_id' ORDER BY id"); + $actions_sth = $this->pdo->prepare("SELECT * FROM ttrss_filters2_actions + WHERE filter_id = ? ORDER BY id"); + $actions_sth->execute([$filter_id]); - while ($line = $this->dbh->fetch_assoc($actions_result)) { - $line["action_param_label"] = $line["action_param"]; + while ($line = $actions_sth->fetch()) { + $line["action_param_label"] = $line["action_param"]; - unset($line["filter_id"]); - unset($line["id"]); + unset($line["filter_id"]); + unset($line["id"]); - $data = htmlspecialchars(json_encode($line)); + $data = htmlspecialchars(json_encode($line)); - print "<li><input dojoType='dijit.form.CheckBox' type='checkbox' onclick='toggleSelectListRow2(this)'>". - "<span onclick=\"dijit.byId('filterEditDlg').editAction(this)\">".$this->getActionName($line)."</span>". - "<input type='hidden' name='action[]' value=\"$data\"/></li>"; - } + print "<li><input dojoType='dijit.form.CheckBox' type='checkbox' onclick='toggleSelectListRow2(this)'>". + "<span onclick=\"dijit.byId('filterEditDlg').editAction(this)\">".$this->getActionName($line)."</span>". + "<input type='hidden' name='action[]' value=\"$data\"/></li>"; + } - print "</ul>"; + print "</ul>"; - print "</div>"; + print "</div>"; - if ($enabled) { - $checked = "checked=\"1\""; - } else { - $checked = ""; - } + if ($enabled) { + $checked = "checked=\"1\""; + } else { + $checked = ""; + } - print "<input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" name=\"enabled\" id=\"enabled\" $checked> + print "<input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" name=\"enabled\" id=\"enabled\" $checked> <label for=\"enabled\">".__('Enabled')."</label>"; - if ($match_any_rule) { - $checked = "checked=\"1\""; - } else { - $checked = ""; - } + if ($match_any_rule) { + $checked = "checked=\"1\""; + } else { + $checked = ""; + } - print "<br/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" name=\"match_any_rule\" id=\"match_any_rule\" $checked> + print "<br/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" name=\"match_any_rule\" id=\"match_any_rule\" $checked> <label for=\"match_any_rule\">".__('Match any rule')."</label>"; - if ($inverse) { - $checked = "checked=\"1\""; - } else { - $checked = ""; - } + if ($inverse) { + $checked = "checked=\"1\""; + } else { + $checked = ""; + } - print "<br/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" name=\"inverse\" id=\"inverse\" $checked> + print "<br/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" name=\"inverse\" id=\"inverse\" $checked> <label for=\"inverse\">".__('Inverse matching')."</label>"; - print "<p/>"; + print "<p/>"; - print "<div class=\"dlgButtons\">"; + print "<div class=\"dlgButtons\">"; - print "<div style=\"float : left\">"; - print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').removeFilter()\">". - __('Remove')."</button>"; - print "</div>"; + print "<div style=\"float : left\">"; + print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').removeFilter()\">". + __('Remove')."</button>"; + print "</div>"; - print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').test()\">". - __('Test')."</button> "; + print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').test()\">". + __('Test')."</button> "; - print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').execute()\">". - __('Save')."</button> "; + print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').execute()\">". + __('Save')."</button> "; - print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').hide()\">". - __('Cancel')."</button>"; + print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').hide()\">". + __('Cancel')."</button>"; - print "</div>"; + print "</div>"; + + } } private function getRuleName($rule) { - if (!$rule) $rule = json_decode($_REQUEST["rule"], true); + if (!$rule) $rule = json_decode(clean($_REQUEST["rule"]), true); $feeds = $rule["feed_id"]; $feeds_fmt = []; @@ -547,9 +555,15 @@ class Pref_Filters extends Handler_Protected { $feed = implode(", ", $feeds_fmt); - $result = $this->dbh->query("SELECT description FROM ttrss_filter_types - WHERE id = ".(int)$rule["filter_type"]); - $filter_type = $this->dbh->fetch_result($result, 0, "description"); + $sth = $this->pdo->prepare("SELECT description FROM ttrss_filter_types + WHERE id = ?"); + $sth->execute([(int)$rule["filter_type"]]); + + if ($row = $sth->fetch()) { + $filter_type = $row["description"]; + } else { + $filter_type = "?UNKNOWN?"; + } $inverse = isset($rule["inverse"]) ? "inverse" : ""; @@ -559,29 +573,35 @@ class Pref_Filters extends Handler_Protected { } function printRuleName() { - print $this->getRuleName(json_decode($_REQUEST["rule"], true)); + print $this->getRuleName(json_decode(clean($_REQUEST["rule"]), true)); } private function getActionName($action) { - $result = $this->dbh->query("SELECT description FROM - ttrss_filter_actions WHERE id = " .(int)$action["action_id"]); + $sth = $this->pdo->prepare("SELECT description FROM + ttrss_filter_actions WHERE id = ?"); + $sth->execute([(int)$action["action_id"]]); + + $title = ""; - $title = __($this->dbh->fetch_result($result, 0, "description")); + if ($row = $sth->fetch()) { - if ($action["action_id"] == 4 || $action["action_id"] == 6 || - $action["action_id"] == 7) + $title = __($row["description"]); + + if ($action["action_id"] == 4 || $action["action_id"] == 6 || + $action["action_id"] == 7) $title .= ": " . $action["action_param"]; - if ($action["action_id"] == 9) { - list ($pfclass, $pfaction) = explode(":", $action["action_param"]); + if ($action["action_id"] == 9) { + list ($pfclass, $pfaction) = explode(":", $action["action_param"]); - $filter_actions = PluginHost::getInstance()->get_filter_actions(); + $filter_actions = PluginHost::getInstance()->get_filter_actions(); - foreach ($filter_actions as $fclass => $factions) { - foreach ($factions as $faction) { - if ($pfaction == $faction["action"] && $pfclass == $fclass) { - $title .= ": " . $fclass . ": " . $faction["description"]; - break; + foreach ($filter_actions as $fclass => $factions) { + foreach ($factions as $faction) { + if ($pfaction == $faction["action"] && $pfclass == $fclass) { + $title .= ": " . $fclass . ": " . $faction["description"]; + break; + } } } } @@ -591,54 +611,64 @@ class Pref_Filters extends Handler_Protected { } function printActionName() { - print $this->getActionName(json_decode($_REQUEST["action"], true)); + print $this->getActionName(json_decode(clean($_REQUEST["action"]), true)); } function editSave() { - if ($_REQUEST["savemode"] && $_REQUEST["savemode"] == "test") { + if (clean($_REQUEST["savemode"] && $_REQUEST["savemode"]) == "test") { return $this->testFilter(); } -# print_r($_REQUEST); + $filter_id = clean($_REQUEST["id"]); + $enabled = checkbox_to_sql_bool(clean($_REQUEST["enabled"])); + $match_any_rule = checkbox_to_sql_bool(clean($_REQUEST["match_any_rule"])); + $inverse = checkbox_to_sql_bool(clean($_REQUEST["inverse"])); + $title = clean($_REQUEST["title"]); - $filter_id = $this->dbh->escape_string($_REQUEST["id"]); - $enabled = checkbox_to_sql_bool($this->dbh->escape_string($_REQUEST["enabled"])); - $match_any_rule = checkbox_to_sql_bool($this->dbh->escape_string($_REQUEST["match_any_rule"])); - $inverse = checkbox_to_sql_bool($this->dbh->escape_string($_REQUEST["inverse"])); - $title = $this->dbh->escape_string($_REQUEST["title"]); + $this->pdo->beginTransaction(); - $this->dbh->query("UPDATE ttrss_filters2 SET enabled = $enabled, - match_any_rule = $match_any_rule, - inverse = $inverse, - title = '$title' - WHERE id = '$filter_id' - AND owner_uid = ". $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_filters2 SET enabled = ?, + match_any_rule = ?, + inverse = ?, + title = ? + WHERE id = ? AND owner_uid = ?"); + + $sth->execute([$enabled, $match_any_rule, $inverse, $title, $filter_id, $_SESSION['uid']]); $this->saveRulesAndActions($filter_id); + $this->pdo->commit(); } function remove() { - $ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"])); + $ids = explode(",", clean($_REQUEST["ids"])); + $ids_qmarks = arr_qmarks($ids); - foreach ($ids as $id) { - $this->dbh->query("DELETE FROM ttrss_filters2 WHERE id = '$id' AND owner_uid = ". $_SESSION["uid"]); - } + $sth = $this->pdo->prepare("DELETE FROM ttrss_filters2 WHERE id IN ($ids_qmarks) + AND owner_uid = ?"); + $sth->execute(array_merge($ids, [$_SESSION['uid']])); } - private function saveRulesAndActions($filter_id) { + private function saveRulesAndActions($filter_id) + { - $this->dbh->query("DELETE FROM ttrss_filters2_rules WHERE filter_id = '$filter_id'"); - $this->dbh->query("DELETE FROM ttrss_filters2_actions WHERE filter_id = '$filter_id'"); + $sth = $this->pdo->prepare("DELETE FROM ttrss_filters2_rules WHERE filter_id = ?"); + $sth->execute([$filter_id]); + $sth = $this->pdo->prepare("DELETE FROM ttrss_filters2_actions WHERE filter_id = ?"); + $sth->execute([$filter_id]); + + if (!is_array(clean($_REQUEST["rule"]))) $_REQUEST["rule"] = []; + if (!is_array(clean($_REQUEST["action"]))) $_REQUEST["action"] = []; + if ($filter_id) { /* create rules */ $rules = array(); $actions = array(); - foreach ($_REQUEST["rule"] as $rule) { + foreach (clean($_REQUEST["rule"]) as $rule) { $rule = json_decode($rule, true); unset($rule["id"]); @@ -647,7 +677,7 @@ class Pref_Filters extends Handler_Protected { } } - foreach ($_REQUEST["action"] as $action) { + foreach (clean($_REQUEST["action"]) as $action) { $action = json_decode($action, true); unset($action["id"]); @@ -656,104 +686,83 @@ class Pref_Filters extends Handler_Protected { } } + $rsth = $this->pdo->prepare("INSERT INTO ttrss_filters2_rules + (filter_id, reg_exp,filter_type,feed_id,cat_id,match_on,inverse) VALUES + (?, ?, ?, NULL, NULL, ?, ?)"); + foreach ($rules as $rule) { if ($rule) { - $reg_exp = $this->dbh->escape_string(trim($rule["reg_exp"]), false); - $inverse = isset($rule["inverse"]) ? "true" : "false"; - - $filter_type = (int) $this->dbh->escape_string(trim($rule["filter_type"])); - $match_on = $this->dbh->escape_string(json_encode($rule["feed_id"])); - - /*if (strpos($feed_id, "CAT:") === 0) { - - $cat_filter = bool_to_sql_bool(true); - $cat_id = (int) substr($feed_id, 4); - $feed_id = "NULL"; + $reg_exp = trim($rule["reg_exp"]); + $inverse = isset($rule["inverse"]) ? 1 : 0; - if (!$cat_id) $cat_id = "NULL"; // Uncategorized - } else { - $cat_filter = bool_to_sql_bool(false); - $feed_id = (int) $feed_id; - $cat_id = "NULL"; - - if (!$feed_id) $feed_id = "NULL"; // Uncategorized - }*/ + $filter_type = (int)trim($rule["filter_type"]); + $match_on = json_encode($rule["feed_id"]); - $query = "INSERT INTO ttrss_filters2_rules - (filter_id, reg_exp,filter_type,feed_id,cat_id,match_on,inverse) VALUES - ('$filter_id', '$reg_exp', '$filter_type', NULL, NULL, '$match_on', $inverse)"; - - $this->dbh->query($query); + $rsth->execute([$filter_id, $reg_exp, $filter_type, $match_on, $inverse]); } } + $asth = $this->pdo->prepare("INSERT INTO ttrss_filters2_actions + (filter_id, action_id, action_param) VALUES + (?, ?, ?)"); + foreach ($actions as $action) { if ($action) { - $action_id = (int) $this->dbh->escape_string($action["action_id"]); - $action_param = $this->dbh->escape_string($action["action_param"]); - $action_param_label = $this->dbh->escape_string($action["action_param_label"]); + $action_id = (int)$action["action_id"]; + $action_param = $action["action_param"]; + $action_param_label = $action["action_param_label"]; if ($action_id == 7) { $action_param = $action_param_label; } if ($action_id == 6) { - $action_param = (int) str_replace("+", "", $action_param); + $action_param = (int)str_replace("+", "", $action_param); } - $query = "INSERT INTO ttrss_filters2_actions - (filter_id, action_id, action_param) VALUES - ('$filter_id', '$action_id', '$action_param')"; - - $this->dbh->query($query); + $asth->execute([$filter_id, $action_id, $action_param]); } } } - - } function add() { - if ($_REQUEST["savemode"] && $_REQUEST["savemode"] == "test") { + if (clean($_REQUEST["savemode"] && $_REQUEST["savemode"]) == "test") { return $this->testFilter(); } -# print_r($_REQUEST); - - $enabled = checkbox_to_sql_bool($_REQUEST["enabled"]); - $match_any_rule = checkbox_to_sql_bool($_REQUEST["match_any_rule"]); - $title = $this->dbh->escape_string($_REQUEST["title"]); - $inverse = checkbox_to_sql_bool($_REQUEST["inverse"]); + $enabled = checkbox_to_sql_bool(clean($_REQUEST["enabled"])); + $match_any_rule = checkbox_to_sql_bool(clean($_REQUEST["match_any_rule"])); + $title = clean($_REQUEST["title"]); + $inverse = checkbox_to_sql_bool(clean($_REQUEST["inverse"])); - $this->dbh->query("BEGIN"); + $this->pdo->beginTransaction(); /* create base filter */ - $result = $this->dbh->query("INSERT INTO ttrss_filters2 + $sth = $this->pdo->prepare("INSERT INTO ttrss_filters2 (owner_uid, match_any_rule, enabled, title, inverse) VALUES - (".$_SESSION["uid"].",$match_any_rule,$enabled, '$title', $inverse)"); + (?, ?, ?, ?, ?)"); - $result = $this->dbh->query("SELECT MAX(id) AS id FROM ttrss_filters2 - WHERE owner_uid = ".$_SESSION["uid"]); + $sth->execute([$_SESSION['uid'], $match_any_rule, $enabled, $title, $inverse]); - $filter_id = $this->dbh->fetch_result($result, 0, "id"); + $sth = $this->pdo->prepare("SELECT MAX(id) AS id FROM ttrss_filters2 + WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); - $this->saveRulesAndActions($filter_id); + if ($row = $sth->fetch()) { + $filter_id = $row['id']; + $this->saveRulesAndActions($filter_id); + } - $this->dbh->query("COMMIT"); + $this->pdo->commit(); } function index() { - $sort = $this->dbh->escape_string($_REQUEST["sort"]); - - if (!$sort || $sort == "undefined") { - $sort = "reg_exp"; - } - - $filter_search = $this->dbh->escape_string($_REQUEST["search"]); + $filter_search = clean($_REQUEST["search"]); if (array_key_exists("search", $_REQUEST)) { $_SESSION["prefs_filter_search"] = $filter_search; @@ -765,8 +774,6 @@ class Pref_Filters extends Handler_Protected { print "<div id=\"pref-filter-header\" dojoType=\"dijit.layout.ContentPane\" region=\"top\">"; print "<div id=\"pref-filter-toolbar\" dojoType=\"dijit.Toolbar\">"; - $filter_search = $this->dbh->escape_string($_REQUEST["search"]); - if (array_key_exists("search", $_REQUEST)) { $_SESSION["prefs_filter_search"] = $filter_search; } else { @@ -805,11 +812,6 @@ class Pref_Filters extends Handler_Protected { print "<button dojoType=\"dijit.form.Button\" onclick=\"return removeSelectedFilters()\">". __('Remove')."</button> "; - if (defined('_ENABLE_FEED_DEBUGGING')) { - print "<button dojoType=\"dijit.form.Button\" onclick=\"rescore_all_feeds()\">". - __('Rescore articles')."</button> "; - } - print "</div>"; # toolbar print "</div>"; # toolbar-frame print "<div id=\"pref-filter-content\" dojoType=\"dijit.layout.ContentPane\" region=\"center\">"; @@ -946,7 +948,7 @@ class Pref_Filters extends Handler_Protected { } function newrule() { - $rule = json_decode($_REQUEST["rule"], true); + $rule = json_decode(clean($_REQUEST["rule"]), true); if ($rule) { $reg_exp = htmlspecialchars($rule["reg_exp"]); @@ -960,21 +962,14 @@ class Pref_Filters extends Handler_Protected { $inverse_checked = ""; } - /*if (strpos($feed_id, "CAT:") === 0) { - $feed_id = substr($feed_id, 4); - $cat_filter = true; - } else { - $cat_filter = false; - }*/ - print "<form name='filter_new_rule_form' id='filter_new_rule_form'>"; - $result = $this->dbh->query("SELECT id,description + $res = $this->pdo->query("SELECT id,description FROM ttrss_filter_types WHERE id != 5 ORDER BY description"); $filter_types = array(); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $res->fetch()) { $filter_types[$line["id"]] = __($line["description"]); } @@ -1027,10 +1022,10 @@ class Pref_Filters extends Handler_Protected { } function newaction() { - $action = json_decode($_REQUEST["action"], true); + $action = json_decode(clean($_REQUEST["action"]), true); if ($action) { - $action_param = $this->dbh->escape_string($action["action_param"]); + $action_param = $action["action_param"]; $action_id = (int)$action["action_id"]; } else { $action_param = ""; @@ -1046,10 +1041,10 @@ class Pref_Filters extends Handler_Protected { print "<select name=\"action_id\" dojoType=\"dijit.form.Select\" onchange=\"filterDlgCheckAction(this)\">"; - $result = $this->dbh->query("SELECT id,description FROM ttrss_filter_actions + $res = $this->pdo->query("SELECT id,description FROM ttrss_filter_actions ORDER BY name"); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $res->fetch()) { $is_selected = ($line["id"] == $action_id) ? "selected='1'" : ""; printf("<option $is_selected value='%d'>%s</option>", $line["id"], __($line["description"])); } @@ -1121,60 +1116,72 @@ class Pref_Filters extends Handler_Protected { private function getFilterName($id) { - $result = $this->dbh->query( + $sth = $this->pdo->prepare( "SELECT title,match_any_rule,COUNT(DISTINCT r.id) AS num_rules,COUNT(DISTINCT a.id) AS num_actions FROM ttrss_filters2 AS f LEFT JOIN ttrss_filters2_rules AS r ON (r.filter_id = f.id) LEFT JOIN ttrss_filters2_actions AS a - ON (a.filter_id = f.id) WHERE f.id = '$id' GROUP BY f.title, f.match_any_rule"); + ON (a.filter_id = f.id) WHERE f.id = ? GROUP BY f.title, f.match_any_rule"); + $sth->execute([$id]); - $title = $this->dbh->fetch_result($result, 0, "title"); - $num_rules = $this->dbh->fetch_result($result, 0, "num_rules"); - $num_actions = $this->dbh->fetch_result($result, 0, "num_actions"); - $match_any_rule = sql_bool_to_bool($this->dbh->fetch_result($result, 0, "match_any_rule")); + if ($row = $sth->fetch()) { - if (!$title) $title = __("[No caption]"); + $title = $row["title"]; + $num_rules = $row["num_rules"]; + $num_actions = $row["num_actions"]; + $match_any_rule = $row["match_any_rule"]; - $title = sprintf(_ngettext("%s (%d rule)", "%s (%d rules)", (int) $num_rules), $title, $num_rules); + if (!$title) $title = __("[No caption]"); + $title = sprintf(_ngettext("%s (%d rule)", "%s (%d rules)", (int) $num_rules), $title, $num_rules); - $result = $this->dbh->query( - "SELECT * FROM ttrss_filters2_actions WHERE filter_id = '$id' ORDER BY id LIMIT 1"); + $sth = $this->pdo->prepare("SELECT * FROM ttrss_filters2_actions + WHERE filter_id = ? ORDER BY id LIMIT 1"); + $sth->execute([$id]); - $actions = ""; + $actions = ""; - if ($this->dbh->num_rows($result) > 0) { - $line = $this->dbh->fetch_assoc($result); - $actions = $this->getActionName($line); + if ($line = $sth->fetch()) { + $actions = $this->getActionName($line); - $num_actions -= 1; - } + $num_actions -= 1; + } + + if ($match_any_rule) $title .= " (" . __("matches any rule") . ")"; - if ($match_any_rule) $title .= " (" . __("matches any rule") . ")"; + if ($num_actions > 0) + $actions = sprintf(_ngettext("%s (+%d action)", "%s (+%d actions)", (int) $num_actions), $actions, $num_actions); - if ($num_actions > 0) - $actions = sprintf(_ngettext("%s (+%d action)", "%s (+%d actions)", (int) $num_actions), $actions, $num_actions); + return [$title, $actions]; + } - return array($title, $actions); + return []; } function join() { - $ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"])); + $ids = explode(",", clean($_REQUEST["ids"])); if (count($ids) > 1) { $base_id = array_shift($ids); - $ids_str = join(",", $ids); + $ids_qmarks = arr_qmarks($ids); + + $this->pdo->beginTransaction(); + + $sth = $this->pdo->prepare("UPDATE ttrss_filters2_rules + SET filter_id = ? WHERE filter_id IN ($ids_qmarks)"); + $sth->execute(array_merge([$base_id], $ids)); + + $sth = $this->pdo->prepare("UPDATE ttrss_filters2_actions + SET filter_id = ? WHERE filter_id IN ($ids_qmarks)"); + $sth->execute(array_merge([$base_id], $ids)); - $this->dbh->query("BEGIN"); - $this->dbh->query("UPDATE ttrss_filters2_rules - SET filter_id = '$base_id' WHERE filter_id IN ($ids_str)"); - $this->dbh->query("UPDATE ttrss_filters2_actions - SET filter_id = '$base_id' WHERE filter_id IN ($ids_str)"); + $sth = $this->pdo->prepare("DELETE FROM ttrss_filters2 WHERE id IN ($ids_qmarks)"); + $sth->execute($ids); - $this->dbh->query("DELETE FROM ttrss_filters2 WHERE id IN ($ids_str)"); - $this->dbh->query("UPDATE ttrss_filters2 SET match_any_rule = true WHERE id = '$base_id'"); + $sth = $this->pdo->prepare("UPDATE ttrss_filters2 SET match_any_rule = true WHERE id = ?"); + $sth->execute([$base_id]); - $this->dbh->query("COMMIT"); + $this->pdo->commit(); $this->optimizeFilter($base_id); @@ -1182,14 +1189,17 @@ class Pref_Filters extends Handler_Protected { } private function optimizeFilter($id) { - $this->dbh->query("BEGIN"); - $result = $this->dbh->query("SELECT * FROM ttrss_filters2_actions - WHERE filter_id = '$id'"); + + $this->pdo->beginTransaction(); + + $sth = $this->pdo->prepare("SELECT * FROM ttrss_filters2_actions + WHERE filter_id = ?"); + $sth->execute([$id]); $tmp = array(); $dupe_ids = array(); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { $id = $line["id"]; unset($line["id"]); @@ -1202,17 +1212,18 @@ class Pref_Filters extends Handler_Protected { if (count($dupe_ids) > 0) { $ids_str = join(",", $dupe_ids); - $this->dbh->query("DELETE FROM ttrss_filters2_actions - WHERE id IN ($ids_str)"); + + $this->pdo->query("DELETE FROM ttrss_filters2_actions WHERE id IN ($ids_str)"); } - $result = $this->dbh->query("SELECT * FROM ttrss_filters2_rules - WHERE filter_id = '$id'"); + $sth = $this->pdo->prepare("SELECT * FROM ttrss_filters2_rules + WHERE filter_id = ?"); + $sth->execute([$id]); $tmp = array(); $dupe_ids = array(); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { $id = $line["id"]; unset($line["id"]); @@ -1225,10 +1236,10 @@ class Pref_Filters extends Handler_Protected { if (count($dupe_ids) > 0) { $ids_str = join(",", $dupe_ids); - $this->dbh->query("DELETE FROM ttrss_filters2_rules - WHERE id IN ($ids_str)"); + + $this->pdo->query("DELETE FROM ttrss_filters2_rules WHERE id IN ($ids_str)"); } - $this->dbh->query("COMMIT"); + $this->pdo->commit(); } }
\ No newline at end of file diff --git a/classes/pref/labels.php b/classes/pref/labels.php index 5720a1f4b..38ec850a6 100644 --- a/classes/pref/labels.php +++ b/classes/pref/labels.php @@ -8,80 +8,80 @@ class Pref_Labels extends Handler_Protected { } function edit() { - $label_id = $this->dbh->escape_string($_REQUEST['id']); + $label_id = clean($_REQUEST['id']); - $result = $this->dbh->query("SELECT * FROM ttrss_labels2 WHERE - id = '$label_id' AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT * FROM ttrss_labels2 WHERE + id = ? AND owner_uid = ?"); + $sth->execute([$label_id, $_SESSION['uid']]); - $line = $this->dbh->fetch_assoc($result); + if ($line = $sth->fetch()) { - print_hidden("id", "$label_id"); - print_hidden("op", "pref-labels"); - print_hidden("method", "save"); + print_hidden("id", "$label_id"); + print_hidden("op", "pref-labels"); + print_hidden("method", "save"); - print "<div class=\"dlgSec\">".__("Caption")."</div>"; + print "<div class=\"dlgSec\">".__("Caption")."</div>"; - print "<div class=\"dlgSecCont\">"; + print "<div class=\"dlgSecCont\">"; - $fg_color = $line['fg_color']; - $bg_color = $line['bg_color']; + $fg_color = $line['fg_color']; + $bg_color = $line['bg_color']; - print "<span class=\"labelColorIndicator\" id=\"label-editor-indicator\" style='color : $fg_color; background-color : $bg_color; margin-bottom : 4px; margin-right : 4px'>α</span>"; + print "<span class=\"labelColorIndicator\" id=\"label-editor-indicator\" style='color : $fg_color; background-color : $bg_color; margin-bottom : 4px; margin-right : 4px'>α</span>"; - print "<input style=\"font-size : 16px\" name=\"caption\" + print "<input style=\"font-size : 16px\" name=\"caption\" dojoType=\"dijit.form.ValidationTextBox\" required=\"true\" value=\"".htmlspecialchars($line['caption'])."\">"; - print "</div>"; - print "<div class=\"dlgSec\">" . __("Colors") . "</div>"; - print "<div class=\"dlgSecCont\">"; + print "</div>"; + print "<div class=\"dlgSec\">" . __("Colors") . "</div>"; + print "<div class=\"dlgSecCont\">"; - print "<table cellspacing=\"0\">"; + print "<table cellspacing=\"0\">"; - print "<tr><td>".__("Foreground:")."</td><td>".__("Background:"). - "</td></tr>"; + print "<tr><td>".__("Foreground:")."</td><td>".__("Background:"). + "</td></tr>"; - print "<tr><td style='padding-right : 10px'>"; + print "<tr><td style='padding-right : 10px'>"; - print "<input dojoType=\"dijit.form.TextBox\" + print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" id=\"labelEdit_fgColor\" name=\"fg_color\" value=\"$fg_color\">"; - print "<input dojoType=\"dijit.form.TextBox\" + print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" id=\"labelEdit_bgColor\" name=\"bg_color\" value=\"$bg_color\">"; - print "<div dojoType=\"dijit.ColorPalette\"> + print "<div dojoType=\"dijit.ColorPalette\"> <script type=\"dojo/method\" event=\"onChange\" args=\"fg_color\"> dijit.byId(\"labelEdit_fgColor\").attr('value', fg_color); $('label-editor-indicator').setStyle({color: fg_color}); </script> - </div>"; - print "</div>"; + </div>"; + print "</div>"; - print "</td><td>"; + print "</td><td>"; - print "<div dojoType=\"dijit.ColorPalette\"> + print "<div dojoType=\"dijit.ColorPalette\"> <script type=\"dojo/method\" event=\"onChange\" args=\"bg_color\"> dijit.byId(\"labelEdit_bgColor\").attr('value', bg_color); $('label-editor-indicator').setStyle({backgroundColor: bg_color}); </script> - </div>"; - print "</div>"; + </div>"; + print "</div>"; - print "</td></tr></table>"; - print "</div>"; + print "</td></tr></table>"; + print "</div>"; # print "</form>"; - print "<div class=\"dlgButtons\">"; - print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('labelEditDlg').execute()\">". - __('Save')."</button>"; - print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('labelEditDlg').hide()\">". - __('Cancel')."</button>"; - print "</div>"; - - return; + print "<div class=\"dlgButtons\">"; + print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('labelEditDlg').execute()\">". + __('Save')."</button>"; + print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('labelEditDlg').hide()\">". + __('Cancel')."</button>"; + print "</div>"; + } } function getlabeltree() { @@ -90,12 +90,13 @@ class Pref_Labels extends Handler_Protected { $root['name'] = __('Labels'); $root['items'] = array(); - $result = $this->dbh->query("SELECT * + $sth = $this->pdo->prepare("SELECT * FROM ttrss_labels2 - WHERE owner_uid = ".$_SESSION["uid"]." + WHERE owner_uid = ? ORDER BY caption"); + $sth->execute([$_SESSION['uid']]); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { $label = array(); $label['id'] = 'LABEL:' . $line['id']; $label['bare_id'] = $line['id']; @@ -118,86 +119,94 @@ class Pref_Labels extends Handler_Protected { } function colorset() { - $kind = $this->dbh->escape_string($_REQUEST["kind"]); - $ids = explode(',', $this->dbh->escape_string($_REQUEST["ids"])); - $color = $this->dbh->escape_string($_REQUEST["color"]); - $fg = $this->dbh->escape_string($_REQUEST["fg"]); - $bg = $this->dbh->escape_string($_REQUEST["bg"]); + $kind = clean($_REQUEST["kind"]); + $ids = explode(',', clean($_REQUEST["ids"])); + $color = clean($_REQUEST["color"]); + $fg = clean($_REQUEST["fg"]); + $bg = clean($_REQUEST["bg"]); foreach ($ids as $id) { if ($kind == "fg" || $kind == "bg") { - $this->dbh->query("UPDATE ttrss_labels2 SET - ${kind}_color = '$color' WHERE id = '$id' - AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_labels2 SET + ${kind}_color = ? WHERE id = ? + AND owner_uid = ?"); + + $sth->execute([$color, $id, $_SESSION['uid']]); + } else { - $this->dbh->query("UPDATE ttrss_labels2 SET - fg_color = '$fg', bg_color = '$bg' WHERE id = '$id' - AND owner_uid = " . $_SESSION["uid"]); + + $sth = $this->pdo->prepare("UPDATE ttrss_labels2 SET + fg_color = ?, bg_color = ? WHERE id = ? + AND owner_uid = ?"); + + $sth->execute([$fg, $bg, $id, $_SESSION['uid']]); } - $caption = $this->dbh->escape_string(Labels::find_caption($id, $_SESSION["uid"])); + $caption = Labels::find_caption($id, $_SESSION["uid"]); /* Remove cached data */ - $this->dbh->query("UPDATE ttrss_user_entries SET label_cache = '' - WHERE label_cache LIKE '%$caption%' AND owner_uid = " . $_SESSION["uid"]); - + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET label_cache = '' + WHERE label_cache LIKE ? AND owner_uid = ?"); + $sth->execute(["%$caption%", $_SESSION['uid']]); } - - return; } function colorreset() { - $ids = explode(',', $this->dbh->escape_string($_REQUEST["ids"])); + $ids = explode(',', clean($_REQUEST["ids"])); foreach ($ids as $id) { - $this->dbh->query("UPDATE ttrss_labels2 SET - fg_color = '', bg_color = '' WHERE id = '$id' - AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_labels2 SET + fg_color = '', bg_color = '' WHERE id = ? + AND owner_uid = ?"); + $sth->execute([$id, $_SESSION['uid']]); - $caption = $this->dbh->escape_string(Labels::find_caption($id, $_SESSION["uid"])); + $caption = Labels::find_caption($id, $_SESSION["uid"]); /* Remove cached data */ - $this->dbh->query("UPDATE ttrss_user_entries SET label_cache = '' - WHERE label_cache LIKE '%$caption%' AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET label_cache = '' + WHERE label_cache LIKE ? AND owner_uid = ?"); + $sth->execute(["%$caption%", $_SESSION['uid']]); } - } function save() { - $id = $this->dbh->escape_string($_REQUEST["id"]); - $caption = $this->dbh->escape_string(trim($_REQUEST["caption"])); + $id = clean($_REQUEST["id"]); + $caption = trim(clean($_REQUEST["caption"])); - $this->dbh->query("BEGIN"); + $this->pdo->beginTransaction(); - $result = $this->dbh->query("SELECT caption FROM ttrss_labels2 - WHERE id = '$id' AND owner_uid = ". $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT caption FROM ttrss_labels2 + WHERE id = ? AND owner_uid = ?"); + $sth->execute([$id, $_SESSION['uid']]); - if ($this->dbh->num_rows($result) != 0) { - $old_caption = $this->dbh->fetch_result($result, 0, "caption"); + if ($row = $sth->fetch()) { + $old_caption = $row["caption"]; - $result = $this->dbh->query("SELECT id FROM ttrss_labels2 - WHERE caption = '$caption' AND owner_uid = ". $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_labels2 + WHERE caption = ? AND owner_uid = ?"); + $sth->execute([$caption, $_SESSION['uid']]); - if ($this->dbh->num_rows($result) == 0) { + if (!$sth->fetch()) { if ($caption) { - $result = $this->dbh->query("UPDATE ttrss_labels2 SET - caption = '$caption' WHERE id = '$id' AND - owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_labels2 SET + caption = ? WHERE id = ? AND + owner_uid = ?"); + $sth->execute([$caption, $id, $_SESSION['uid']]); /* Update filters that reference label being renamed */ - $old_caption = $this->dbh->escape_string($old_caption); - - $this->dbh->query("UPDATE ttrss_filters2_actions SET - action_param = '$caption' WHERE action_param = '$old_caption' + $sth = $this->pdo->prepare("UPDATE ttrss_filters2_actions SET + action_param = ? WHERE action_param = ? AND action_id = 7 - AND filter_id IN (SELECT id FROM ttrss_filters2 WHERE owner_uid = ".$_SESSION["uid"].")"); + AND filter_id IN (SELECT id FROM ttrss_filters2 WHERE owner_uid = ?)"); - print $_REQUEST["value"]; + $sth->execute([$caption, $old_caption, $_SESSION['uid']]); + + print clean($_REQUEST["value"]); } else { print $old_caption; } @@ -206,14 +215,13 @@ class Pref_Labels extends Handler_Protected { } } - $this->dbh->query("COMMIT"); + $this->pdo->commit(); - return; } function remove() { - $ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"])); + $ids = explode(",", clean($_REQUEST["ids"])); foreach ($ids as $id) { Labels::remove($id, $_SESSION["uid"]); @@ -222,8 +230,8 @@ class Pref_Labels extends Handler_Protected { } function add() { - $caption = $this->dbh->escape_string($_REQUEST["caption"]); - $output = $this->dbh->escape_string($_REQUEST["output"]); + $caption = clean($_REQUEST["caption"]); + $output = clean($_REQUEST["output"]); if ($caption) { diff --git a/classes/pref/prefs.php b/classes/pref/prefs.php index cf27a72df..5fc76b32c 100644 --- a/classes/pref/prefs.php +++ b/classes/pref/prefs.php @@ -60,9 +60,9 @@ class Pref_Prefs extends Handler_Protected { function changepassword() { - $old_pw = $_POST["old_password"]; - $new_pw = $_POST["new_password"]; - $con_pw = $_POST["confirm_password"]; + $old_pw = clean($_POST["old_password"]); + $new_pw = clean($_POST["new_password"]); + $con_pw = clean($_POST["confirm_password"]); if ($old_pw == "") { print "ERROR: ".format_error("Old password cannot be blank."); @@ -89,7 +89,7 @@ class Pref_Prefs extends Handler_Protected { } function saveconfig() { - $boolean_prefs = explode(",", $_POST["boolean_prefs"]); + $boolean_prefs = explode(",", clean($_POST["boolean_prefs"])); foreach ($boolean_prefs as $pref) { if (!isset($_POST[$pref])) $_POST[$pref] = 'false'; @@ -99,14 +99,14 @@ class Pref_Prefs extends Handler_Protected { foreach (array_keys($_POST) as $pref_name) { - $pref_name = $this->dbh->escape_string($pref_name); - $value = $this->dbh->escape_string($_POST[$pref_name]); + $value = $_POST[$pref_name]; if ($pref_name == 'DIGEST_PREFERRED_TIME') { if (get_pref('DIGEST_PREFERRED_TIME') != $value) { - $this->dbh->query("UPDATE ttrss_users SET - last_digest_sent = NULL WHERE id = " . $_SESSION['uid']); + $sth = $this->pdo->prepare("UPDATE ttrss_users SET + last_digest_sent = NULL WHERE id = ?"); + $sth->execute([$_SESSION['uid']]); } } @@ -129,13 +129,13 @@ class Pref_Prefs extends Handler_Protected { function changeemail() { - $email = $this->dbh->escape_string($_POST["email"]); - $full_name = $this->dbh->escape_string($_POST["full_name"]); - + $email = clean($_POST["email"]); + $full_name = clean($_POST["full_name"]); $active_uid = $_SESSION["uid"]; - $this->dbh->query("UPDATE ttrss_users SET email = '$email', - full_name = '$full_name' WHERE id = '$active_uid'"); + $sth = $this->pdo->prepare("UPDATE ttrss_users SET email = ?, + full_name = ? WHERE id = ?"); + $sth->execute([$email, $full_name, $active_uid]); print __("Your personal data has been saved."); @@ -146,14 +146,10 @@ class Pref_Prefs extends Handler_Protected { $_SESSION["prefs_op_result"] = "reset-to-defaults"; - if ($_SESSION["profile"]) { - $profile_qpart = "profile = '" . $_SESSION["profile"] . "'"; - } else { - $profile_qpart = "profile IS NULL"; - } - - $this->dbh->query("DELETE FROM ttrss_user_prefs - WHERE $profile_qpart AND owner_uid = ".$_SESSION["uid"]); + $sth = $this->pdo->query("DELETE FROM ttrss_user_prefs + WHERE (profile = :profile OR (:profile IS NULL AND profile IS NULL)) + AND owner_uid = :uid"); + $sth->execute([":profile" => $_SESSION['profile'], ":uid" => $_SESSION['uid']]); initialize_user_prefs($_SESSION["uid"], $_SESSION["profile"]); @@ -202,13 +198,15 @@ class Pref_Prefs extends Handler_Protected { print "<h2>" . __("Personal data") . "</h2>"; - $result = $this->dbh->query("SELECT email,full_name,otp_enabled, + $sth = $this->pdo->prepare("SELECT email,full_name,otp_enabled, access_level FROM ttrss_users - WHERE id = ".$_SESSION["uid"]); + WHERE id = ?"); + $sth->execute([$_SESSION["uid"]]); + $row = $sth->fetch(); - $email = htmlspecialchars($this->dbh->fetch_result($result, 0, "email")); - $full_name = htmlspecialchars($this->dbh->fetch_result($result, 0, "full_name")); - $otp_enabled = sql_bool_to_bool($this->dbh->fetch_result($result, 0, "otp_enabled")); + $email = htmlspecialchars($row["email"]); + $full_name = htmlspecialchars($row["full_name"]); + $otp_enabled = sql_bool_to_bool($row["otp_enabled"]); print "<tr><td width=\"40%\">".__('Full name')."</td>"; print "<td class=\"prefValue\"><input dojoType=\"dijit.form.ValidationTextBox\" name=\"full_name\" required=\"1\" @@ -219,7 +217,7 @@ class Pref_Prefs extends Handler_Protected { if (!SINGLE_USER_MODE && !$_SESSION["hide_hello"]) { - $access_level = $this->dbh->fetch_result($result, 0, "access_level"); + $access_level = $row["access_level"]; print "<tr><td width=\"40%\">".__('Access level')."</td>"; print "<td>" . $access_level_names[$access_level] . "</td></tr>"; } @@ -246,14 +244,6 @@ class Pref_Prefs extends Handler_Protected { print "<div style='display : none' id='pwd_change_infobox'></div>"; - $result = $this->dbh->query("SELECT id FROM ttrss_users - WHERE id = ".$_SESSION["uid"]." AND pwd_hash - = 'SHA1:5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8'"); - - if ($this->dbh->num_rows($result) != 0) { - print format_warning(__("Your password is at default value, please change it."), "default_pass_warning"); - } - print "<form dojoType=\"dijit.form.Form\">"; print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\"> @@ -468,31 +458,22 @@ class Pref_Prefs extends Handler_Protected { if ($_SESSION["profile"]) { initialize_user_prefs($_SESSION["uid"], $_SESSION["profile"]); - $profile_qpart = "profile = '" . $_SESSION["profile"] . "'"; } else { initialize_user_prefs($_SESSION["uid"]); - $profile_qpart = "profile IS NULL"; } - /* if ($_SESSION["prefs_show_advanced"]) - $access_query = "true"; - else - $access_query = "(access_level = 0 AND section_id != 3)"; */ - - $access_query = 'true'; - - $result = $this->dbh->query("SELECT DISTINCT + $sth = $this->pdo->prepare("SELECT DISTINCT ttrss_user_prefs.pref_name,value,type_name, ttrss_prefs_sections.order_id, def_value,section_id FROM ttrss_prefs,ttrss_prefs_types,ttrss_prefs_sections,ttrss_user_prefs WHERE type_id = ttrss_prefs_types.id AND - $profile_qpart AND + (profile = :profile OR (:profile IS NULL AND profile IS NULL)) AND section_id = ttrss_prefs_sections.id AND ttrss_user_prefs.pref_name = ttrss_prefs.pref_name AND - $access_query AND - owner_uid = ".$_SESSION["uid"]." + owner_uid = :uid ORDER BY ttrss_prefs_sections.order_id,pref_name"); + $sth->execute([":uid" => $_SESSION['uid'], ":profile" => $_SESSION['profile']]); $lnum = 0; @@ -500,7 +481,7 @@ class Pref_Prefs extends Handler_Protected { $listed_boolean_prefs = array(); - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { if (in_array($line["pref_name"], $prefs_blacklist)) { continue; @@ -872,20 +853,25 @@ class Pref_Prefs extends Handler_Protected { require_once "lib/otphp/lib/totp.php"; require_once "lib/phpqrcode/phpqrcode.php"; - $result = $this->dbh->query("SELECT login,salt,otp_enabled + $sth = $this->pdo->prepare("SELECT login,salt,otp_enabled FROM ttrss_users - WHERE id = ".$_SESSION["uid"]); + WHERE id = ?"); + $sth->execute([$_SESSION['uid']]); + + if ($row = $sth->fetch()) { + + $base32 = new Base32(); - $base32 = new Base32(); + $login = $row["login"]; + $otp_enabled = sql_bool_to_bool($row["otp_enabled"]); - $login = $this->dbh->fetch_result($result, 0, "login"); - $otp_enabled = sql_bool_to_bool($this->dbh->fetch_result($result, 0, "otp_enabled")); + if (!$otp_enabled) { + $secret = $base32->encode(sha1($row["salt"])); - if (!$otp_enabled) { - $secret = $base32->encode(sha1($this->dbh->fetch_result($result, 0, "salt"))); - print QRcode::png("otpauth://totp/".urlencode($login). - "?secret=$secret&issuer=".urlencode("Tiny Tiny RSS")); + QRcode::png("otpauth://totp/".urlencode($login). + "?secret=$secret&issuer=".urlencode("Tiny Tiny RSS")); + } } } @@ -894,47 +880,65 @@ class Pref_Prefs extends Handler_Protected { require_once "lib/otphp/lib/otp.php"; require_once "lib/otphp/lib/totp.php"; - $password = $_REQUEST["password"]; - $otp = $_REQUEST["otp"]; + $password = clean($_REQUEST["password"]); + $otp = clean($_REQUEST["otp"]); $authenticator = PluginHost::getInstance()->get_plugin($_SESSION["auth_module"]); if ($authenticator->check_password($_SESSION["uid"], $password)) { - $result = $this->dbh->query("SELECT salt + $sth = $this->pdo->query("SELECT salt FROM ttrss_users - WHERE id = ".$_SESSION["uid"]); + WHERE id = ?"); + $sth->execute([$_SESSION['uid']]); - $base32 = new Base32(); + if ($row = $sth->fetch()) { - $secret = $base32->encode(sha1($this->dbh->fetch_result($result, 0, "salt"))); - $topt = new \OTPHP\TOTP($secret); + $base32 = new Base32(); - $otp_check = $topt->now(); + $secret = $base32->encode(sha1($row["salt"])); + $topt = new \OTPHP\TOTP($secret); - if ($otp == $otp_check) { - $this->dbh->query("UPDATE ttrss_users SET otp_enabled = true WHERE - id = " . $_SESSION["uid"]); + $otp_check = $topt->now(); - print "OK"; - } else { - print "ERROR:".__("Incorrect one time password"); + if ($otp == $otp_check) { + $sth = $this->pdo->prepare("UPDATE ttrss_users + SET otp_enabled = true WHERE id = ?"); + + $sth->execute([$_SESSION['uid']]); + + print "OK"; + } else { + print "ERROR:".__("Incorrect one time password"); + } } + } else { print "ERROR:".__("Incorrect password"); } } + static function isdefaultpassword() { + $authenticator = PluginHost::getInstance()->get_plugin($_SESSION["auth_module"]); + + if ($authenticator->check_password($_SESSION["uid"], "password")) { + return true; + } + + return false; + } + function otpdisable() { - $password = $this->dbh->escape_string($_REQUEST["password"]); + $password = clean($_REQUEST["password"]); $authenticator = PluginHost::getInstance()->get_plugin($_SESSION["auth_module"]); if ($authenticator->check_password($_SESSION["uid"], $password)) { - $this->dbh->query("UPDATE ttrss_users SET otp_enabled = false WHERE - id = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_users SET otp_enabled = false WHERE + id = ?"); + $sth->execute([$_SESSION['uid']]); print "OK"; } else { @@ -944,8 +948,8 @@ class Pref_Prefs extends Handler_Protected { } function setplugins() { - if (is_array($_REQUEST["plugins"])) - $plugins = join(",", $_REQUEST["plugins"]); + if (is_array(clean($_REQUEST["plugins"]))) + $plugins = join(",", clean($_REQUEST["plugins"])); else $plugins = ""; @@ -953,7 +957,7 @@ class Pref_Prefs extends Handler_Protected { } function clearplugindata() { - $name = $this->dbh->escape_string($_REQUEST["name"]); + $name = clean($_REQUEST["name"]); PluginHost::getInstance()->clear_data(PluginHost::getInstance()->get_plugin($name)); } @@ -1007,8 +1011,9 @@ class Pref_Prefs extends Handler_Protected { print "</div>"; - $result = $this->dbh->query("SELECT title,id FROM ttrss_settings_profiles - WHERE owner_uid = ".$_SESSION["uid"]." ORDER BY title"); + $sth = $this->pdo->prepare("SELECT title,id FROM ttrss_settings_profiles + WHERE owner_uid = ? ORDER BY title"); + $sth->execute([$_SESSION['uid']]); print "<div class=\"prefProfileHolder\">"; @@ -1038,7 +1043,7 @@ class Pref_Prefs extends Handler_Protected { $lnum = 1; - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { $profile_id = $line["id"]; $this_row_id = "id=\"FCATR-$profile_id\""; diff --git a/classes/pref/system.php b/classes/pref/system.php index 56c0fec94..2099ebb9c 100644 --- a/classes/pref/system.php +++ b/classes/pref/system.php @@ -20,7 +20,7 @@ class Pref_System extends Handler_Protected { } function clearLog() { - $this->dbh->query("DELETE FROM ttrss_error_log"); + $this->pdo->query("DELETE FROM ttrss_error_log"); } function index() { @@ -30,7 +30,7 @@ class Pref_System extends Handler_Protected { if (LOG_DESTINATION == "sql") { - $result = $this->dbh->query("SELECT errno, errstr, filename, lineno, + $res = $this->pdo->query("SELECT errno, errstr, filename, lineno, created_at, login, context FROM ttrss_error_log LEFT JOIN ttrss_users ON (owner_uid = ttrss_users.id) ORDER BY ttrss_error_log.id DESC @@ -52,7 +52,7 @@ class Pref_System extends Handler_Protected { <td width='5%'>".__("Date")."</td> </tr>"; - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $res->fetch()) { print "<tr class=\"errrow\">"; foreach ($line as $k => $v) { diff --git a/classes/pref/users.php b/classes/pref/users.php index a937a2409..d65af1680 100644 --- a/classes/pref/users.php +++ b/classes/pref/users.php @@ -25,66 +25,71 @@ class Pref_Users extends Handler_Protected { print "<form id=\"user_edit_form\" onsubmit='return false' dojoType=\"dijit.form.Form\">"; - $id = (int) $this->dbh->escape_string($_REQUEST["id"]); + $id = (int) clean($_REQUEST["id"]); print_hidden("id", "$id"); print_hidden("op", "pref-users"); print_hidden("method", "editSave"); - $result = $this->dbh->query("SELECT * FROM ttrss_users WHERE id = '$id'"); + $sth = $this->pdo->prepare("SELECT * FROM ttrss_users WHERE id = ?"); + $sth->execute([$id]); - $login = $this->dbh->fetch_result($result, 0, "login"); - $access_level = $this->dbh->fetch_result($result, 0, "access_level"); - $email = $this->dbh->fetch_result($result, 0, "email"); + if ($row = $sth->fetch()) { - $sel_disabled = ($id == $_SESSION["uid"] || $login == "admin") ? "disabled" : ""; + $login = $row["login"]; + $access_level = $row["access_level"]; + $email = $row["email"]; - print "<div class=\"dlgSec\">".__("User")."</div>"; - print "<div class=\"dlgSecCont\">"; + $sel_disabled = ($id == $_SESSION["uid"] || $login == "admin") ? "disabled" : ""; - if ($sel_disabled) { - print_hidden("login", "$login"); - } + print "<div class=\"dlgSec\">".__("User")."</div>"; + print "<div class=\"dlgSecCont\">"; - print "<input size=\"30\" style=\"font-size : 16px\" - dojoType=\"dijit.form.ValidationTextBox\" required=\"1\" - $sel_disabled - name=\"login\" value=\"$login\">"; + if ($sel_disabled) { + print_hidden("login", "$login"); + } - print "</div>"; + print "<input size=\"30\" style=\"font-size : 16px\" + dojoType=\"dijit.form.ValidationTextBox\" required=\"1\" + $sel_disabled + name=\"login\" value=\"$login\">"; - print "<div class=\"dlgSec\">".__("Authentication")."</div>"; - print "<div class=\"dlgSecCont\">"; + print "</div>"; - print __('Access level: ') . " "; + print "<div class=\"dlgSec\">".__("Authentication")."</div>"; + print "<div class=\"dlgSecCont\">"; - if (!$sel_disabled) { - print_select_hash("access_level", $access_level, $access_level_names, - "dojoType=\"dijit.form.Select\" $sel_disabled"); - } else { - print_select_hash("", $access_level, $access_level_names, - "dojoType=\"dijit.form.Select\" $sel_disabled"); - print_hidden("access_level", "$access_level"); - } + print __('Access level: ') . " "; + + if (!$sel_disabled) { + print_select_hash("access_level", $access_level, $access_level_names, + "dojoType=\"dijit.form.Select\" $sel_disabled"); + } else { + print_select_hash("", $access_level, $access_level_names, + "dojoType=\"dijit.form.Select\" $sel_disabled"); + print_hidden("access_level", "$access_level"); + } - print "<hr/>"; + print "<hr/>"; - print "<input dojoType=\"dijit.form.TextBox\" type=\"password\" size=\"20\" placeholder=\"Change password\" + print "<input dojoType=\"dijit.form.TextBox\" type=\"password\" size=\"20\" placeholder=\"Change password\" name=\"password\">"; - print "</div>"; + print "</div>"; - print "<div class=\"dlgSec\">".__("Options")."</div>"; - print "<div class=\"dlgSecCont\">"; + print "<div class=\"dlgSec\">".__("Options")."</div>"; + print "<div class=\"dlgSecCont\">"; - print "<input dojoType=\"dijit.form.TextBox\" size=\"30\" name=\"email\" placeholder=\"E-mail\" + print "<input dojoType=\"dijit.form.TextBox\" size=\"30\" name=\"email\" placeholder=\"E-mail\" value=\"$email\">"; - print "</div>"; + print "</div>"; - print "</table>"; + print "</table>"; - print "</form>"; + print "</form>"; + + } print '</div>'; #tab print "<div href=\"backend.php?op=pref-users&method=userdetails&id=$id\" @@ -103,131 +108,138 @@ class Pref_Users extends Handler_Protected { } function userdetails() { - $id = (int) $this->dbh->escape_string($_REQUEST["id"]); + $id = (int) clean($_REQUEST["id"]); - $result = $this->dbh->query("SELECT login, + $sth = $this->pdo->prepare("SELECT login, ".SUBSTRING_FOR_DATE."(last_login,1,16) AS last_login, access_level, (SELECT COUNT(int_id) FROM ttrss_user_entries WHERE owner_uid = id) AS stored_articles, ".SUBSTRING_FOR_DATE."(created,1,16) AS created FROM ttrss_users - WHERE id = '$id'"); + WHERE id = ?"); + $sth->execute([$id]); - if ($this->dbh->num_rows($result) == 0) { - print "<h1>".__('User not found')."</h1>"; - return; - } + if ($row = $sth->fetch()) { + print "<table width='100%'>"; - print "<table width='100%'>"; + $last_login = make_local_datetime( + $row["last_login"], true); - $last_login = make_local_datetime( - $this->dbh->fetch_result($result, 0, "last_login"), true); + $created = make_local_datetime( + $row["created"], true); - $created = make_local_datetime( - $this->dbh->fetch_result($result, 0, "created"), true); + $stored_articles = $row["stored_articles"]; - $stored_articles = $this->dbh->fetch_result($result, 0, "stored_articles"); + print "<tr><td>".__('Registered')."</td><td>$created</td></tr>"; + print "<tr><td>".__('Last logged in')."</td><td>$last_login</td></tr>"; - print "<tr><td>".__('Registered')."</td><td>$created</td></tr>"; - print "<tr><td>".__('Last logged in')."</td><td>$last_login</td></tr>"; + $sth = $this->pdo->prepare("SELECT COUNT(id) as num_feeds FROM ttrss_feeds + WHERE owner_uid = ?"); + $sth->execute([$id]); + $row = $sth->fetch(); + $num_feeds = $row["num_feeds"]; - $result = $this->dbh->query("SELECT COUNT(id) as num_feeds FROM ttrss_feeds - WHERE owner_uid = '$id'"); + print "<tr><td>".__('Subscribed feeds count')."</td><td>$num_feeds</td></tr>"; + print "<tr><td>".__('Stored articles')."</td><td>$stored_articles</td></tr>"; - $num_feeds = $this->dbh->fetch_result($result, 0, "num_feeds"); + print "</table>"; - print "<tr><td>".__('Subscribed feeds count')."</td><td>$num_feeds</td></tr>"; - print "<tr><td>".__('Stored articles')."</td><td>$stored_articles</td></tr>"; + print "<h1>".__('Subscribed feeds')."</h1>"; - print "</table>"; + $sth = $this->pdo->prepare("SELECT id,title,site_url FROM ttrss_feeds + WHERE owner_uid = ? ORDER BY title"); + $sth->execute([$id]); - print "<h1>".__('Subscribed feeds')."</h1>"; + print "<ul class=\"userFeedList\">"; - $result = $this->dbh->query("SELECT id,title,site_url FROM ttrss_feeds - WHERE owner_uid = '$id' ORDER BY title"); + while ($line = $sth->fetch()) { - print "<ul class=\"userFeedList\">"; + $icon_file = ICONS_URL."/".$line["id"].".ico"; - while ($line = $this->dbh->fetch_assoc($result)) { + if (file_exists($icon_file) && filesize($icon_file) > 0) { + $feed_icon = "<img class=\"tinyFeedIcon\" src=\"$icon_file\">"; + } else { + $feed_icon = "<img class=\"tinyFeedIcon\" src=\"images/blank_icon.gif\">"; + } - $icon_file = ICONS_URL."/".$line["id"].".ico"; + print "<li>$feed_icon <a href=\"".$line["site_url"]."\">".$line["title"]."</a></li>"; - if (file_exists($icon_file) && filesize($icon_file) > 0) { - $feed_icon = "<img class=\"tinyFeedIcon\" src=\"$icon_file\">"; - } else { - $feed_icon = "<img class=\"tinyFeedIcon\" src=\"images/blank_icon.gif\">"; } - print "<li>$feed_icon <a href=\"".$line["site_url"]."\">".$line["title"]."</a></li>"; - - } - - if ($this->dbh->num_rows($result) < $num_feeds) { - // FIXME - add link to show ALL subscribed feeds here somewhere - print "<li><img - class=\"tinyFeedIcon\" src=\"images/blank_icon.gif\"> ...</li>"; + print "</ul>"; + + + } else { + print "<h1>".__('User not found')."</h1>"; } - - print "</ul>"; + } function editSave() { - $login = $this->dbh->escape_string(trim($_REQUEST["login"])); - $uid = $this->dbh->escape_string($_REQUEST["id"]); - $access_level = (int) $_REQUEST["access_level"]; - $email = $this->dbh->escape_string(trim($_REQUEST["email"])); - $password = $_REQUEST["password"]; + $login = trim(clean($_REQUEST["login"])); + $uid = clean($_REQUEST["id"]); + $access_level = (int) clean($_REQUEST["access_level"]); + $email = trim(clean($_REQUEST["email"])); + $password = clean($_REQUEST["password"]); if ($password) { $salt = substr(bin2hex(get_random_bytes(125)), 0, 250); $pwd_hash = encrypt_password($password, $salt, true); - $pass_query_part = "pwd_hash = '$pwd_hash', salt = '$salt',"; + $pass_query_part = "pwd_hash = ".$this->pdo->quote($pwd_hash).", + salt = ".$this->pdo->quote($salt).","; } else { $pass_query_part = ""; } - $this->dbh->query("UPDATE ttrss_users SET $pass_query_part login = '$login', - access_level = '$access_level', email = '$email', otp_enabled = false - WHERE id = '$uid'"); + $sth = $this->pdo->prepare("UPDATE ttrss_users SET $pass_query_part login = ?, + access_level = ?, email = ?, otp_enabled = false WHERE id = ?"); + $sth->execute([$login, $access_level, $email, $uid]); } function remove() { - $ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"])); + $ids = explode(",", clean($_REQUEST["ids"])); foreach ($ids as $id) { if ($id != $_SESSION["uid"] && $id != 1) { - $this->dbh->query("DELETE FROM ttrss_tags WHERE owner_uid = '$id'"); - $this->dbh->query("DELETE FROM ttrss_feeds WHERE owner_uid = '$id'"); - $this->dbh->query("DELETE FROM ttrss_users WHERE id = '$id'"); + $sth = $this->pdo->prepare("DELETE FROM ttrss_tags WHERE owner_uid = ?"); + $sth->execute([$id]); + + $sth = $this->pdo->prepare("DELETE FROM ttrss_feeds WHERE owner_uid = ?"); + $sth->execute([$id]); + + $sth = $this->pdo->prepare("DELETE FROM ttrss_users WHERE id = ?"); + $sth->execute([$id]); } } } function add() { - $login = $this->dbh->escape_string(trim($_REQUEST["login"])); + $login = trim(clean($_REQUEST["login"])); $tmp_user_pwd = make_password(8); $salt = substr(bin2hex(get_random_bytes(125)), 0, 250); $pwd_hash = encrypt_password($tmp_user_pwd, $salt, true); - $result = $this->dbh->query("SELECT id FROM ttrss_users WHERE - login = '$login'"); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE + login = ?"); + $sth->execute([$login]); - if ($this->dbh->num_rows($result) == 0) { + if (!$sth->fetch()) { - $this->dbh->query("INSERT INTO ttrss_users + $sth = $this->pdo->prepare("INSERT INTO ttrss_users (login,pwd_hash,access_level,last_login,created, salt) - VALUES ('$login', '$pwd_hash', 0, null, NOW(), '$salt')"); - + VALUES (?, ?, 0, null, NOW(), ?)"); + $sth->execute([$login, $pwd_hash, $salt]); - $result = $this->dbh->query("SELECT id FROM ttrss_users WHERE - login = '$login' AND pwd_hash = '$pwd_hash'"); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE + login = ? AND pwd_hash = ?"); + $sth->execute([$login, $pwd_hash]); - if ($this->dbh->num_rows($result) == 1) { + if ($row = $sth->fetch()) { - $new_uid = $this->dbh->fetch_result($result, 0, "id"); + $new_uid = $row['id']; print format_notice(T_sprintf("Added user <b>%s</b> with password <b>%s</b>", $login, $tmp_user_pwd)); @@ -246,56 +258,65 @@ class Pref_Users extends Handler_Protected { static function resetUserPassword($uid, $show_password) { - $result = db_query("SELECT login,email - FROM ttrss_users WHERE id = '$uid'"); + $pdo = Db::pdo(); - $login = db_fetch_result($result, 0, "login"); - $email = db_fetch_result($result, 0, "email"); + $sth = $pdo->prepare("SELECT login, email + FROM ttrss_users WHERE id = ?"); + $sth->execute([$uid]); + + if ($row = $sth->fetch()) { - $new_salt = substr(bin2hex(get_random_bytes(125)), 0, 250); - $tmp_user_pwd = make_password(8); + $login = $row["login"]; + $email = $row["email"]; - $pwd_hash = encrypt_password($tmp_user_pwd, $new_salt, true); + $new_salt = substr(bin2hex(get_random_bytes(125)), 0, 250); + $tmp_user_pwd = make_password(8); - db_query("UPDATE ttrss_users SET pwd_hash = '$pwd_hash', salt = '$new_salt', otp_enabled = false - WHERE id = '$uid'"); + $pwd_hash = encrypt_password($tmp_user_pwd, $new_salt, true); - if ($show_password) { - print T_sprintf("Changed password of user <b>%s</b> to <b>%s</b>", $login, $tmp_user_pwd); - } else { - print_notice(T_sprintf("Sending new password of user <b>%s</b> to <b>%s</b>", $login, $email)); - } + $sth = $pdo->prepare("UPDATE ttrss_users + SET pwd_hash = ?, salt = ?, otp_enabled = false + WHERE id = ?"); + $sth->execute([$pwd_hash, $new_salt, $uid]); - require_once 'classes/ttrssmailer.php'; + if ($show_password) { + print T_sprintf("Changed password of user <b>%s</b> to <b>%s</b>", $login, $tmp_user_pwd); + } else { + print_notice(T_sprintf("Sending new password of user <b>%s</b> to <b>%s</b>", $login, $email)); + } + + require_once 'classes/ttrssmailer.php'; - if ($email) { - require_once "lib/MiniTemplator.class.php"; + if ($email) { + require_once "lib/MiniTemplator.class.php"; - $tpl = new MiniTemplator; + $tpl = new MiniTemplator; - $tpl->readTemplateFromFile("templates/resetpass_template.txt"); + $tpl->readTemplateFromFile("templates/resetpass_template.txt"); - $tpl->setVariable('LOGIN', $login); - $tpl->setVariable('NEWPASS', $tmp_user_pwd); + $tpl->setVariable('LOGIN', $login); + $tpl->setVariable('NEWPASS', $tmp_user_pwd); - $tpl->addBlock('message'); + $tpl->addBlock('message'); - $message = ""; + $message = ""; - $tpl->generateOutputToString($message); + $tpl->generateOutputToString($message); - $mail = new ttrssMailer(); + $mail = new ttrssMailer(); - $rc = $mail->quickMail($email, $login, - __("[tt-rss] Password change notification"), - $message, false); + $rc = $mail->quickMail($email, $login, + __("[tt-rss] Password change notification"), + $message, false); - if (!$rc) print_error($mail->ErrorInfo); + if (!$rc) print_error($mail->ErrorInfo); + } + } } function resetPass() { - $uid = $this->dbh->escape_string($_REQUEST["id"]); + $uid = clean($_REQUEST["id"]); Pref_Users::resetUserPassword($uid, true); } @@ -308,7 +329,7 @@ class Pref_Users extends Handler_Protected { print "<div id=\"pref-user-toolbar\" dojoType=\"dijit.Toolbar\">"; - $user_search = $this->dbh->escape_string($_REQUEST["search"]); + $user_search = trim(clean($_REQUEST["search"])); if (array_key_exists("search", $_REQUEST)) { $_SESSION["prefs_user_search"] = $user_search; @@ -323,7 +344,7 @@ class Pref_Users extends Handler_Protected { __('Search')."</button> </div>"; - $sort = $this->dbh->escape_string($_REQUEST["sort"]); + $sort = clean($_REQUEST["sort"]); if (!$sort || $sort == "undefined") { $sort = "login"; @@ -355,25 +376,12 @@ class Pref_Users extends Handler_Protected { print "</div>"; #pane print "<div id=\"pref-user-content\" dojoType=\"dijit.layout.ContentPane\" region=\"center\">"; - print "<div id=\"sticky-status-msg\"></div>"; - - if ($user_search) { + $sort = validate_field($sort, + ["login", "access_level", "created", "num_feeds", "created", "last_login"], "login"); - $user_search = explode(" ", $user_search); - $tokens = array(); + if ($sort != "login") $sort = "$sort DESC"; - foreach ($user_search as $token) { - $token = trim($token); - array_push($tokens, "(UPPER(login) LIKE UPPER('%$token%'))"); - } - - $user_search_query = "(" . join($tokens, " AND ") . ") AND "; - - } else { - $user_search_query = ""; - } - - $result = $this->dbh->query("SELECT + $sth = $this->pdo->prepare("SELECT tu.id, login,access_level,email, ".SUBSTRING_FOR_DATE."(last_login,1,16) as last_login, @@ -382,11 +390,9 @@ class Pref_Users extends Handler_Protected { FROM ttrss_users tu WHERE - $user_search_query - tu.id > 0 + (:search = '' OR login LIKE :search) AND tu.id > 0 ORDER BY $sort"); - - if ($this->dbh->num_rows($result) > 0) { + $sth->execute([":search" => $user_search ? "%$user_search%" : ""]); print "<p><table width=\"100%\" cellspacing=\"0\" class=\"prefUserList\" id=\"prefUserList\">"; @@ -401,7 +407,7 @@ class Pref_Users extends Handler_Protected { $lnum = 0; - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { $uid = $line["id"]; @@ -434,15 +440,12 @@ class Pref_Users extends Handler_Protected { print "</table>"; - } else { - print "<p>"; + if ($lnum == 0) { if (!$user_search) { print_warning(__('No users defined.')); } else { print_warning(__('No matching users found.')); } - print "</p>"; - } print "</div>"; #pane diff --git a/classes/rpc.php b/classes/rpc.php index 7d39261b1..f8cf7a828 100755 --- a/classes/rpc.php +++ b/classes/rpc.php @@ -2,47 +2,54 @@ class RPC extends Handler_Protected { function csrf_ignore($method) { - $csrf_ignored = array("sanitycheck", "completelabels"); + $csrf_ignored = array("sanitycheck", "completelabels", "saveprofile"); return array_search($method, $csrf_ignored) !== false; } function setprofile() { - $id = $this->dbh->escape_string($_REQUEST["id"]); + $_SESSION["profile"] = clean($_REQUEST["id"]); - $_SESSION["profile"] = $id; + // default value + if (!$_SESSION["profile"]) $_SESSION["profile"] = null; } function remprofiles() { - $ids = explode(",", $this->dbh->escape_string(trim($_REQUEST["ids"]))); + $ids = explode(",", trim(clean($_REQUEST["ids"]))); foreach ($ids as $id) { if ($_SESSION["profile"] != $id) { - $this->dbh->query("DELETE FROM ttrss_settings_profiles WHERE id = '$id' AND - owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("DELETE FROM ttrss_settings_profiles WHERE id = ? AND + owner_uid = ?"); + $sth->execute([$id, $_SESSION['uid']]); } } } // Silent function addprofile() { - $title = $this->dbh->escape_string(trim($_REQUEST["title"])); + $title = trim(clean($_REQUEST["title"])); + if ($title) { - $this->dbh->query("BEGIN"); + $this->pdo->beginTransaction(); + + $sth = $this->pdo->prepare("SELECT id FROM ttrss_settings_profiles + WHERE title = ? AND owner_uid = ?"); + $sth->execute([$title, $_SESSION['uid']]); - $result = $this->dbh->query("SELECT id FROM ttrss_settings_profiles - WHERE title = '$title' AND owner_uid = " . $_SESSION["uid"]); + if (!$sth->fetch()) { - if ($this->dbh->num_rows($result) == 0) { + $sth = $this->pdo->prepare("INSERT INTO ttrss_settings_profiles (title, owner_uid) + VALUES (?, ?)"); - $this->dbh->query("INSERT INTO ttrss_settings_profiles (title, owner_uid) - VALUES ('$title', ".$_SESSION["uid"] .")"); + $sth->execute([$title, $_SESSION['uid']]); - $result = $this->dbh->query("SELECT id FROM ttrss_settings_profiles WHERE - title = '$title'"); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_settings_profiles WHERE + title = ? AND owner_uid = ?"); + $sth->execute([$title, $_SESSION['uid']]); - if ($this->dbh->num_rows($result) != 0) { - $profile_id = $this->dbh->fetch_result($result, 0, "id"); + if ($row = $sth->fetch()) { + $profile_id = $row['id']; if ($profile_id) { initialize_user_prefs($_SESSION["uid"], $profile_id); @@ -50,14 +57,13 @@ class RPC extends Handler_Protected { } } - $this->dbh->query("COMMIT"); + $this->pdo->commit(); } } - // Silent function saveprofile() { - $id = $this->dbh->escape_string($_REQUEST["id"]); - $title = $this->dbh->escape_string(trim($_REQUEST["value"])); + $id = clean($_REQUEST["id"]); + $title = trim(clean($_REQUEST["value"])); if ($id == 0) { print __("Default profile"); @@ -65,45 +71,34 @@ class RPC extends Handler_Protected { } if ($title) { - $this->dbh->query("BEGIN"); - - $result = $this->dbh->query("SELECT id FROM ttrss_settings_profiles - WHERE title = '$title' AND owner_uid =" . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_settings_profiles + SET title = ? WHERE id = ? AND + owner_uid = ?"); - if ($this->dbh->num_rows($result) == 0) { - $this->dbh->query("UPDATE ttrss_settings_profiles - SET title = '$title' WHERE id = '$id' AND - owner_uid = " . $_SESSION["uid"]); - print $title; - } else { - $result = $this->dbh->query("SELECT title FROM ttrss_settings_profiles - WHERE id = '$id' AND owner_uid =" . $_SESSION["uid"]); - print $this->dbh->fetch_result($result, 0, "title"); - } - - $this->dbh->query("COMMIT"); + $sth->execute([$title, $id, $_SESSION['uid']]); + print $title; } } // Silent function remarchive() { - $ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"])); + $ids = explode(",", clean($_REQUEST["ids"])); - foreach ($ids as $id) { - $result = $this->dbh->query("DELETE FROM ttrss_archived_feeds WHERE - (SELECT COUNT(*) FROM ttrss_user_entries - WHERE orig_feed_id = '$id') = 0 AND - id = '$id' AND owner_uid = ".$_SESSION["uid"]); + $sth = $this->pdo->prepare("DELETE FROM ttrss_archived_feeds WHERE + (SELECT COUNT(*) FROM ttrss_user_entries + WHERE orig_feed_id = :id) = 0 AND + id = :id AND owner_uid = :uid"); - $this->dbh->affected_rows($result); + foreach ($ids as $id) { + $sth->execute([":id" => $id, ":uid" => $_SESSION['uid']]); } } function addfeed() { - $feed = $this->dbh->escape_string($_REQUEST['feed']); - $cat = $this->dbh->escape_string($_REQUEST['cat']); - $login = $this->dbh->escape_string($_REQUEST['login']); - $pass = trim($_REQUEST['pass']); // escaped later + $feed = clean($_REQUEST['feed']); + $cat = clean($_REQUEST['cat']); + $login = clean($_REQUEST['login']); + $pass = trim(clean($_REQUEST['pass'])); $rc = Feeds::subscribe_to_feed($feed, $cat, $login, $pass); @@ -111,7 +106,7 @@ class RPC extends Handler_Protected { } function togglepref() { - $key = $this->dbh->escape_string($_REQUEST["key"]); + $key = clean($_REQUEST["key"]); set_pref($key, !get_pref($key)); $value = get_pref($key); @@ -120,8 +115,8 @@ class RPC extends Handler_Protected { function setpref() { // set_pref escapes input, so no need to double escape it here - $key = $_REQUEST['key']; - $value = str_replace("\n", "<br/>", $_REQUEST['value']); + $key = clean($_REQUEST['key']); + $value = $_REQUEST['value']; set_pref($key, $value, false, $key != 'USER_STYLESHEET'); @@ -129,27 +124,25 @@ class RPC extends Handler_Protected { } function mark() { - $mark = $_REQUEST["mark"]; - $id = $this->dbh->escape_string($_REQUEST["id"]); - - if ($mark == "1") { - $mark = "true"; - } else { - $mark = "false"; - } + $mark = clean($_REQUEST["mark"]); + $id = clean($_REQUEST["id"]); - $this->dbh->query("UPDATE ttrss_user_entries SET marked = $mark, + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET marked = ?, last_marked = NOW() - WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]); + WHERE ref_id = ? AND owner_uid = ?"); + + $sth->execute([$mark, $id, $_SESSION['uid']]); print json_encode(array("message" => "UPDATE_COUNTERS")); } function delete() { - $ids = $this->dbh->escape_string($_REQUEST["ids"]); + $ids = explode(",", clean($_REQUEST["ids"])); + $ids_qmarks = arr_qmarks($ids); - $this->dbh->query("DELETE FROM ttrss_user_entries - WHERE ref_id IN ($ids) AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("DELETE FROM ttrss_user_entries + WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); + $sth->execute(array_merge($ids, [$_SESSION['uid']])); Article::purge_orphans(); @@ -157,64 +150,60 @@ class RPC extends Handler_Protected { } function unarchive() { - $ids = explode(",", $_REQUEST["ids"]); + $ids = explode(",", clean($_REQUEST["ids"])); foreach ($ids as $id) { - $id = $this->dbh->escape_string(trim($id)); - $this->dbh->query("BEGIN"); - - $result = $this->dbh->query("SELECT feed_url,site_url,title FROM ttrss_archived_feeds - WHERE id = (SELECT orig_feed_id FROM ttrss_user_entries WHERE ref_id = $id - AND owner_uid = ".$_SESSION["uid"].") AND owner_uid = " . $_SESSION["uid"]); + $this->pdo->beginTransaction(); - if ($this->dbh->num_rows($result) != 0) { - $feed_url = $this->dbh->escape_string(db_fetch_result($result, 0, "feed_url")); - $site_url = $this->dbh->escape_string(db_fetch_result($result, 0, "site_url")); - $title = $this->dbh->escape_string(db_fetch_result($result, 0, "title")); + $sth = $this->pdo->prepare("SELECT feed_url,site_url,title FROM ttrss_archived_feeds + WHERE id = (SELECT orig_feed_id FROM ttrss_user_entries WHERE ref_id = :id + AND owner_uid = :uid) AND owner_uid = :uid"); + $sth->execute([":uid" => $_SESSION['uid'], ":id" => $id]); - $result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE feed_url = '$feed_url' - AND owner_uid = " .$_SESSION["uid"]); + if ($row = $sth->fetch()) { + $feed_url = $row['feed_url']; + $site_url = $row['site_url']; + $title = $row['title']; - if ($this->dbh->num_rows($result) == 0) { + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE feed_url = ? + AND owner_uid = ?"); + $sth->execute([$feed_url, $_SESSION['uid']]); + if ($row = $sth->fetch()) { + $feed_id = $row["id"]; + } else { if (!$title) $title = '[Unknown]'; - $result = $this->dbh->query( - "INSERT INTO ttrss_feeds + $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds (owner_uid,feed_url,site_url,title,cat_id,auth_login,auth_pass,update_method) - VALUES (".$_SESSION["uid"].", - '$feed_url', - '$site_url', - '$title', - NULL, '', '', 0)"); - - $result = $this->dbh->query( - "SELECT id FROM ttrss_feeds WHERE feed_url = '$feed_url' - AND owner_uid = ".$_SESSION["uid"]); - - if ($this->dbh->num_rows($result) != 0) { - $feed_id = $this->dbh->fetch_result($result, 0, "id"); - } + VALUES (?, ?, ?, ?, NULL, '', '', 0)"); + $sth->execute([$_SESSION['uid'], $feed_url, $site_url, $title]); - } else { - $feed_id = $this->dbh->fetch_result($result, 0, "id"); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE feed_url = ? + AND owner_uid = ?"); + $sth->execute([$feed_url, $_SESSION['uid']]); + + if ($row = $sth->fetch()) { + $feed_id = $row['id']; + } } if ($feed_id) { - $result = $this->dbh->query("UPDATE ttrss_user_entries - SET feed_id = '$feed_id', orig_feed_id = NULL - WHERE ref_id = $id AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries + SET feed_id = ?, orig_feed_id = NULL + WHERE ref_id = ? AND owner_uid = ?"); + $sth->execute([$feed_id, $id, $_SESSION['uid']]); } } - $this->dbh->query("COMMIT"); + $this->pdo->commit(); } print json_encode(array("message" => "UPDATE_COUNTERS")); } function archive() { - $ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"])); + $ids = explode(",", clean($_REQUEST["ids"])); foreach ($ids as $id) { $this->archive_article($id, $_SESSION["uid"]); @@ -224,61 +213,64 @@ class RPC extends Handler_Protected { } private function archive_article($id, $owner_uid) { - $this->dbh->query("BEGIN"); + $this->pdo->beginTransaction(); - $result = $this->dbh->query("SELECT feed_id FROM ttrss_user_entries - WHERE ref_id = '$id' AND owner_uid = $owner_uid"); + if (!$owner_uid) $owner_uid = $_SESSION['uid']; - if ($this->dbh->num_rows($result) != 0) { + $sth = $this->pdo->prepare("SELECT feed_id FROM ttrss_user_entries + WHERE ref_id = ? AND owner_uid = ?"); + $sth->execute([$id, $owner_uid]); + + if ($row = $sth->fetch()) { /* prepare the archived table */ - $feed_id = (int) $this->dbh->fetch_result($result, 0, "feed_id"); + $feed_id = (int) $row['feed_id']; if ($feed_id) { - $result = $this->dbh->query("SELECT id FROM ttrss_archived_feeds - WHERE id = '$feed_id' AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_archived_feeds + WHERE id = ? AND owner_uid = ?"); + $sth->execute([$feed_id, $owner_uid]); - if ($this->dbh->num_rows($result) == 0) { - $result = db_query("SELECT MAX(id) AS id FROM ttrss_archived_feeds"); - $new_feed_id = (int)db_fetch_result($result, 0, "id") + 1; + if ($row = $sth->fetch()) { + $new_feed_id = $row['id']; + } else { + $row = $this->pdo->query("SELECT MAX(id) AS id FROM ttrss_archived_feeds")->fetch(); + $new_feed_id = (int)$row['id'] + 1; - $this->dbh->query("INSERT INTO ttrss_archived_feeds + $sth = $this->pdo->prepare("INSERT INTO ttrss_archived_feeds (id, owner_uid, title, feed_url, site_url) - SELECT $new_feed_id, owner_uid, title, feed_url, site_url from ttrss_feeds - WHERE id = '$feed_id'"); - } else { - $new_feed_id = $this->dbh->fetch_result($result, 0, "id"); + SELECT ?, owner_uid, title, feed_url, site_url from ttrss_feeds + WHERE id = ?"); + + $sth->execute([$new_feed_id, $feed_id]); } - $this->dbh->query("UPDATE ttrss_user_entries - SET orig_feed_id = $new_feed_id, feed_id = NULL - WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries + SET orig_feed_id = ?, feed_id = NULL + WHERE ref_id = ? AND owner_uid = ?"); + $sth->execute([$new_feed_id, $id, $owner_uid]); } } - $this->dbh->query("COMMIT"); + $this->pdo->commit(); } function publ() { - $pub = $_REQUEST["pub"]; - $id = $this->dbh->escape_string($_REQUEST["id"]); + $pub = clean($_REQUEST["pub"]); + $id = clean($_REQUEST["id"]); - if ($pub == "1") { - $pub = "true"; - } else { - $pub = "false"; - } + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET + published = ?, last_published = NOW() + WHERE ref_id = ? AND owner_uid = ?"); - $this->dbh->query("UPDATE ttrss_user_entries SET - published = $pub, last_published = NOW() - WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]); + $sth->execute([$pub, $id, $_SESSION['uid']]); print json_encode(array("message" => "UPDATE_COUNTERS")); } function getAllCounters() { - $last_article_id = (int) $_REQUEST["last_article_id"]; + $last_article_id = (int) clean($_REQUEST["last_article_id"]); $reply = array(); @@ -295,8 +287,8 @@ class RPC extends Handler_Protected { /* GET["cmode"] = 0 - mark as read, 1 - as unread, 2 - toggle */ function catchupSelected() { - $ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"])); - $cmode = sprintf("%d", $_REQUEST["cmode"]); + $ids = explode(",", clean($_REQUEST["ids"])); + $cmode = sprintf("%d", clean($_REQUEST["cmode"])); Article::catchupArticlesById($ids, $cmode); @@ -304,8 +296,8 @@ class RPC extends Handler_Protected { } function markSelected() { - $ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"])); - $cmode = sprintf("%d", $_REQUEST["cmode"]); + $ids = explode(",", clean($_REQUEST["ids"])); + $cmode = (int)clean($_REQUEST["cmode"]); $this->markArticlesById($ids, $cmode); @@ -313,8 +305,8 @@ class RPC extends Handler_Protected { } function publishSelected() { - $ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"])); - $cmode = sprintf("%d", $_REQUEST["cmode"]); + $ids = explode(",", clean($_REQUEST["ids"])); + $cmode = (int)clean($_REQUEST["cmode"]); $this->publishArticlesById($ids, $cmode); @@ -322,10 +314,10 @@ class RPC extends Handler_Protected { } function sanityCheck() { - $_SESSION["hasAudio"] = $_REQUEST["hasAudio"] === "true"; - $_SESSION["hasSandbox"] = $_REQUEST["hasSandbox"] === "true"; - $_SESSION["hasMp3"] = $_REQUEST["hasMp3"] === "true"; - $_SESSION["clientTzOffset"] = $_REQUEST["clientTzOffset"]; + $_SESSION["hasAudio"] = clean($_REQUEST["hasAudio"]) === "true"; + $_SESSION["hasSandbox"] = clean($_REQUEST["hasSandbox"]) === "true"; + $_SESSION["hasMp3"] = clean($_REQUEST["hasMp3"]) === "true"; + $_SESSION["clientTzOffset"] = clean($_REQUEST["clientTzOffset"]); $reply = array(); @@ -340,42 +332,28 @@ class RPC extends Handler_Protected { } function completeLabels() { - $search = $this->dbh->escape_string($_REQUEST["search"]); + $search = clean($_REQUEST["search"]); - $result = $this->dbh->query("SELECT DISTINCT caption FROM + $sth = $this->pdo->prepare("SELECT DISTINCT caption FROM ttrss_labels2 - WHERE owner_uid = '".$_SESSION["uid"]."' AND - LOWER(caption) LIKE LOWER('$search%') ORDER BY caption + WHERE owner_uid = ? AND + LOWER(caption) LIKE LOWER(?) ORDER BY caption LIMIT 5"); + $sth->execute([$_SESSION['uid'], "%$search%"]); print "<ul>"; - while ($line = $this->dbh->fetch_assoc($result)) { + while ($line = $sth->fetch()) { print "<li>" . $line["caption"] . "</li>"; } print "</ul>"; } - function purge() { - $ids = explode(",", $this->dbh->escape_string($_REQUEST["ids"])); - $days = sprintf("%d", $_REQUEST["days"]); - - foreach ($ids as $id) { - - $result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE - id = '$id' AND owner_uid = ".$_SESSION["uid"]); - - if ($this->dbh->num_rows($result) == 1) { - purge_feed($id, $days); - } - } - } - function updateFeedBrowser() { if (defined('_DISABLE_FEED_BROWSER') && _DISABLE_FEED_BROWSER) return; - $search = $this->dbh->escape_string($_REQUEST["search"]); - $limit = $this->dbh->escape_string($_REQUEST["limit"]); - $mode = (int) $this->dbh->escape_string($_REQUEST["mode"]); + $search = clean($_REQUEST["search"]); + $limit = clean($_REQUEST["limit"]); + $mode = (int) clean($_REQUEST["mode"]); require_once "feedbrowser.php"; @@ -387,46 +365,51 @@ class RPC extends Handler_Protected { // Silent function massSubscribe() { - $payload = json_decode($_REQUEST["payload"], false); - $mode = $_REQUEST["mode"]; + $payload = json_decode(clean($_REQUEST["payload"]), false); + $mode = clean($_REQUEST["mode"]); if (!$payload || !is_array($payload)) return; if ($mode == 1) { foreach ($payload as $feed) { - $title = $this->dbh->escape_string($feed[0]); - $feed_url = $this->dbh->escape_string($feed[1]); + $title = $feed[0]; + $feed_url = $feed[1]; - $result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE - feed_url = '$feed_url' AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE + feed_url = ? AND owner_uid = ?"); + $sth->execute([$feed_url, $_SESSION['uid']]); - if ($this->dbh->num_rows($result) == 0) { - $result = $this->dbh->query("INSERT INTO ttrss_feeds + if (!$sth->fetch()) { + $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds (owner_uid,feed_url,title,cat_id,site_url) - VALUES ('".$_SESSION["uid"]."', - '$feed_url', '$title', NULL, '')"); + VALUES (?, ?, ?, NULL, '')"); + + $sth->execute([$_SESSION['uid'], $feed_url, $title]); } } } else if ($mode == 2) { // feed archive foreach ($payload as $id) { - $result = $this->dbh->query("SELECT * FROM ttrss_archived_feeds - WHERE id = '$id' AND owner_uid = " . $_SESSION["uid"]); - - if ($this->dbh->num_rows($result) != 0) { - $site_url = $this->dbh->escape_string(db_fetch_result($result, 0, "site_url")); - $feed_url = $this->dbh->escape_string(db_fetch_result($result, 0, "feed_url")); - $title = $this->dbh->escape_string(db_fetch_result($result, 0, "title")); - - $result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE - feed_url = '$feed_url' AND owner_uid = " . $_SESSION["uid"]); - - if ($this->dbh->num_rows($result) == 0) { - $result = $this->dbh->query("INSERT INTO ttrss_feeds - (owner_uid,feed_url,title,cat_id,site_url) - VALUES ('".$_SESSION["uid"]."', - '$feed_url', '$title', NULL, '$site_url')"); + $sth = $this->pdo->prepare("SELECT * FROM ttrss_archived_feeds + WHERE id = ? AND owner_uid = ?"); + $sth->execute([$id, $_SESSION['uid']]); + + if ($row = $sth->fetch()) { + $site_url = $row['site_url']; + $feed_url = $row['feed_url']; + $title = $row['title']; + + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE + feed_url = ? AND owner_uid = ?"); + $sth->execute([$feed_url, $_SESSION['uid']]); + + if (!$sth->fetch()) { + $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds + (owner_uid,feed_url,title,cat_id,site_url) + VALUES (?, ?, ?, NULL, ?)"); + + $sth->execute([$_SESSION['uid'], $feed_url, $title, $site_url]); } } } @@ -434,36 +417,19 @@ class RPC extends Handler_Protected { } function catchupFeed() { - $feed_id = $this->dbh->escape_string($_REQUEST['feed_id']); - $is_cat = $this->dbh->escape_string($_REQUEST['is_cat']) == "true"; - $mode = $this->dbh->escape_string($_REQUEST['mode']); - $search_query = $this->dbh->escape_string($_REQUEST['search_query']); - $search_lang = $this->dbh->escape_string($_REQUEST['search_lang']); + $feed_id = clean($_REQUEST['feed_id']); + $is_cat = clean($_REQUEST['is_cat']) == "true"; + $mode = clean($_REQUEST['mode']); + $search_query = clean($_REQUEST['search_query']); + $search_lang = clean($_REQUEST['search_lang']); Feeds::catchup_feed($feed_id, $is_cat, false, $mode, [$search_query, $search_lang]); print json_encode(array("message" => "UPDATE_COUNTERS")); } - function quickAddCat() { - $cat = $this->dbh->escape_string($_REQUEST["cat"]); - - add_feed_category($cat); - - $result = $this->dbh->query("SELECT id FROM ttrss_feed_categories WHERE - title = '$cat' AND owner_uid = " . $_SESSION["uid"]); - - if ($this->dbh->num_rows($result) == 1) { - $id = $this->dbh->fetch_result($result, 0, "id"); - } else { - $id = 0; - } - - print_feed_cat_select("cat_id", $id, ''); - } - function setpanelmode() { - $wide = (int) $_REQUEST["wide"]; + $wide = (int) clean($_REQUEST["wide"]); setcookie("ttrss_widescreen", $wide, time() + COOKIE_LIFETIME_LONG); @@ -471,7 +437,7 @@ class RPC extends Handler_Protected { print json_encode(array("wide" => $wide)); } - static function updaterandomfeed_real($dbh) { + static function updaterandomfeed_real() { // Test if the feed need a update (update interval exceded). if (DB_TYPE == "pgsql") { @@ -503,15 +469,17 @@ class RPC extends Handler_Protected { $random_qpart = sql_random_function(); + $pdo = Db::pdo(); + // we could be invoked from public.php with no active session if ($_SESSION["uid"]) { - $owner_check_qpart = "AND ttrss_feeds.owner_uid = '".$_SESSION["uid"]."'"; + $owner_check_qpart = "AND ttrss_feeds.owner_uid = ".$pdo->quote($_SESSION["uid"]); } else { $owner_check_qpart = ""; } // We search for feed needing update. - $result = $dbh->query("SELECT ttrss_feeds.feed_url,ttrss_feeds.id + $res = $pdo->query("SELECT ttrss_feeds.feed_url,ttrss_feeds.id FROM ttrss_feeds, ttrss_users, ttrss_user_prefs WHERE @@ -523,13 +491,11 @@ class RPC extends Handler_Protected { $updstart_thresh_qpart ORDER BY $random_qpart LIMIT 30"); - $feed_id = -1; - $num_updated = 0; $tstart = time(); - while ($line = $dbh->fetch_assoc($result)) { + while ($line = $res->fetch()) { $feed_id = $line["id"]; if (time() - $tstart < ini_get("max_execution_time") * 0.7) { @@ -554,68 +520,61 @@ class RPC extends Handler_Protected { } function updaterandomfeed() { - RPC::updaterandomfeed_real($this->dbh); + RPC::updaterandomfeed_real(); } private function markArticlesById($ids, $cmode) { - $tmp_ids = array(); - - foreach ($ids as $id) { - array_push($tmp_ids, "ref_id = '$id'"); - } - - $ids_qpart = join(" OR ", $tmp_ids); + $ids_qmarks = arr_qmarks($ids); if ($cmode == 0) { - $this->dbh->query("UPDATE ttrss_user_entries SET - marked = false, last_marked = NOW() - WHERE ($ids_qpart) AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET + marked = false, last_marked = NOW() + WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); } else if ($cmode == 1) { - $this->dbh->query("UPDATE ttrss_user_entries SET - marked = true, last_marked = NOW() - WHERE ($ids_qpart) AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET + marked = true, last_marked = NOW() + WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); } else { - $this->dbh->query("UPDATE ttrss_user_entries SET - marked = NOT marked,last_marked = NOW() - WHERE ($ids_qpart) AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET + marked = NOT marked,last_marked = NOW() + WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); } + + $sth->execute(array_merge($ids, [$_SESSION['uid']])); } private function publishArticlesById($ids, $cmode) { - $tmp_ids = array(); - - foreach ($ids as $id) { - array_push($tmp_ids, "ref_id = '$id'"); - } - - $ids_qpart = join(" OR ", $tmp_ids); + $ids_qmarks = arr_qmarks($ids); if ($cmode == 0) { - $this->dbh->query("UPDATE ttrss_user_entries SET - published = false,last_published = NOW() - WHERE ($ids_qpart) AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET + published = false, last_published = NOW() + WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); } else if ($cmode == 1) { - $this->dbh->query("UPDATE ttrss_user_entries SET - published = true,last_published = NOW() - WHERE ($ids_qpart) AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET + published = true, last_published = NOW() + WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); } else { - $this->dbh->query("UPDATE ttrss_user_entries SET - published = NOT published,last_published = NOW() - WHERE ($ids_qpart) AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET + published = NOT published,last_published = NOW() + WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?"); } + + $sth->execute(array_merge($ids, [$_SESSION['uid']])); } function getlinktitlebyid() { - $id = $this->dbh->escape_string($_REQUEST['id']); + $id = clean($_REQUEST['id']); - $result = $this->dbh->query("SELECT link, title FROM ttrss_entries, ttrss_user_entries - WHERE ref_id = '$id' AND ref_id = id AND owner_uid = ". $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT link, title FROM ttrss_entries, ttrss_user_entries + WHERE ref_id = ? AND ref_id = id AND owner_uid = ?"); + $sth->execute([$id, $_SESSION['uid']]); - if ($this->dbh->num_rows($result) != 0) { - $link = $this->dbh->fetch_result($result, 0, "link"); - $title = $this->dbh->fetch_result($result, 0, "title"); + if ($row = $sth->fetch()) { + $link = $row['link']; + $title = $row['title']; echo json_encode(array("link" => $link, "title" => $title)); } else { @@ -624,10 +583,10 @@ class RPC extends Handler_Protected { } function log() { - $msg = $this->dbh->escape_string($_REQUEST['msg']); - $file = $this->dbh->escape_string(basename($_REQUEST['file'])); - $line = (int) $_REQUEST['line']; - $context = $this->dbh->escape_string($_REQUEST['context']); + $msg = clean($_REQUEST['msg']); + $file = basename(clean($_REQUEST['file'])); + $line = (int) clean($_REQUEST['line']); + $context = clean($_REQUEST['context']); if ($msg) { Logger::get()->log_error(E_USER_WARNING, diff --git a/classes/rssutils.php b/classes/rssutils.php index f6326720d..613947afb 100644 --- a/classes/rssutils.php +++ b/classes/rssutils.php @@ -18,31 +18,38 @@ class RSSUtils { static function update_feedbrowser_cache() { - $result = db_query("SELECT feed_url, site_url, title, COUNT(id) AS subscribers + $pdo = Db::pdo(); + + $sth = $pdo->query("SELECT feed_url, site_url, title, COUNT(id) AS subscribers FROM ttrss_feeds WHERE feed_url NOT IN (SELECT feed_url FROM ttrss_feeds WHERE private IS true OR auth_login != '' OR auth_pass != '' OR feed_url LIKE '%:%@%/%') GROUP BY feed_url, site_url, title ORDER BY subscribers DESC LIMIT 1000"); - db_query("BEGIN"); + $pdo->beginTransaction(); - db_query("DELETE FROM ttrss_feedbrowser_cache"); + $pdo->query("DELETE FROM ttrss_feedbrowser_cache"); $count = 0; - while ($line = db_fetch_assoc($result)) { - $subscribers = db_escape_string($line["subscribers"]); - $feed_url = db_escape_string($line["feed_url"]); - $title = db_escape_string($line["title"]); - $site_url = db_escape_string($line["site_url"]); + while ($line = $sth->fetch()) { + + $subscribers = $line["subscribers"]; + $feed_url = $line["feed_url"]; + $title = $line["title"]; + $site_url = $line["site_url"]; - $tmp_result = db_query("SELECT subscribers FROM - ttrss_feedbrowser_cache WHERE feed_url = '$feed_url'"); + $tmph = $pdo->prepare("SELECT subscribers FROM + ttrss_feedbrowser_cache WHERE feed_url = ?"); + $tmph->execute([$feed_url]); - if (db_num_rows($tmp_result) == 0) { + if (!$tmph->fetch()) { - db_query("INSERT INTO ttrss_feedbrowser_cache - (feed_url, site_url, title, subscribers) VALUES ('$feed_url', - '$site_url', '$title', '$subscribers')"); + $tmph = $pdo->prepare("INSERT INTO ttrss_feedbrowser_cache + (feed_url, site_url, title, subscribers) + VALUES + (?, ?, ?, ?)"); + + $tmph->execute([$feed_url, $site_url, $title, $subscribers]); ++$count; @@ -50,7 +57,7 @@ class RSSUtils { } - db_query("COMMIT"); + $pdo->commit(); return $count; @@ -63,6 +70,8 @@ class RSSUtils { die("Schema version is wrong, please upgrade the database.\n"); } + $pdo = Db::pdo(); + if (!SINGLE_USER_MODE && DAEMON_UPDATE_LOGIN_LIMIT > 0) { if (DB_TYPE == "pgsql") { $login_thresh_qpart = "AND ttrss_users.last_login >= NOW() - INTERVAL '".DAEMON_UPDATE_LOGIN_LIMIT." days'"; @@ -124,22 +133,23 @@ class RSSUtils { $updstart_thresh_qpart $query_order $query_limit"; - $result = db_query($query); - - if ($debug) _debug(sprintf("Scheduled %d feeds to update...", db_num_rows($result))); + $res = $pdo->query($query); $feeds_to_update = array(); - while ($line = db_fetch_assoc($result)) { + while ($line = $res->fetch()) { array_push($feeds_to_update, $line['feed_url']); } + if ($debug) _debug(sprintf("Scheduled %d feeds to update...", count($feeds_to_update))); + // Update last_update_started before actually starting the batch // in order to minimize collision risk for parallel daemon tasks if (count($feeds_to_update) > 0) { - $feeds_quoted = array_map(function ($s) { return "'" . db_escape_string($s) . "'"; }, $feeds_to_update); + $feeds_qmarks = arr_qmarks($feeds_to_update); - db_query(sprintf("UPDATE ttrss_feeds SET last_update_started = NOW() - WHERE feed_url IN (%s)", implode(',', $feeds_quoted))); + $tmph = $pdo->prepare("UPDATE ttrss_feeds SET last_update_started = NOW() + WHERE feed_url IN ($feeds_qmarks)"); + $tmph->execute($feeds_to_update); } $nf = 0; @@ -147,38 +157,37 @@ class RSSUtils { $batch_owners = array(); - foreach ($feeds_to_update as $feed) { - if($debug) _debug("Base feed: $feed"); - - //update_rss_feed($line["id"], true); - - // since we have the data cached, we can deal with other feeds with the same url - $tmp_result = db_query("SELECT DISTINCT ttrss_feeds.id,last_updated,ttrss_feeds.owner_uid + // since we have the data cached, we can deal with other feeds with the same url + $usth = $pdo->prepare("SELECT DISTINCT ttrss_feeds.id,last_updated,ttrss_feeds.owner_uid FROM ttrss_feeds, ttrss_users, ttrss_user_prefs WHERE ttrss_user_prefs.owner_uid = ttrss_feeds.owner_uid AND ttrss_users.id = ttrss_user_prefs.owner_uid AND ttrss_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL' AND ttrss_user_prefs.profile IS NULL AND - feed_url = '".db_escape_string($feed)."' + feed_url = ? $update_limit_qpart $login_thresh_qpart ORDER BY ttrss_feeds.id $query_limit"); - if (db_num_rows($tmp_result) > 0) { - while ($tline = db_fetch_assoc($tmp_result)) { - if ($debug) _debug(" => " . $tline["last_updated"] . ", " . $tline["id"] . " " . $tline["owner_uid"]); + foreach ($feeds_to_update as $feed) { + if($debug) _debug("Base feed: $feed"); - if (array_search($tline["owner_uid"], $batch_owners) === FALSE) - array_push($batch_owners, $tline["owner_uid"]); + $usth->execute([$feed]); + //update_rss_feed($line["id"], true); - $fstarted = microtime(true); - RSSUtils::update_rss_feed($tline["id"], true, false); - _debug_suppress(false); + if ($tline = $usth->fetch()) { + if ($debug) _debug(" => " . $tline["last_updated"] . ", " . $tline["id"] . " " . $tline["owner_uid"]); - _debug(sprintf(" %.4f (sec)", microtime(true) - $fstarted)); + if (array_search($tline["owner_uid"], $batch_owners) === FALSE) + array_push($batch_owners, $tline["owner_uid"]); - ++$nf; - } + $fstarted = microtime(true); + RSSUtils::update_rss_feed($tline["id"], true, false); + _debug_suppress(false); + + _debug(sprintf(" %.4f (sec)", microtime(true) - $fstarted)); + + ++$nf; } } @@ -197,85 +206,95 @@ class RSSUtils { Digest::send_headlines_digests($debug); return $nf; - } // this is used when subscribing static function set_basic_feed_info($feed) { - $feed = db_escape_string($feed); + $pdo = Db::pdo(); - $result = db_query("SELECT owner_uid,feed_url,auth_pass,auth_login,auth_pass_encrypted - FROM ttrss_feeds WHERE id = '$feed'"); + $sth = $pdo->prepare("SELECT owner_uid,feed_url,auth_pass,auth_login,auth_pass_encrypted + FROM ttrss_feeds WHERE id = ?"); + $sth->execute([$feed]); - $owner_uid = db_fetch_result($result, 0, "owner_uid"); + if ($row = $sth->fetch()) { - $auth_pass_encrypted = sql_bool_to_bool(db_fetch_result($result, - 0, "auth_pass_encrypted")); + $owner_uid = $row["owner_uid"]; - $auth_login = db_fetch_result($result, 0, "auth_login"); - $auth_pass = db_fetch_result($result, 0, "auth_pass"); + $auth_pass_encrypted = $row["auth_pass_encrypted"]; - if ($auth_pass_encrypted && function_exists("mcrypt_decrypt")) { - require_once "crypt.php"; - $auth_pass = decrypt_string($auth_pass); - } + $auth_login = $row["auth_login"]; + $auth_pass = $row["auth_pass"]; - $fetch_url = db_fetch_result($result, 0, "feed_url"); + if ($auth_pass_encrypted && function_exists("mcrypt_decrypt")) { + require_once "crypt.php"; + $auth_pass = decrypt_string($auth_pass); + } - $pluginhost = new PluginHost(); - $user_plugins = get_pref("_ENABLED_PLUGINS", $owner_uid); + $fetch_url = $row["feed_url"]; - $pluginhost->load(PLUGINS, PluginHost::KIND_ALL); - $pluginhost->load($user_plugins, PluginHost::KIND_USER, $owner_uid); - $pluginhost->load_data(); + $pluginhost = new PluginHost(); + $user_plugins = get_pref("_ENABLED_PLUGINS", $owner_uid); - $basic_info = array(); - foreach ($pluginhost->get_hooks(PluginHost::HOOK_FEED_BASIC_INFO) as $plugin) { - $basic_info = $plugin->hook_feed_basic_info($basic_info, $fetch_url, $owner_uid, $feed, $auth_login, $auth_pass); - } + $pluginhost->load(PLUGINS, PluginHost::KIND_ALL); + $pluginhost->load($user_plugins, PluginHost::KIND_USER, $owner_uid); + $pluginhost->load_data(); - if (!$basic_info) { - $feed_data = fetch_file_contents($fetch_url, false, - $auth_login, $auth_pass, false, - FEED_FETCH_TIMEOUT, - 0); + $basic_info = array(); + foreach ($pluginhost->get_hooks(PluginHost::HOOK_FEED_BASIC_INFO) as $plugin) { + $basic_info = $plugin->hook_feed_basic_info($basic_info, $fetch_url, $owner_uid, $feed, $auth_login, $auth_pass); + } - global $fetch_curl_used; + if (!$basic_info) { + $feed_data = fetch_file_contents($fetch_url, false, + $auth_login, $auth_pass, false, + FEED_FETCH_TIMEOUT, + 0); - if (!$fetch_curl_used) { - $tmp = @gzdecode($feed_data); + global $fetch_curl_used; - if ($tmp) $feed_data = $tmp; - } + if (!$fetch_curl_used) { + $tmp = @gzdecode($feed_data); - $feed_data = trim($feed_data); + if ($tmp) $feed_data = $tmp; + } - $rss = new FeedParser($feed_data); - $rss->init(); + $feed_data = trim($feed_data); - if (!$rss->error()) { - $basic_info = array( - 'title' => db_escape_string(mb_substr($rss->get_title(), 0, 199)), - 'site_url' => db_escape_string(mb_substr(rewrite_relative_url($fetch_url, $rss->get_link()), 0, 245)) - ); + $rss = new FeedParser($feed_data); + $rss->init(); + + if (!$rss->error()) { + $basic_info = array( + 'title' => mb_substr($rss->get_title(), 0, 199), + 'site_url' => mb_substr(rewrite_relative_url($fetch_url, $rss->get_link()), 0, 245) + ); + } } - } - if ($basic_info && is_array($basic_info)) { - $result = db_query("SELECT title, site_url FROM ttrss_feeds WHERE id = '$feed'"); + if ($basic_info && is_array($basic_info)) { + $sth = $pdo->prepare("SELECT title, site_url FROM ttrss_feeds WHERE id = ?"); + $sth->execute([$feed]); - $registered_title = db_fetch_result($result, 0, "title"); - $orig_site_url = db_fetch_result($result, 0, "site_url"); + if ($row = $sth->fetch()) { - if ($basic_info['title'] && (!$registered_title || $registered_title == "[Unknown]")) { - db_query("UPDATE ttrss_feeds SET - title = '${basic_info['title']}' WHERE id = '$feed'"); - } + $registered_title = $row["title"]; + $orig_site_url = $row["site_url"]; + + if ($basic_info['title'] && (!$registered_title || $registered_title == "[Unknown]")) { + + $sth = $pdo->prepare("UPDATE ttrss_feeds SET + title = ? WHERE id = ?"); + $sth->execute([$basic_info['title'], $feed]); + } + + if ($basic_info['site_url'] && $orig_site_url != $basic_info['site_url']) { + $sth = $pdo->prepare("UPDATE ttrss_feeds SET + site_url = ? WHERE id = ?"); + $sth->execute([$basic_info['site_url'], $feed]); + } - if ($basic_info['site_url'] && $orig_site_url != $basic_info['site_url']) { - db_query("UPDATE ttrss_feeds SET - site_url = '${basic_info['site_url']}' WHERE id = '$feed'"); + } } } } @@ -285,21 +304,23 @@ class RSSUtils { */ 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); - $result = db_query("SELECT title FROM ttrss_feeds - WHERE id = '$feed'"); + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT title FROM ttrss_feeds WHERE id = ?"); + $sth->execute([$feed]); - if (db_num_rows($result) == 0) { + if (!$row = $sth->fetch()) { _debug("feed $feed NOT FOUND/SKIPPED", $debug_enabled); user_error("Attempt to update unknown/invalid feed $feed", E_USER_WARNING); return false; } - $title = db_fetch_result($result, 0, "title"); + $title = $row["title"]; // feed was batch-subscribed or something, we need to get basic info // this is not optimal currently as it fetches stuff separately TODO: optimize @@ -308,43 +329,47 @@ class RSSUtils { RSSUtils::set_basic_feed_info($feed); } - $result = db_query("SELECT id,update_interval,auth_login, + $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 - FROM ttrss_feeds WHERE id = '$feed'"); + FROM ttrss_feeds WHERE id = ?"); + $sth->execute([$feed]); + + if ($row = $sth->fetch()) { - $owner_uid = db_fetch_result($result, 0, "owner_uid"); - $mark_unread_on_update = sql_bool_to_bool(db_fetch_result($result, - 0, "mark_unread_on_update")); - $auth_pass_encrypted = sql_bool_to_bool(db_fetch_result($result, - 0, "auth_pass_encrypted")); + $owner_uid = $row["owner_uid"]; + $mark_unread_on_update = $row["mark_unread_on_update"]; + $auth_pass_encrypted = $row["auth_pass_encrypted"]; - db_query("UPDATE ttrss_feeds SET last_update_started = NOW() - WHERE id = '$feed'"); + $sth = $pdo->prepare("UPDATE ttrss_feeds SET last_update_started = NOW() + WHERE id = ?"); + $sth->execute([$feed]); - $auth_login = db_fetch_result($result, 0, "auth_login"); - $auth_pass = db_fetch_result($result, 0, "auth_pass"); + $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); - } + if ($auth_pass_encrypted && function_exists("mcrypt_decrypt")) { + require_once "crypt.php"; + $auth_pass = decrypt_string($auth_pass); + } - $stored_last_modified = db_fetch_result($result, 0, "last_modified"); - $last_unconditional = db_fetch_result($result, 0, "last_unconditional"); - $cache_images = sql_bool_to_bool(db_fetch_result($result, 0, "cache_images")); - $fetch_url = db_fetch_result($result, 0, "feed_url"); - $feed_language = db_escape_string(mb_strtolower(db_fetch_result($result, 0, "feed_language"))); - if (!$feed_language) $feed_language = 'english'; + $stored_last_modified = $row["last_modified"]; + $last_unconditional = $row["last_unconditional"]; + $cache_images = $row["cache_images"]; + $fetch_url = $row["feed_url"]; + $feed_language = mb_strtolower($row["feed_language"]); + if (!$feed_language) $feed_language = 'english'; - $feed = db_escape_string($feed); + } else { + return false; + } $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); @@ -424,14 +449,12 @@ class RSSUtils { _debug("source last modified: " . $fetch_last_modified, $debug_enabled); if ($feed_data && $fetch_last_modified != $stored_last_modified) { - $last_modified_escaped = db_escape_string(substr($fetch_last_modified, 0, 245)); - - db_query("UPDATE ttrss_feeds SET last_modified = '$last_modified_escaped' WHERE id = '$feed'"); - + $sth = $pdo->prepare("UPDATE ttrss_feeds SET last_modified = ? WHERE id = ?"); + $sth->execute([substr($fetch_last_modified, 0, 245), $feed]); } // 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) { @@ -447,18 +470,17 @@ class RSSUtils { _debug("unable to fetch: $fetch_last_error [$fetch_last_error_code]", $debug_enabled); - $error_escaped = ''; - // If-Modified-Since if ($fetch_last_error_code != 304) { - $error_escaped = db_escape_string($fetch_last_error); + $error_message = $fetch_last_error; } else { _debug("source claims data not modified, nothing to do.", $debug_enabled); + $error_message = ""; } - db_query( - "UPDATE ttrss_feeds SET last_error = '$error_escaped', - last_updated = NOW() WHERE id = '$feed'"); + $sth = $pdo->prepare("UPDATE ttrss_feeds SET last_error = ?, + last_updated = NOW() WHERE id = ?"); + $sth->execute([$error_message, $feed]); return; } @@ -470,8 +492,6 @@ class RSSUtils { $rss = new FeedParser($feed_data); $rss->init(); - $feed = db_escape_string($feed); - if (!$rss->error()) { // We use local pluginhost here because we need to load different per-user feed plugins @@ -480,26 +500,27 @@ class RSSUtils { _debug("language: $feed_language", $debug_enabled); _debug("processing feed data...", $debug_enabled); -// db_query("BEGIN"); - if (DB_TYPE == "pgsql") { $favicon_interval_qpart = "favicon_last_checked < NOW() - INTERVAL '12 hour'"; } else { $favicon_interval_qpart = "favicon_last_checked < DATE_SUB(NOW(), INTERVAL 12 HOUR)"; } - $result = db_query("SELECT owner_uid,favicon_avg_color, + $sth = $pdo->prepare("SELECT owner_uid,favicon_avg_color, (favicon_last_checked IS NULL OR $favicon_interval_qpart) AS favicon_needs_check - FROM ttrss_feeds WHERE id = '$feed'"); + FROM ttrss_feeds WHERE id = ?"); + $sth->execute([$feed]); - $favicon_needs_check = sql_bool_to_bool(db_fetch_result($result, 0, - "favicon_needs_check")); - $favicon_avg_color = db_fetch_result($result, 0, "favicon_avg_color"); - - $owner_uid = db_fetch_result($result, 0, "owner_uid"); + if ($row = $sth->fetch()) { + $favicon_needs_check = $row["favicon_needs_check"]; + $favicon_avg_color = $row["favicon_avg_color"]; + $owner_uid = $row["owner_uid"]; + } else { + return false; + } - $site_url = db_escape_string(mb_substr(rewrite_relative_url($fetch_url, $rss->get_link()), 0, 245)); + $site_url = mb_substr(rewrite_relative_url($fetch_url, $rss->get_link()), 0, 245); _debug("site_url: $site_url", $debug_enabled); _debug("feed_title: " . $rss->get_title(), $debug_enabled); @@ -520,23 +541,25 @@ class RSSUtils { if ($favicon_modified_new > $favicon_modified) $favicon_avg_color = ''; + $favicon_colorstring = ""; if (file_exists($favicon_file) && function_exists("imagecreatefromstring") && $favicon_avg_color == '') { require_once "colors.php"; - db_query("UPDATE ttrss_feeds SET favicon_avg_color = 'fail' WHERE - id = '$feed'"); + $sth = $pdo->prepare("UPDATE ttrss_feeds SET favicon_avg_color = 'fail' WHERE + id = ?"); + $sth->execute([$feed]); - $favicon_color = db_escape_string( - calculate_avg_color($favicon_file)); + $favicon_color = calculate_avg_color($favicon_file); + + $favicon_colorstring = ",favicon_avg_color = " . $pdo->quote($favicon_color); - $favicon_colorstring = ",favicon_avg_color = '".$favicon_color."'"; } else if ($favicon_avg_color == 'fail') { _debug("floicon failed on this file, not trying to recalculate avg color", $debug_enabled); } - db_query("UPDATE ttrss_feeds SET favicon_last_checked = NOW() - $favicon_colorstring - WHERE id = '$feed'"); + $sth = $pdo->prepare("UPDATE ttrss_feeds SET favicon_last_checked = NOW() + $favicon_colorstring WHERE id = ?"); + $sth->execute([$feed]); } _debug("loading filters & labels...", $debug_enabled); @@ -554,10 +577,11 @@ class RSSUtils { if (!is_array($items)) { _debug("no articles found.", $debug_enabled); - db_query("UPDATE ttrss_feeds - SET last_updated = NOW(), last_unconditional = NOW(), last_error = '' WHERE id = '$feed'"); + $sth = $pdo->prepare("UPDATE ttrss_feeds + SET last_updated = NOW(), last_unconditional = NOW(), last_error = '' WHERE id = ?"); + $sth->execute([$feed]); - return; // no articles + return true; // no articles } _debug("processing articles...", $debug_enabled); @@ -565,29 +589,34 @@ class RSSUtils { $tstart = time(); foreach ($items as $item) { - if ($_REQUEST['xdebug'] == 3) { + $pdo->beginTransaction(); + + if (clean($_REQUEST['xdebug']) == 3) { print_r($item); } if (ini_get("max_execution_time") > 0 && time() - $tstart >= ini_get("max_execution_time") * 0.7) { _debug("looks like there's too many articles to process at once, breaking out", $debug_enabled); + $pdo->commit(); break; } - $entry_guid = $item->get_id(); - if (!$entry_guid) $entry_guid = $item->get_link(); + $entry_guid = strip_tags($item->get_id()); + if (!$entry_guid) $entry_guid = strip_tags($item->get_link()); if (!$entry_guid) $entry_guid = RSSUtils::make_guid_from_title($item->get_title()); - if (!$entry_guid) continue; + + if (!$entry_guid) { + $pdo->commit(); + continue; + } $entry_guid = "$owner_uid,$entry_guid"; - $entry_guid_hashed = db_escape_string('SHA1:' . sha1($entry_guid)); + $entry_guid_hashed = 'SHA1:' . sha1($entry_guid); _debug("guid $entry_guid / $entry_guid_hashed", $debug_enabled); - $entry_timestamp = ""; - - $entry_timestamp = $item->get_date(); + $entry_timestamp = strip_tags($item->get_date()); _debug("orig date: " . $item->get_date(), $debug_enabled); @@ -599,9 +628,7 @@ class RSSUtils { _debug("date $entry_timestamp [$entry_timestamp_fmt]", $debug_enabled); -// $entry_title = html_entity_decode($item->get_title(), ENT_COMPAT, 'UTF-8'); -// $entry_title = decode_numeric_entities($entry_title); - $entry_title = $item->get_title(); + $entry_title = strip_tags($item->get_title()); $entry_link = rewrite_relative_url($site_url, $item->get_link()); @@ -613,17 +640,17 @@ class RSSUtils { $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_comments = db_escape_string(mb_substr($item->get_comments_url(), 0, 245)); + $entry_comments = mb_substr(strip_tags($item->get_comments_url()), 0, 245); $num_comments = (int) $item->get_comments_count(); - $entry_author = $item->get_author(); // escaped later - $entry_guid = db_escape_string(mb_substr($entry_guid, 0, 245)); + $entry_author = strip_tags($item->get_author()); + $entry_guid = mb_substr($entry_guid, 0, 245); _debug("author $entry_author", $debug_enabled); _debug("num_comments: $num_comments", $debug_enabled); @@ -650,18 +677,18 @@ class RSSUtils { _debug("done collecting data.", $debug_enabled); - $result = db_query("SELECT id, content_hash, lang FROM ttrss_entries - WHERE guid = '".db_escape_string($entry_guid)."' OR guid = '$entry_guid_hashed'"); + $sth = $pdo->prepare("SELECT id, content_hash, lang FROM ttrss_entries + WHERE guid = ? OR guid = ?"); + $sth->execute([$entry_guid, $entry_guid_hashed]); - if (db_num_rows($result) != 0) { - $base_entry_id = db_fetch_result($result, 0, "id"); - $entry_stored_hash = db_fetch_result($result, 0, "content_hash"); + if ($row = $sth->fetch()) { + $base_entry_id = $row["id"]; + $entry_stored_hash = $row["content_hash"]; $article_labels = Article::get_article_labels($base_entry_id, $owner_uid); - $entry_language = db_fetch_result($result, 0, "lang"); + $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)); - } else { $base_entry_id = false; $entry_stored_hash = ""; @@ -701,11 +728,11 @@ class RSSUtils { // dupes when the entry gets purged and reinserted again e.g. // in the case of SLOW SLOW OMG SLOW updating feeds - $base_entry_id = db_fetch_result($result, 0, "id"); - - db_query("UPDATE ttrss_entries SET date_updated = NOW() - WHERE id = '$base_entry_id'"); + $sth = $pdo->prepare("UPDATE ttrss_entries SET date_updated = NOW() + WHERE id = ?"); + $sth->execute([$base_entry_id]); + $pdo->commit(); continue; } @@ -722,14 +749,12 @@ class RSSUtils { $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"; } - $entry_plugin_data = db_escape_string($entry_plugin_data); - _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 @@ -794,15 +819,14 @@ class RSSUtils { } $entry_tags = $article["tags"]; - $entry_guid = db_escape_string($entry_guid); - $entry_title = db_escape_string($article["title"]); - $entry_author = db_escape_string(mb_substr($article["author"], 0, 245)); - $entry_link = db_escape_string($article["link"]); + $entry_title = strip_tags($article["title"]); + $entry_author = mb_substr(strip_tags($article["author"]), 0, 245); + $entry_link = strip_tags($article["link"]); $entry_content = $article["content"]; // escaped below $entry_force_catchup = $article["force_catchup"]; $article_labels = $article["labels"]; $entry_score_modifier = (int) $article["score_modifier"]; - $entry_language = db_escape_string($article["language"]); + $entry_language = $article["language"]; if ($debug_enabled) { _debug("article labels:", $debug_enabled); @@ -817,22 +841,19 @@ class RSSUtils { if ($cache_images && is_writable(CACHE_DIR . '/images')) RSSUtils::cache_media($entry_content, $site_url, $debug_enabled); - $entry_content = db_escape_string($entry_content, false); + $csth = $pdo->prepare("SELECT id FROM ttrss_entries + WHERE guid = ? OR guid = ?"); + $csth->execute([$entry_guid, $entry_guid_hashed]); - //db_query("BEGIN"); - - $result = db_query("SELECT id FROM ttrss_entries - WHERE (guid = '$entry_guid' OR guid = '$entry_guid_hashed')"); - - if (db_num_rows($result) == 0) { + if (!$row = $csth->fetch()) { _debug("base guid [$entry_guid or $entry_guid_hashed] not found, creating...", $debug_enabled); // base post entry does not exist, create it - db_query( + $usth = $pdo->prepare( "INSERT INTO ttrss_entries - (title, + (title, guid, link, updated, @@ -847,48 +868,40 @@ class RSSUtils { lang, author) VALUES - ('$entry_title', - '$entry_guid_hashed', - '$entry_link', - '$entry_timestamp_fmt', - '$entry_content', - '$entry_current_hash', + (?, ?, ?, ?, ?, ?, false, NOW(), - '$date_feed_processed', - '$entry_comments', - '$num_comments', - '$entry_plugin_data', - '$entry_language', - '$entry_author')"); + ?, ?, ?, ?, ?, ?)"); + + $usth->execute([$entry_title, + $entry_guid_hashed, + $entry_link, + $entry_timestamp_fmt, + "$entry_content", + $entry_current_hash, + $date_feed_processed, + $entry_comments, + (int)$num_comments, + $entry_plugin_data, + "$entry_language", + "$entry_author"]); } - // now it should exist, if not - bad luck then - - $result = db_query("SELECT id FROM ttrss_entries - WHERE guid = '$entry_guid' OR guid = '$entry_guid_hashed'"); + $csth->execute([$entry_guid, $entry_guid_hashed]); $entry_ref_id = 0; $entry_int_id = 0; - if (db_num_rows($result) == 1) { + if ($row = $csth->fetch()) { _debug("base guid found, checking for user record", $debug_enabled); - $ref_id = db_fetch_result($result, 0, "id"); + $ref_id = $row['id']; $entry_ref_id = $ref_id; - /* $stored_guid = db_fetch_result($result, 0, "guid"); - if ($stored_guid != $entry_guid_hashed) { - if ($debug_enabled) _debug("upgrading compat guid to hashed one", $debug_enabled); - - db_query("UPDATE ttrss_entries SET guid = '$entry_guid_hashed' WHERE - id = '$ref_id'"); - } */ - if (RSSUtils::find_article_filter($article_filters, "filter")) { - //db_query("COMMIT"); // close transaction in progress + $pdo->commit(); continue; } @@ -898,103 +911,114 @@ class RSSUtils { // check for user post link to main table - $query = "SELECT ref_id, int_id FROM ttrss_user_entries WHERE - ref_id = '$ref_id' AND owner_uid = '$owner_uid'"; - -// if ($_REQUEST["xdebug"]) print "$query\n"; - - $result = db_query($query); + $sth = $pdo->prepare("SELECT ref_id, int_id FROM ttrss_user_entries WHERE + ref_id = ? AND owner_uid = ?"); + $sth->execute([$ref_id, $owner_uid]); // okay it doesn't exist - create user entry - if (db_num_rows($result) == 0) { + if ($row = $sth->fetch()) { + $entry_ref_id = $row["ref_id"]; + $entry_int_id = $row["int_id"]; + _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) { - $unread = 'true'; - $last_read_qpart = 'NULL'; + $unread = 1; + $last_read_qpart = null; } else { - $unread = 'false'; + $unread = 0; $last_read_qpart = 'NOW()'; } if (RSSUtils::find_article_filter($article_filters, 'mark') || $score > 1000) { - $marked = 'true'; + $marked = 1; } else { - $marked = 'false'; + $marked = 0; } if (RSSUtils::find_article_filter($article_filters, 'publish')) { - $published = 'true'; + $published = 1; } else { - $published = 'false'; + $published = 0; } - $last_marked = ($marked == 'true') ? 'NOW()' : 'NULL'; - $last_published = ($published == 'true') ? 'NOW()' : 'NULL'; + $last_marked = ($marked == 'true') ? 'NOW()' : null; + $last_published = ($published == 'true') ? 'NOW()' : null; - $result = db_query( + $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 ('$ref_id', '$owner_uid', '$feed', $unread, - $last_read_qpart, $marked, $published, '$score', '', '', - '', $last_marked, $last_published)"); + VALUES (?, ?, ?, ?, ?, ?, ?, ?, '', '', '', ".$last_marked.", ".$last_published.")"); - $result = db_query( - "SELECT int_id FROM ttrss_user_entries WHERE - ref_id = '$ref_id' AND owner_uid = '$owner_uid' AND - feed_id = '$feed' LIMIT 1"); + $sth->execute([$ref_id, $owner_uid, $feed, $unread, $last_read_qpart, $marked, + $published, $score]); - if (db_num_rows($result) == 1) { - $entry_int_id = db_fetch_result($result, 0, "int_id"); - } - } else { - _debug("user record FOUND", $debug_enabled); + $sth = $pdo->prepare("SELECT int_id FROM ttrss_user_entries WHERE + ref_id = ? AND owner_uid = ? AND + feed_id = ? LIMIT 1"); + + $sth->execute([$ref_id, $owner_uid, $feed]); - $entry_ref_id = db_fetch_result($result, 0, "ref_id"); - $entry_int_id = db_fetch_result($result, 0, "int_id"); + if ($row = $sth->fetch()) + $entry_int_id = $row['int_id']; } - _debug("RID: $entry_ref_id, IID: $entry_int_id", $debug_enabled); + _debug("resulting RID: $entry_ref_id, IID: $entry_int_id", $debug_enabled); if (DB_TYPE == "pgsql") { - $tsvector_combined = db_escape_string(mb_substr($entry_title . ' ' . strip_tags(str_replace('<', ' <', $entry_content)), - 0, 1000000)); + $tsvector_combined = mb_substr($entry_title . ' ' . + preg_replace('/[<\?\:]/', ' ', strip_tags($entry_content)), + 0, 1000000); - $tsvector_qpart = "tsvector_combined = to_tsvector('$feed_language', '$tsvector_combined'),"; + $tsvector_qpart = "tsvector_combined = to_tsvector(".$pdo->quote($feed_language).", ".$pdo->quote($tsvector_combined)."),"; } else { $tsvector_qpart = ""; } - db_query("UPDATE ttrss_entries - SET title = '$entry_title', - content = '$entry_content', - content_hash = '$entry_current_hash', - updated = '$entry_timestamp_fmt', + //_debug($tsvector_qpart); + + $sth = $pdo->prepare("UPDATE ttrss_entries + SET title = :title, $tsvector_qpart - num_comments = '$num_comments', - plugin_data = '$entry_plugin_data', - author = '$entry_author', - lang = '$entry_language' - WHERE id = '$ref_id'"); + content = :content, + content_hash = :content_hash, + updated = :updated, + num_comments = :num_comments, + plugin_data = :plugin_data, + author = :author, + lang = :lang + WHERE id = :id"); + + $sth->execute([":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]); // update aux data - db_query("UPDATE ttrss_user_entries - SET score = '$score' WHERE ref_id = '$ref_id'"); + $sth = $pdo->prepare("UPDATE ttrss_user_entries + SET score = ? WHERE ref_id = ?"); + $sth->execute([$score, $ref_id]); if ($mark_unread_on_update) { _debug("article updated, marking unread as requested.", $debug_enabled); - db_query("UPDATE ttrss_user_entries - SET last_read = null, unread = true WHERE ref_id = '$ref_id'"); + $sth = $pdo->prepare("UPDATE ttrss_user_entries + SET last_read = null, unread = true WHERE ref_id = ?"); + $sth->execute([$ref_id]); } } - //db_query("COMMIT"); - _debug("assigning labels [other]...", $debug_enabled); foreach ($article_labels as $label) { @@ -1031,31 +1055,28 @@ class RSSUtils { print_r($enclosures); } - //db_query("BEGIN"); + $esth = $pdo->prepare("SELECT id FROM ttrss_enclosures + WHERE content_url = ? AND post_id = ?"); -// debugging -// db_query("DELETE FROM ttrss_enclosures WHERE post_id = '$entry_ref_id'"); + $usth = $pdo->prepare("INSERT INTO ttrss_enclosures + (content_url, content_type, title, duration, post_id, width, height) VALUES + (?, ?, ?, ?, ?, ?, ?)"); foreach ($enclosures as $enc) { - $enc_url = db_escape_string($enc[0]); - $enc_type = db_escape_string($enc[1]); - $enc_dur = db_escape_string($enc[2]); - $enc_title = db_escape_string($enc[3]); + $enc_url = $enc[0]; + $enc_type = $enc[1]; + $enc_dur = (int)$enc[2]; + $enc_title = $enc[3]; $enc_width = intval($enc[4]); $enc_height = intval($enc[5]); - $result = db_query("SELECT id FROM ttrss_enclosures - WHERE content_url = '$enc_url' AND post_id = '$entry_ref_id'"); + $esth->execute([$enc_url, $entry_ref_id]); - if (db_num_rows($result) == 0) { - db_query("INSERT INTO ttrss_enclosures - (content_url, content_type, title, duration, post_id, width, height) VALUES - ('$enc_url', '$enc_type', '$enc_title', '$enc_dur', '$entry_ref_id', $enc_width, $enc_height)"); + if (!$esth->fetch()) { + $usth->execute([$enc_url, $enc_type, (string)$enc_title, $enc_dur, $entry_ref_id, $enc_width, $enc_height]); } } - //db_query("COMMIT"); - // check for manual tags (we have to do it here since they're loaded from filters) foreach ($article_filters as $f) { @@ -1098,24 +1119,24 @@ class RSSUtils { if (count($filtered_tags) > 0) { - //db_query("BEGIN"); + $tsth = $pdo->prepare("SELECT id FROM ttrss_tags + WHERE tag_name = ? AND post_int_id = ? AND + owner_uid = ? LIMIT 1"); + + $usth = $pdo->prepare("INSERT INTO ttrss_tags + (owner_uid,tag_name,post_int_id) + VALUES (?, ?, ?)"); foreach ($filtered_tags as $tag) { $tag = sanitize_tag($tag); - $tag = db_escape_string($tag); if (!tag_is_valid($tag)) continue; - $result = db_query("SELECT id FROM ttrss_tags - WHERE tag_name = '$tag' AND post_int_id = '$entry_int_id' AND - owner_uid = '$owner_uid' LIMIT 1"); + $tsth->execute([$tag, $entry_int_id, $owner_uid]); - if ($result && db_num_rows($result) == 0) { - - db_query("INSERT INTO ttrss_tags - (owner_uid,tag_name,post_int_id) - VALUES ('$owner_uid','$tag', '$entry_int_id')"); + if (!$tsth->fetch()) { + $usth->execute([$owner_uid, $tag, $entry_int_id]); } array_push($tags_to_cache, $tag); @@ -1125,30 +1146,30 @@ class RSSUtils { $tags_to_cache = array_unique($tags_to_cache); - $tags_str = db_escape_string(join(",", $tags_to_cache)); - - db_query("UPDATE ttrss_user_entries - SET tag_cache = '$tags_str' WHERE ref_id = '$entry_ref_id' - AND owner_uid = $owner_uid"); + $tags_str = join(",", $tags_to_cache); - //db_query("COMMIT"); + $tsth = $pdo->prepare("UPDATE ttrss_user_entries + SET tag_cache = ? WHERE ref_id = ? + AND owner_uid = ?"); + $tsth->execute([$tags_str, $entry_ref_id, $owner_uid]); } _debug("article processed", $debug_enabled); + + $pdo->commit(); } _debug("purging feed...", $debug_enabled); purge_feed($feed, 0, $debug_enabled); - db_query("UPDATE ttrss_feeds - SET last_updated = NOW(), last_unconditional = NOW(), last_error = '' WHERE id = '$feed'"); - -// db_query("COMMIT"); + $sth = $pdo->prepare("UPDATE ttrss_feeds + SET last_updated = NOW(), last_unconditional = NOW(), last_error = '' WHERE id = ?"); + $sth->execute([$feed]); } else { - $error_msg = db_escape_string(mb_substr($rss->error(), 0, 245)); + $error_msg = mb_substr($rss->error(), 0, 245); _debug("fetch error: $error_msg", $debug_enabled); @@ -1158,12 +1179,12 @@ class RSSUtils { } } - db_query( - "UPDATE ttrss_feeds SET last_error = '$error_msg', - last_updated = NOW(), last_unconditional = NOW() WHERE id = '$feed'"); + $sth = $pdo->prepare("UPDATE ttrss_feeds SET last_error = ?, + last_updated = NOW(), last_unconditional = NOW() WHERE id = ?"); + $sth->execute([$error_msg, $feed]); unset($rss); - return; + return false; } _debug("done", $debug_enabled); @@ -1232,14 +1253,15 @@ class RSSUtils { static function expire_error_log($debug) { if ($debug) _debug("Removing old error log entries..."); + $pdo = Db::pdo(); + if (DB_TYPE == "pgsql") { - db_query("DELETE FROM ttrss_error_log + $pdo->query("DELETE FROM ttrss_error_log WHERE created_at < NOW() - INTERVAL '7 days'"); } else { - db_query("DELETE FROM ttrss_error_log + $pdo->query("DELETE FROM ttrss_error_log WHERE created_at < DATE_SUB(NOW(), INTERVAL 7 DAY)"); } - } static function expire_lock_files($debug) { @@ -1264,7 +1286,7 @@ class RSSUtils { } 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"); @@ -1446,19 +1468,23 @@ class RSSUtils { } static function cleanup_counters_cache($debug) { - $result = db_query("DELETE FROM ttrss_counters_cache + $pdo = Db::pdo(); + + $res = $pdo->query("DELETE FROM ttrss_counters_cache WHERE feed_id > 0 AND (SELECT COUNT(id) FROM ttrss_feeds WHERE id = feed_id AND ttrss_counters_cache.owner_uid = ttrss_feeds.owner_uid) = 0"); - $frows = db_affected_rows($result); - $result = db_query("DELETE FROM ttrss_cat_counters_cache + $frows = $res->rowCount(); + + $res = $pdo->query("DELETE FROM ttrss_cat_counters_cache WHERE feed_id > 0 AND (SELECT COUNT(id) FROM ttrss_feed_categories WHERE id = feed_id AND ttrss_cat_counters_cache.owner_uid = ttrss_feed_categories.owner_uid) = 0"); - $crows = db_affected_rows($result); + + $crows = $res->rowCount(); if ($debug) _debug("Removed $frows (feeds) $crows (cats) orphaned counter cache entries."); } diff --git a/css/cdm.less b/css/cdm.less index 5a79addbd..94f863423 100644 --- a/css/cdm.less +++ b/css/cdm.less @@ -28,7 +28,7 @@ } span.updated { - color : #555; + color : @default-text; font-weight : normal; font-size : 11px; white-space : nowrap; @@ -59,7 +59,7 @@ height : 30px; padding-left : 5px; font-weight : normal; - color : #555; + color : @default-text; clear : both; > * { @@ -150,7 +150,7 @@ div.cdm.expandable.active div.cdmHeader span.titleWrap { div.cdm.expandable div.cdmHeader a.title { font-weight : 600; - color : #555; + color : @default-text; font-size : 14px; -webkit-transition : color 0.2s; transition : color 0.2s; @@ -197,7 +197,7 @@ div.cdm.expanded.Unread div.cdmHeader a.title { } div.cdm.expanded div.cdmContent { - color : #555; + color : @default-text; } div.cdm.expanded.Unread div.cdmContent { @@ -218,7 +218,7 @@ span.cdmExcerpt { div.cdmContent div.postEnclosures { margin-top : 1em; - color : #555; + color : @default-text; } div.cdmFeedTitle { @@ -228,12 +228,12 @@ div.cdmFeedTitle { } div.cdmFeedTitle a.title { - color : #555; + color : @default-text; font-weight : bold; } div.cdmFeedTitle a { - color : #555; + color : @default-text; } div.cdmFeedTitle a:hover { @@ -248,7 +248,7 @@ div.cdmHeader span.hlFeed { div.cdmHeader div.hlFeed, div.cdmHeader div.hlFeed a { vertical-align : middle; - color : #555; + color : @default-text; font-weight : normal; font-style : italic; font-size : 11px; @@ -274,7 +274,7 @@ div.cdmContentInner iframe { div.cdmHeader span.author { white-space : nowrap; - color : #555; + color : @default-text; font-size : 11px; font-weight : normal; } @@ -288,7 +288,7 @@ div#floatingTitle { border: 0px solid #ddd; border-bottom-width: 1px; background : white; - color : #555; + color : @default-text; box-shadow : 0px 1px 1px -1px rgba(0,0,0,0.1); > * { @@ -304,7 +304,7 @@ div#floatingTitle { } span.author { - color : #555; + color : @default-text; font-size : 11px; font-weight : normal; } @@ -325,7 +325,7 @@ div#floatingTitle { div.hlFeed { padding-right : 10px; - color : #555; + color : @default-text; font-weight : normal; font-style : italic; font-size : 11px; @@ -341,12 +341,12 @@ div#floatingTitle { span.updated { padding-right : 10px; white-space : nowrap; - color : #555; + color : @default-text; font-size : 11px; } div.hlFeed a { - color : #555; + color : @default-text; } span.titleWrap { @@ -403,7 +403,7 @@ div#floatingTitle.Unread a.title { a.catchup { text-align : right; - color : #555; + color : @default-text; padding-right : 10px; font-size : 11px; white-space : nowrap; diff --git a/css/default.css b/css/default.css index 0abe17236..504d456f6 100644 --- a/css/default.css +++ b/css/default.css @@ -1 +1 @@ -body{background:#fff;color:#000;margin:0;padding:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px}body#ttrssMain{overflow:hidden;max-height:100%}:focus{outline:none}div.postReply{padding:0}div.postReply div.postHeader{padding:5px;margin-right:4px;color:#909090;border:0 solid #ddd;border-bottom-width:1px}div.postReply div.postHeader div.postDate{text-align:right;color:#909090;float:right}div.postReply div.postHeader div{padding-bottom:3px}div.postReply div.postHeader span.author{color:#555;font-size:11px;font-weight:normal}div.postReply div.postTitle{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600;text-rendering:optimizelegibility;font-family:"Segoe WP Semibold","Segoe UI Semibold","Segoe UI Web Semibold","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}div.postReply div.postDate{padding-left:10px}div.postReply div.postContent{padding:10px;font-size:16px}div.postReply div.postContent img,div.postReply div.postContent video{border-width:0;max-width:98%;height:auto}div.postReply div.postContent h1{font-size:16px}div.postReply div.postContent h2,div.postReply div.postContent h3,div.postReply div.postContent h4{font-size:15px}div.postReply div.postContent p{hyphens:auto}div.postReply div.postContent iframe{min-width:50%;max-width:98%}div.postReply div.postEnclosures{color:#555}div.postReply img.tagsPic{width:16px;height:16px;margin-left:4px;vertical-align:middle}div.postReply span.author{font-size:12px}div.articleNote{background-color:#fff7d5;padding:5px;margin:5px;border:1px solid #e7d796;color:#9a8c59}div.articleNote div.noteEdit{float:right;cursor:pointer}h1{font-size:18px;font-weight:600;text-rendering:optimizelegibility;font-family:"Segoe UI Web","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}h2{font-size:16px;font-weight:600;border:0 solid #ecf4ff;border-bottom-width:1px;text-rendering:optimizelegibility;font-family:"Segoe UI Web","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}h3{font-size:13px;border:0 solid #ecf4ff;border-bottom-width:1px;font-weight:600;text-rendering:optimizelegibility;font-family:"Segoe UI Web","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}h4{font-size:14px;font-weight:600;text-rendering:optimizelegibility;font-family:"Segoe UI Web","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}hr{border:0 solid #ccc;border-bottom-width:1px}a{color:#08c;text-decoration:none}a:hover{color:#046;text-decoration:underline}#notify.visible{transform:translate(0, -35px)}#notify{bottom:-35px;right:0;height:20px;left:0;border-width:1px 0 0 0;border-style:solid;position:fixed;font-size:12px;z-index:99;padding:5px;box-shadow:0 -2px 2px rgba(0,0,0,0.1);transition:all .5s ease-in-out}#notify img{vertical-align:middle;max-height:14px}#notify span.msg{width:100%}#notify img.close{cursor:pointer}#notify span{display:table-cell;vertical-align:middle;padding:2px}.notify{border-color:#d7c47a;background-color:#fff7d5}.notify.notify_progress{border-color:#d7c47a;background-color:#fff7d5}.notify.notify_info{border-color:#52a8ec;background-color:#ecf4ff}.notify.notify_error{background-color:#fcc;border-color:#f00}.hl{border:0 solid #ddd;border-bottom-width:1px;padding:1px}.hl div.hlTitle{display:table-cell;cursor:pointer;width:100%;vertical-align:middle;overflow:hidden;white-space:nowrap;max-width:500px;text-overflow:ellipsis;padding:4px 6px}.hl div.hlLeft{display:table-cell;vertical-align:middle;white-space:nowrap}.hl div.hlRight{display:table-cell;white-space:nowrap;text-align:right;vertical-align:middle}.hl div.hlRight img{max-width:16px;max-height:16px}.hl span.hlFeed{display:table-cell;vertical-align:middle;text-align:right}.hl span.hlFeed a{border-radius:4px;display:inline-block;padding:1px 4px 1px 4px;font-size:11px;font-style:italic;font-weight:normal;color:#555;white-space:nowrap}.hl span.hlFeed a:hover{color:#52a8ec}.hl span.hlUpdated{color:#555;display:table-cell;vertical-align:middle;text-align:right;font-size:11px;white-space:nowrap;padding-left:10px}.hl span.hlUpdated div{display:inline-block}.hl div.hlLeft{padding-left:8px}.hl div.hlLeft input{margin-left:4px;margin-right:4px}.hl div.hlLeft img,.hl div.hlRight img{margin:0 4px}.hl div.hlLeft img{width:16px;height:16px}.hl div.hlTitle a{font-weight:600;text-rendering:optimizelegibility;font-family:"Segoe UI Web","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif;color:#777}.hl a.title.high,.hl span.hlContent.high .contentPreview{color:#0a0}.hl.Unread a.title.high,.hl.Unread span.hlContent.high .contentPreview{color:#0d0}.hl a.title.low,span.hlContent.low .contentPreview,.hl.Unread a.title.low,.hl.Unread span.hlContent.low .contentPreview{color:#909090;text-decoration:line-through}.hl.Unread div.hlTitle a{color:#000}.hl.active{background:#ecf4ff ! important}.hl.active div.hlTitle a{color:#52a8ec}.hl.Selected{background:#f9fbff}.hl.Grayed{color:#909090}div.filterTestHolder{height:300px;overflow:auto;border-color:#ddd;border-style:solid;margin:0 0 5px 0;border-width:1px}#content-insert blockquote,#headlines-frame blockquote,.dijitContentPane blockquote{margin:5px 0 5px 0;color:#555;padding-left:10px;border:0 solid #ccc;border-left-width:4px}#content-insert code,#headlines-frame code,.dijitContentPane code{color:#090;font-family:monospace;font-size:12px}#content-insert pre,#headlines-frame pre,.dijitContentPane pre{margin:5px 0 5px 0;padding:10px;color:#555;font-family:monospace;font-size:12px;border:0 solid #ccc;background:#f5f5f5;display:block;max-width:98%;overflow:auto}.alert{padding:8px 35px 8px 14px;margin-bottom:10px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px;cursor:pointer}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}ul.nomarks{list-style-type:none;margin:0;padding:10px}div.prefHelp{color:#555;padding:5px}.insensitive{color:#555}.small{font-size:11px}#main-toolbar>*{white-space:nowrap;display:table-cell;color:#999;overflow:hidden}#main-toolbar>*,#main-toolbar table *,#main-toolbar .actionChooser *{text-rendering:optimizelegibility;font-family:"Segoe UI Web","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px}#main-toolbar #headlines-toolbar{padding-right:4px;width:100%}#main-toolbar #headlines-toolbar span.holder{display:table;width:100%}#main-toolbar #headlines-toolbar span.holder>*{display:table-cell}#main-toolbar #headlines-toolbar .main{text-align:right}#main-toolbar #headlines-toolbar .main,#main-toolbar #headlines-toolbar .r{line-height:24px}#main-toolbar #headlines-toolbar span.r img{margin-right:4px;position:relative;top:3px}#main-toolbar #headlines-toolbar span.r .error a{color:#f00}#main-toolbar #selected_prompt{font-style:italic;text-align:right;margin-right:4px}@media (max-width:992px){#main-toolbar #selected_prompt{display:none}}span.contentPreview{color:#999;font-weight:normal;font-size:12px;padding-left:4px}span.hlLabelRef{background-color:#fff7d5;font-size:8px;color:#063064;font-weight:normal;margin-left:2px;padding:1px 4px 1px 4px;display:inline-block;vertical-align:middle;white-space:nowrap;border-radius:4px}img.markedPic,img.pubPic{cursor:pointer;vertical-align:middle;opacity:.5;transition:opacity .25s}img.markedPic:hover,img.pubPic:hover{opacity:1}img[src*='pub_set.png'],img[src*='mark_set.png']{opacity:1}div.tagCloudContainer{border:1px solid #ddd;margin:5px 0 5px 0;padding:5px;text-align:center}div.errorExplained{border:1px solid #ddd;margin:5px 0 5px 0;padding:5px}ul.feedErrorsList{max-height:300px;overflow:auto;list-style-type:none;border:1px solid #ddd;margin:0 0 5px 0;padding:5px}ul.feedErrorsList em{color:#555}ul.browseFeedList{height:300px;overflow:auto;border-width:0 1px 1px 1px;border-color:#ddd;border-style:solid;margin:0 0 5px 0;background-color:#fff;list-style-type:none;padding:0}ul.browseFeedList li{margin:0;padding:2px 4px 2px 4px}.browseFeedList span.subscribers{color:#808080}ul.compact{list-style-type:none;margin:0;padding:0}ul.compact li{margin:0;padding:0}.noborder{border-width:0}#overlay{background:#fff;left:0;top:0;height:100%;width:100%;z-index:100;position:absolute}#overlay_inner{font-weight:bold;margin:1em}form{margin:0;padding:0}div.loadingPrompt{padding:1em;text-align:center;font-weight:bold}div.whiteBox{margin-left:1px;text-align:center;padding:1em 1em 0 1em;font-size:11px;border:0 solid #ddd;border-bottom-width:1px}div.autocomplete{position:absolute;width:250px;background-color:#fff;border:1px solid #789;margin:0;padding:0}div.autocomplete ul{list-style-type:none;margin:0;padding:0}div.autocomplete ul li.selected{background-color:#fff7d5}div.autocomplete ul li{list-style-type:none;display:block;margin:0;padding:2px;height:32px;cursor:pointer}div#headlines-frame.wide .hlTitle{max-width:none;overflow:visible;white-space:normal}div#headlines-frame.wide .hl .hlFeed{display:none}img.hlScorePic{vertical-align:middle;width:16px;height:16px}div.dlgSec{font-size:12px;color:#555;font-weight:bold;clear:both;height:20px}div.dlgSecCont{position:relative;left:150px;top:-20px;float:left;font-size:12px;font-weight:normal}div.dlgSecCont>*{position:relative;top:-2px}div.dlgSecCont hr,div.dlgSecSimple hr{height:0;line-height:0;border:0 solid transparent;margin:2px}div.dlgButtons{text-align:right;clear:both}span.labelColorIndicator{height:16px;width:16px;border-radius:4px;line-height:14px;vertical-align:middle;font-size:9px;display:inline-block;border:1px solid #ccc;background-color:#fff7d5;color:#063064;text-align:center}div#cmdline{position:absolute;left:5px;bottom:5px;font-size:11px;color:#555;font-weight:bold;background-color:#fff;border:1px solid #52a8ec;padding:3px 5px 3px 5px;z-index:5}#feed_browser_spinner{vertical-align:middle;height:18px;width:18px}div.fatalError{margin-bottom:10px}div.fatalError button{margin-top:5px}div.fatalError textarea{width:565px;height:200px}#ttrssMain #main{border-width:0;margin:0;padding:0}#header-wrap{border-width:0;margin:0;padding:0}#content-wrap{padding:0;border-width:0;margin:0}#feeds-holder{padding:0;border:0 solid #ddd;overflow:hidden;background:#f5f5f5;box-shadow:inset -1px 0 2px -1px rgba(0,0,0,0.1);-webkit-overflow-scrolling:touch}#headlines-wrap-inner{padding:0;margin:0;border-width:0}#headlines-frame{padding:0;border:0 #ddd;margin-top:0;-webkit-overflow-scrolling:touch;-webkit-transform:translateZ(0);-webkit-backface-visibility:hidden}#headlines-toolbar_splitter,#toolbar_splitter{display:none}#toolbar{padding:0;margin:0;border-width:0;white-space:nowrap;font-size:12px}#main-toolbar{background:#fff;border:0 solid #ddd;border-bottom-width:1px;padding-left:4px;height:26px}#header{border-width:0;text-align:right;color:#555;padding:5px 5px 0 0;margin:0;position:absolute;right:0;top:0;z-index:5}#footer{text-align:center;color:#555;padding:4px 4px 8px 4px;border-width:0}#content-insert{padding:0;border-color:#ddd;border-width:0;line-height:1.5;font-size:15px;overflow:auto;-webkit-overflow-scrolling:touch}#feedTree .dijitTreeRow .dijitTreeLabel.Unread{font-weight:bold}#feedTree .dijitTreeRow.Error .dijitTreeLabel{color:#f00}img.feedIcon,img.tinyFeedIcon{width:16px;height:16px;line-height:16px;vertical-align:middle;display:inline-block}.player{display:inline-block;color:#555;font-size:11px;font-family:sans-serif;border:1px solid #555;padding:0 4px 0 4px;margin:0 2px 0 2px;width:50px;text-align:center;background:#fff}.player.playing{color:#00c000;border-color:#00c000}.player:hover{background:#f0f0f0;cursor:pointer}#headlines-spacer{height:100%;margin-left:1px;text-align:center;color:#555;font-size:11px;font-style:italic}#headlines-spacer a,#headlines-spacer span{color:#555;padding:10px;display:block}#headlines-spacer a:hover{color:#52a8ec}ul#filterDlg_Matches,ul#filterDlg_Actions{max-height:100px;overflow:auto;list-style-type:none;border-style:solid;border-color:#ddd;border-width:0 1px 1px 1px;background-color:#fff;margin:0 0 5px 0;padding:0}ul#filterDlg_Matches li,ul#filterDlg_Actions li{cursor:pointer;padding:0 0 0 5px}ul.helpKbList{max-height:300px;overflow:auto;list-style-type:none;border:1px solid #ddd;margin:0 0 5px 0;padding:5px}ul.helpKbList span.hksequence{width:6em;margin-left:20px;color:#52a8ec;font-weight:bold;display:inline-block}ul.helpKbList h2{margin-top:0}span.collapseBtn{cursor:pointer}span.collapseBtn img{vertical-align:middle}select.attachments{display:block;margin-top:10px;max-width:120px}#selected_prompt{margin-right:25px;vertical-align:middle}body#ttrssMain.claro #feedTree.dijitTree .dijitTreeNode .dijitTreeRowSelected{box-shadow:-1px 0 2px -1px rgba(0,0,0,0.1);border-right-color:#fff}body#ttrssMain #feedTree.dijitTree .dijitTreeContainer{max-width:100%}body#ttrssMain #feedTree.dijitTree .dijitTreeRow{overflow:hidden;text-overflow:ellipsis}body#ttrssMain #feedTree.dijitTree .dijitTreeNode .dijitTreeRow{padding:4px 0 4px;border-width:1px;color:#333}body#ttrssMain #feedTree.dijitTree img.tinyFeedIcon{position:relative;top:-2px}#filterDlg_feeds select{height:150px;width:410px}ul#filterDlg_Matches li div.dijitCheckBox,ul#filterDlg_Actions li div.dijitCheckBox{margin-right:5px}body#ttrssMain #feedTree{height:100%;overflow-x:hidden;text-rendering:optimizelegibility;font-family:"Segoe UI Web","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}body#ttrssMain #feedTree .counterNode.aux{background:#f0f0f0;color:#999;border-color:#f0f0f0}body#ttrssMain #feedTree .counterNode{font-weight:bold;display:inline-block;font-size:9px;text-align:center;border:1px solid #52a8ec;color:#fff;background:#52a8ec;border-radius:4px;vertical-align:middle;float:right;position:relative;line-height:14px;margin-right:8px;margin-top:2px;min-width:23px;height:14px}body#ttrssMain #feedTree .dijitTreeNode .loadingExpando{left:-3px;height:22px;position:relative;top:-3px}span.highlight{background-color:#ff0;color:#cc90cc}body#ttrssMain #headlines-frame .dijitCheckBox{border-width:0;opacity:.5}body#ttrssMain #headlines-frame .dijitCheckBoxHover,body#ttrssMain #headlines-frame .dijitCheckBoxChecked{opacity:1}body#ttrssMain #feedTree .dijitTreeRow img.dijitTreeExpandoLeaf{width:16px;height:16px;vertical-align:middle;position:relative}.dijitDropDownButton.attachments .dijitButtonText{font-size:12px}.dijitDropDownButton.attachments{display:inline-block}#editTagsDlg{overflow:visible}body#ttrssZoom{margin-left:auto;margin-right:auto;padding:20px;max-width:770px;background:#f5f5f5}body#ttrssZoom div.postHeader div.postFeedTitle{float:left;text-align:right;padding-left:0;font-size:11px}body#ttrssZoom div.postHeader a.postComments{text-align:right;padding-left:0;font-size:11px}body#ttrssZoom div.postHeader div.postDate{float:none;text-align:right;padding-left:0;color:#777;font-size:11px}body#ttrssZoom div.postHeader div.postTags{color:#777;font-size:11px}body#ttrssZoom div.postHeader div.postTitle{white-space:normal;font-size:16px}body#ttrssZoom div.postContent{font-size:15px;line-height:1.5}body#ttrssZoom div.postContent p{-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto}body#ttrssZoom div.postHeader{margin:10px;border-width:0 0 1px 0;border-style:solid;border-color:#eee;background:#fff}body#ttrssZoom div.postReply{border:1px solid #ddd;background:#fff;box-shadow:0 1px 1px -1px rgba(0,0,0,0.1);border-radius:6px}body#ttrssZoom div.footer{margin-top:1em;text-align:center}body#ttrssZoom div.postContent img{max-width:730px;height:auto}body#ttrssZoom div.postContent blockquote{margin:5px 0 5px 0;color:#555;padding-left:10px;border-width:0 0 0 4px;border-color:#ccc;border-style:solid}body#ttrssZoom div.postContent code{color:#090;font-family:monospace;font-size:12px}body#ttrssZoom div.postContent pre{margin:5px 0 5px 0;padding:10px;color:#555;font-family:monospace;font-size:12px;border-width:0;border-color:#ccc;border-style:solid;background:#f5f5f5;display:block;max-width:98%;overflow:auto}.cdm{margin-right:4px}.cdm .cdmHeader,.cdm .cdmFooter{display:table}.cdm .cdmHeader img,.cdm .cdmHeader input,.cdm .cdmFooter img{vertical-align:middle}.cdm .cdmHeader>div,.cdm .cdmFooter>div{white-space:nowrap}.cdm .cdmHeader>span,.cdm .cdmFooter>span.left{width:100%}.cdm .cdmHeader img,.cdm .cdmFooter img{margin:0 4px}.cdm .cdmHeader>*{display:table-cell;padding:5px}.cdm .cdmHeader span.updated{color:#555;font-weight:normal;font-size:11px;white-space:nowrap;vertical-align:middle}.cdm .cdmHeader input{margin-right:5px}.cdm .cdmHeader div.updPic{width:25px;display:inline-block;text-align:center}.cdm .cdmHeader div.updPic img{vertical-align:middle}.cdm .cdmHeader input{margin-left:4px;margin-right:4px}.cdm .cdmFooter{height:30px;padding-left:5px;font-weight:normal;color:#555;clear:both}.cdm .cdmFooter>*{display:table-cell;vertical-align:middle}.cdm .cdmIntermediate{margin:10px}.cdm .cdmContentInner{margin:10px;line-height:1.5;font-size:16px}.cdm .cdmContentInner h1{font-size:16px}.cdm .cdmContentInner h2,.cdm .cdmContentInner h3,.cdm .cdmContentInner h4{font-size:15px}.cdm .cdmIntermediate img,.cdm .cdmIntermediate video,.cdm .cdmContentInner img,.cdm .cdmContentInner video{border-width:0;max-width:98%;height:auto}.cdm.expanded{margin-top:4px;margin-bottom:4px}.cdm.expanded .cdmFooter{border:0 solid #ddd;border-bottom-width:1px}.cdm.expanded>hr{margin-top:0;margin-bottom:0}.cdm.expandable{background-color:#f0f0f0;border:0 solid #ddd;border-bottom-width:1px}.cdm.expandable>hr{display:none}.cdm.expandable div.cdmHeader span.titleWrap{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;max-width:500px}.cdm.expandable.Unread{background:#fff}.cdm.expandable.Selected{background:#f9fbff}.cdm.expandable.active{background:#fff ! important}div.cdm.expandable.active div.cdmHeader span.titleWrap{white-space:normal}div.cdm.expandable div.cdmHeader a.title{font-weight:600;color:#555;font-size:14px;-webkit-transition:color .2s;transition:color .2s;text-rendering:optimizelegibility;font-family:"Segoe WP Semibold","Segoe UI Semibold","Segoe UI Web Semibold","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}div.cdm.expandable.Unread div.cdmHeader a.title{color:#000}div.cdm.expandable.active div.cdmHeader a.title{color:#08c;font-size:16px;font-weight:600;text-rendering:optimizelegibility;font-family:"Segoe WP Semibold","Segoe UI Semibold","Segoe UI Web Semibold","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}div.cdm.expanded div.cdmHeader{background:transparent ! important}div.cdm.expanded div.cdmHeader a.title{font-size:16px;color:#999;font-weight:600;-webkit-transition:color .2s;transition:color .2s;text-rendering:optimizelegibility;font-family:"Segoe WP Semibold","Segoe UI Semibold","Segoe UI Web Semibold","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}div.cdm.expanded.active{background:#fff}div.cdm.expanded.active div.cdmHeader a.title{color:#08c}div.cdm.expanded.Unread div.cdmHeader a.title{color:#000}div.cdm.expanded div.cdmContent{color:#555}div.cdm.expanded.Unread div.cdmContent{color:#000}div.cdm.active div.cdmContent{color:#000}span.cdmExcerpt{white-space:nowrap;font-size:11px;color:#999;font-weight:normal;cursor:pointer}div.cdmContent div.postEnclosures{margin-top:1em;color:#555}div.cdmFeedTitle{border:0 solid #08c;border-bottom-width:1px;padding:5px 3px 5px 5px}div.cdmFeedTitle a.title{color:#555;font-weight:bold}div.cdmFeedTitle a{color:#555}div.cdmFeedTitle a:hover{color:#08c}div.cdmHeader span.hlFeed{float:right;font-weight:normal;font-style:italic}div.cdmHeader div.hlFeed,div.cdmHeader div.hlFeed a{vertical-align:middle;color:#555;font-weight:normal;font-style:italic;font-size:11px}div.cdm .hlFeed a{border-radius:4px;display:inline-block;padding:1px 4px 1px 4px}div.cdmContentInner p{-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto}div.cdmContentInner iframe{min-width:50%;max-width:98%}div.cdmHeader span.author{white-space:nowrap;color:#555;font-size:11px;font-weight:normal}div#floatingTitle{position:absolute;z-index:5;top:0;right:0;left:0;border:0 solid #ddd;border-bottom-width:1px;background:#fff;color:#555;box-shadow:0 1px 1px -1px rgba(0,0,0,0.1)}div#floatingTitle>*{display:table-cell;white-space:nowrap;vertical-align:middle;padding:9px 5px}div#floatingTitle img{margin-right:4px;margin-left:4px}div#floatingTitle span.author{color:#555;font-size:11px;font-weight:normal}div#floatingTitle a.title{font-size:16px;color:#999;-webkit-transition:color .2s;transition:color .2s;font-weight:600;text-rendering:optimizelegibility;font-family:"Segoe WP Semibold","Segoe UI Semibold","Segoe UI Web Semibold","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}div#floatingTitle img.anchor{margin-left:0}div#floatingTitle div.hlFeed{padding-right:10px;color:#555;font-weight:normal;font-style:italic;font-size:11px;white-space:nowrap}div#floatingTitle div.hlFeed a{border-radius:4px;display:inline-block;padding:1px 4px 1px 4px}div#floatingTitle span.updated{padding-right:10px;white-space:nowrap;color:#555;font-size:11px}div#floatingTitle div.hlFeed a{color:#555}div#floatingTitle span.titleWrap{width:100%;white-space:normal}div#floatingTitle .dijit,div#floatingTitle img.hlScorePic{display:none}div#floatingTitle.Unread a.title{color:#000}.cdm.high .cdmHeader a.title.high,.cdm.high .cdmHeader .cdmExcerpt,.cdm.high .cdmHeader span.author{color:#0a0}.cdm.Unread.high .cdmHeader a.title.high,.cdm.Unread.high .cdmHeader .cdmExcerpt,.cdm.Unread.high .cdmHeader span.author{color:#0d0}.cdm .cdmHeader a.title.low,.cdm.low .cdmHeader .cdmExcerpt,.cdm.Unread .cdmHeader a.title.low,.cdm.Unread.low .cdmHeader .cdmExcerpt,.cdm.low .cdmHeader span.author{color:#909090;text-decoration:line-through}.cdmFeedTitle>*{display:table-cell;vertical-align:middle}.cdmFeedTitle a.title{width:100%}.cdmFeedTitle a.catchup{text-align:right;color:#555;padding-right:10px;font-size:11px;white-space:nowrap}.cdmFeedTitle a.catchup:hover{color:#08c}body#ttrssPrefs{background-color:#f5f5f5}body#ttrssPrefs #footer,body#ttrssPrefs #header{background-color:#f5f5f5;padding-left:8px;padding-right:8px}body#ttrssPrefs #header a:hover{color:#000}body#ttrssPrefs #header img{vertical-align:middle;cursor:pointer}body#ttrssPrefs div#pref-tabs .dijitContentPane{font-size:13px}body#ttrssPrefs div#pref-tabs{box-shadow:0 1px 1px -1px rgba(0,0,0,0.1);margin:0 5px 0 5px}body#ttrssPrefs div#pref-tabs .dijitContentPane h3{font-size:14px}body#ttrssPrefs #pref-filter-wrap,body#ttrssPrefs #pref-filter-header,body#ttrssPrefs #pref-filter-content,body#ttrssPrefs #pref-label-wrap,body#ttrssPrefs #pref-label-header,body#ttrssPrefs #pref-label-content,body#ttrssPrefs #pref-user-wrap,body#ttrssPrefs #pref-user-header,body#ttrssPrefs #pref-user-content,body#ttrssPrefs #pref-instance-wrap,body#ttrssPrefs #pref-instance-header,body#ttrssPrefs #pref-instance-content{margin:0;padding:0;border-width:0}body#ttrssPrefs #userConfigTab,body#ttrssPrefs #labelConfigTab,body#ttrssPrefs #filterConfigTab,body#ttrssPrefs #pref-feeds-feeds,body#ttrssPrefs #instanceConfigTab{padding:0}body#ttrssPrefs table.prefPrefsList h3{margin-top:.5em;margin-bottom:0}body#ttrssPrefs tr.title td{border-width:0 0 1px 0;border-color:#ecf4ff;border-style:solid;color:#08c}body#ttrssPrefs div.prefProfileHolder,body#ttrssPrefs div.prefFeedOPMLHolder,body#ttrssPrefs div.inactiveFeedHolder{height:300px;overflow:auto;border-width:0 1px 1px 1px;border-color:#ddd;border-style:solid;margin:0 0 5px 0;background-color:#fff}body#ttrssPrefs div.filterTestHolder,body#ttrssPrefs div.prefFeedOPMLHolder{border-width:1px}body#ttrssPrefs ul.userFeedList{height:200px;overflow:auto;list-style-type:none;border:1px solid #ddd;margin:0 0 5px 0;padding:5px}body#ttrssPrefs div#feedlistLoading,body#ttrssPrefs div#filterlistLoading,body#ttrssPrefs div#labellistLoading{text-align:center;padding:5px;color:#555}body#ttrssPrefs div#feedlistLoading img,body#ttrssPrefs div#filterlistLoading img,body#ttrssPrefs div#labellistLoading{margin-right:5px}body#ttrssPrefs #errorButton{color:#f00}body#ttrssPrefs table.prefPluginsList td label,body#ttrssPrefs table.prefUserList td{cursor:pointer}body#ttrssPrefs table.prefPluginsList label{white-space:nowrap}body#ttrssPrefs table.prefPluginsList label img{vertical-align:middle}body#ttrssPrefs table.prefErrorLog tr.errrow td{font-size:10px}body#ttrssPrefs table.prefErrorLog tr.errrow td.errno{font-style:italic;font-weight:bold;white-space:nowrap}body#ttrssPrefs table.prefErrorLog td.filename,body#ttrssPrefs table.prefErrorLog td.login,body#ttrssPrefs table.prefErrorLog td.timestamp{color:#555}body#ttrssPrefs hr{border-color:#ecf4ff;max-width:100%}.claro .dijitTreeRow .dijitCheckBox{position:relative;top:-2px}.claro .dijitTreeLabel{outline:0}.claro .dijitTree .feedParam{color:#555;float:right;margin-right:1em}.claro .dijitTree .filterRules{display:block;color:#ccc;font-size:10px;margin-left:100px}.claro .dijitTree .filterRules span{display:block;color:#008000}.claro #filterDlg_Matches span.filterRule{color:#008000}.claro .dijitTree .filterRules span.inverse,.claro #filterDlg_Matches span.filterRule.inverse{color:#f00}.claro .dijitTree .labelParam{float:right;margin-right:1em}.claro .dijitTree .dijitTreeLabel.filterDisabled,.claro .dijitTree .labelParam.filterDisabled{color:#555;text-decoration:line-through}.claro .dijitTreeRow.Error{color:#f00}.claro .dijitTreeRow.Hidden{display:none}.claro .dijitTreeNode .loadingNode{margin-left:3px;height:9px}.claro .dijitFolderClosed,.claro .dijitFolderOpened{display:none}.claro .dijitTreeNode .dijitCheckBox{margin-left:4px}.claro .dijitTreeIsRoot>.dijitTreeRow>.dijitTreeExpando{margin-left:5px}.claro .dijitTree .dijitTreeExpando{margin-top:0;opacity:.6}.claro .dijitTree .dijitTreeNode{padding:0;border-width:0}.claro .dijitTree .dijitTreeRowSelected{background:#fff}.claro .dijitTree .dijitTreeRowHover{background:#f0f0f0;border-color:#ddd}.claro .dijitTree .dijitTreeRowSelected{background:#fff;border-color:#ddd}.claro .dijitTreeRowSelected .dijitTreeLabel{text-shadow:1px 1px 2px #fff}.claro .dijitTreeRow .dijitTreeExpando{background-image:url("../images/treeExpandImages.png");position:relative;top:-1px}.claro .dijitTreeRow .dijitTreeExpandoLeaf{background:none}.claro .dijitToolbar{background:#f5f5f5;border-color:#ddd}.claro .dijitDialog .dijitToolbar{border:1px solid #ddd}.claro .dijitDialog h2{margin-top:0;margin-bottom:4px;border-width:0}.claro .dijitMenu .dijitMenuItem .dijitMenuItemLabel{font-size:13px;padding-top:3px;padding-bottom:3px}.claro .dijitCheckBox{background-image:url("../images/untick.png");background-color:transparent;width:15px;height:15px;margin:1px;opacity:.7;background-position:center center;transition:opacity .25s;-webkit-transition:opacity .25s;padding:1px}.claro .dijitCheckBox:hover{opacity:1}.claro .dijitCheckBox.dijitCheckBoxDisabled:hover{opacity:.7}.claro .dijitCheckBox.dijitCheckBoxChecked{border-color:#69c671;background-image:url("../images/tick.png");opacity:1}.claro .dijitButton.danger .dijitButtonText{color:#fff}.claro .dijitButton.danger{text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(to bottom, #ee5f5b, #bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.claro .dijitButton.dijitButtonDisabled.danger,.claro .dijitButton.dijitButtonActive.danger,.claro .dijitButton.dijitButtonHover.danger,.claro .dijitButton.dijitFocused.danger{color:#fff;background-color:#bd362f;*background-color:#a9302a}.claro .dijitButton.dijitButtonActive.danger{background-color:#942a25 \9}.claro .dijitDropDownButton{margin:0}.claro .dijitDropDownButton .dijitButtonNode{padding:0}.claro .dijitButton.dijitButtonActive.danger{color:rgba(255,255,255,0.75)}.claro .dijitButton .dijitButtonNode,.claro .dijitDropDownButton .dijitButtonNode,.claro .dijitComboButton .dijitButtonNode,.claro .dijitToolbar .dijitDropDownButton .dijitButtonNode,.claro .dijitToolbar .dijitComboButton,.claro .dijitToolbar .dijitSelect.dijitDownArrowButton .dijitButtonNode,.claro .dijitToolbar .dijitComboButton .dijitButtonNode{background:none;border-color:transparent;box-shadow:none}.claro button,.claro input[type="submit"]{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px}.claro button,.claro input[type="submit"],.claro .dijitButton,.claro .dijitDropDownButton .dijitDownArrowButton,.claro .dijitComboButton{display:inline-block;padding:4px 12px;margin-bottom:0;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #fff, #e6e6e6);background-image:-webkit-linear-gradient(top, #fff, #e6e6e6);background-image:linear-gradient(to bottom, #fff, #e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#b3b3b3;-webkit-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.claro button:hover,.claro button:focus,.claro button:active,.claro input[type="submit"]:hover,.claro input[type="submit"]:focus,.claro input[type="submit"]:active,.claro .dijitButton:hover,.claro .dijitButton:focus,.claro .dijitButton:active,.claro .dijitDropDownButton .dijitDownArrowButton:hover,.claro .dijitDropDownButton .dijitDownArrowButton:focus,.claro .dijitDropDownButton .dijitDownArrowButton:active,.claro .dijitComboButton:hover,.claro .dijitComboButton:focus,.claro .dijitComboButton:active,.claro .dijitButton.dijitButtonDisabled{color:#333;background-color:#e6e6e6}.claro button:active,.claro input[type="submit"]:active,.claro .dijitButton:active,.claro .dijitDropDownButton .dijitDownArrowButton:active,.claro .dijitComboButton:active{background-color:#cccccc \9}.claro .dijitToolbar .dijitButton,.claro .dijitToolbar .dijitButton.dijitHover,.claro .dijitToolbar .dijitComboButton,.claro .dijitToolbar .dijitDropDownButton .dijitDownArrowButton,.claro .dijitToolbar .dijitComboButton.dijitHover{background:none;border-color:transparent;box-shadow:none;padding:0;margin:0;line-height:auto;text-shadow:none}.claro .dijitToolbar .dijitDropDownButton .dijitButtonText,.claro .dijitToolbar .dijitDownArrowButton .dijitButtonText,.claro .dijitToolbar .dijitComboButton .dijitButtonText{padding:0}.claro .dijitToolbar .dijitDropDownButton .dijitButtonNode{border-radius:4px}.claro .dijitToolbar .dijitButton.dijitHover,.claro .dijitToolbar .dijitDropDownButton.dijitHover .dijitButtonNode,.claro .dijitToolbar .dijitComboButton.dijitHover{border-color:#ccc}.claro .dijitToolbar .dijitButton.dijitHover .dijitButtonNode,.claro .dijitToolbar .dijitButton.dijitButtonActive .dijitButtonNode{background:none}.claro .dijitToolbar .dijitButton .dijitButtonContents,.claro .dijitToolbar .dijitDropDownButton .dijitButtonContents,.claro .dijitToolbar .dijitComboButton .dijitButtonContents{font-size:13px}.claro button:hover,.claro button:focus,.claro input[type="submit"]:hover,.claro input[type="submit"]:focus,.claro .dijitButton:hover,.claro .dijitDropDownButton .dijitDownArrowButton:hover,.claro .dijitToolbar .dijitButton:hover .dijitButtonNode,.claro .dijitToolbar .dijitButton.dijitHover .dijitButtonNode,.claro .dijitButton:focus,.claro .dijitComboButton:hover,.claro .dijitComboButton:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;transition:background-position .1s linear}.claro button:focus,.claro input[type="submit"]:focus,.claro .dijitButton:focus,.claro .dijitDropDownButton .dijitDownArrowButton:focus,.claro .dijitComboButton:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.claro button:active,.claro input[type="submit"]:active,.claro .dijitButton:active,.claro .dijitComboButton:active,.claro .dijitToolbar .dijitDropDownButton.dijitOpened,.claro .dijitToolbar .dijitComboButton.dijitOpened,.claro .dijitToolbar .dijitButton.dijitButtonActive .dijitButtonNode{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.claro input[type="submit"][disabled],.claro button[disabled],.claro .dijitButton[disabled],.claro .dijitDropDownButton .dijitDownArrowButton[disabled],.claro .dijitButton.dijitButtonDisabled,.claro .dijitComboButton.dijitButtonDisabled{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.claro .dijitButton .dijitButtonContents,.claro .dijitDropDownButton .dijitButtonContents,.claro .dijitComboButton .dijitButtonContents{font-size:14px;font-weight:normal;line-height:20px}.claro .dijitButton.small .dijitButtonText{font-size:11px}.claro .dijitMenu{border-color:rgba(82,168,236,0.8)}.claro .dijitMenu .dijitMenuItemSelected,.claro .dijitMenu .dijitMenuItemSelected td{background:#52a8ec;color:#fff;border-color:rgba(82,168,236,0.8)}.claro .dijitButton .dijitButtonNode,.claro .dijitComboButton .dijitButtonNode{padding:0}.claro .dijitAccordionTitle.dijitAccordionTitleHover,.claro .dijitAccordionTitle.dijitAccordionTitleFocused{background:#fff;transition:background .25s}.claro .dijitAccordionTitle{background:#f0f0f0;transition:background .25s}.claro .dijitAccordionInnerContainer.dijitAccordionInnerContainerSelected,.claro .dijitAccordionTitle.dijitAccordionTitleSelected{background:#08c;transition:background .25s}.claro .dijitAccordionTitle.dijitAccordionTitleSelected .dijitAccordionText{color:#fff}.claro .dijitAccordionInnerContainer.dijitAccordionInnerContainerSelected{border-color:#08c}.claro .dijitAccordionContainer .dijitAccordionChildWrapper{border-color:#ddd}.claro .dijitTabInner.dijitTab{background:#f0f0f0}.claro .dijitTabContent{background:#eee}.claro .dijitTabContent.dijitTabChecked,.claro .dijitTabContent.dijitTabHover,.claro .dijitTabContent.dijitFocused{background:#fff}.claro .dijitTabPaneWrapper,.claro .dijitTabContainerTop-tabs,.claro .dijitTab,.claro .dijitAccordionInnerContainer{border-color:#ddd}.claro .dijitComboBox .dijitArrowButton,.claro .dijitSelect .dijitArrowButton{background:transparent;border-color:transparent}.claro .dijitSelect .dijitArrowButton .dijitArrowButtonInner{margin-right:5px;float:right}.claro select,.claro .dijitDownArrowButton.dijitSelect{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;margin-bottom:4px}.claro .dijitSelect .dijitButtonContents{display:inline-block;height:20px;padding:4px 6px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;border-width:0}.claro select,.claro textarea,.claro .input.input-text,.claro .dijitTextBox{display:inline-block;height:20px;padding:4px 6px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;margin-bottom:4px}.claro textarea{height:auto}.claro select,.claro .input.input-text{height:30px}.claro textarea,.claro select,.claro .input.input-text,.claro .dijitTextBox,.claro .dijitSelect{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}.claro select:focus,.claro .input.input-text:focus,.claro .dijitTextBox.dijitFocused,.claro .dijitSelect.dijitFocused{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.claro .dijitError .dijitValidationContainer{padding:0;width:13px;border-width:1px;display:none}.claro .dijitSelect.dijitSelectDisabled{background-color:#eee}.claro .dijitSelect.dijitSelectDisabled .dijitSelectLabel{cursor:not-allowed}.claro .dijitTextBox.dijitTextBoxDisabled,.claro .dijitTextBox.dijitTextBoxDisabled .dijitInputInner,.claro .dijitTextBox.dijitReadOnly,.claro .dijitTextBox.dijitReadOnly .dijitInputInner{cursor:not-allowed;background-color:#eee}.claro .dijitToolbar .dijitDownArrowButton.dijitSelect{border-color:rgba(0,0,0,0.1)}.claro .dijitToolbar .dijitDownArrowButton.dijitSelect .dijitButtonContents{padding:2px 2px 0 4px}.claro .dijitToolbar .dijitDownArrowButton.dijitSelect{margin:0}.claro .dijitToolbar .dijitTextBox{padding:0;margin-bottom:0;border-radius:0}.claro .dijitDialog{border-radius:6px}.claro .dijitDialog .dijitDialogCloseIcon{margin-top:5px}.claro .dijitDialog .dijitDialogTitleBar{background:#fff;padding:8px;font-weight:600;color:#555;font-size:16px;text-rendering:optimizelegibility;font-family:"Segoe WP Semibold","Segoe UI Semibold","Segoe UI Web Semibold","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}.claro .dijitDialog .dijitDialogPaneContent{border-color:#ddd;padding:10px}.claro .dijitProgressBar.dijitProgressBarEmpty{background:#ddd;border-color:#08c}.claro .dijitProgressBar.dijitProgressBarEmpty .dijitProgressBarFull .dijitProgressBarTile{background:#52a8ec}.claro .dijitProgressBar .dijitProgressBarLabel{color:#fff}
\ No newline at end of file +body.ttrss_main,body.ttrss_prefs,#main{position:absolute;width:100%;height:100%;border:0;padding:0;margin:0}body.ttrss_main{background:#fff;color:#000;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;overflow:hidden}body.ttrss_main :focus{outline:none}body.ttrss_main div.postReply{padding:0}body.ttrss_main div.postReply div.postHeader{padding:5px;margin-right:4px;color:#909090;border:0 solid #ddd;border-bottom-width:1px}body.ttrss_main div.postReply div.postHeader div.postDate{text-align:right;color:#909090;float:right}body.ttrss_main div.postReply div.postHeader div{padding-bottom:3px}body.ttrss_main div.postReply div.postHeader span.author{color:#555;font-size:11px;font-weight:normal}body.ttrss_main div.postReply div.postTitle{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600;text-rendering:optimizelegibility;font-family:"Segoe WP Semibold","Segoe UI Semibold","Segoe UI Web Semibold","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}body.ttrss_main div.postReply div.postDate{padding-left:10px}body.ttrss_main div.postReply div.postContent{padding:10px;font-size:16px}body.ttrss_main div.postReply div.postContent img,body.ttrss_main div.postReply div.postContent video{border-width:0;max-width:98%;height:auto}body.ttrss_main div.postReply div.postContent h1{font-size:16px}body.ttrss_main div.postReply div.postContent h2,body.ttrss_main div.postReply div.postContent h3,body.ttrss_main div.postReply div.postContent h4{font-size:15px}body.ttrss_main div.postReply div.postContent p{hyphens:auto}body.ttrss_main div.postReply div.postContent iframe{min-width:50%;max-width:98%}body.ttrss_main div.postReply div.postEnclosures{color:#555}body.ttrss_main div.postReply img.tagsPic{width:16px;height:16px;margin-left:4px;vertical-align:middle}body.ttrss_main div.postReply span.author{font-size:12px}body.ttrss_main div.articleNote{background-color:#fff7d5;padding:5px;margin:5px;border:1px solid #e7d796;color:#9a8c59}body.ttrss_main div.articleNote div.noteEdit{float:right;cursor:pointer}body.ttrss_main h1{font-size:18px;font-weight:600;text-rendering:optimizelegibility;font-family:"Segoe UI Web","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}body.ttrss_main h2{font-size:16px;font-weight:600;border:0 solid #d5ebf6;border-bottom-width:1px;text-rendering:optimizelegibility;font-family:"Segoe UI Web","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}body.ttrss_main h3{font-size:13px;border:0 solid #d5ebf6;border-bottom-width:1px;font-weight:600;text-rendering:optimizelegibility;font-family:"Segoe UI Web","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}body.ttrss_main h4{font-size:14px;font-weight:600;text-rendering:optimizelegibility;font-family:"Segoe UI Web","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}body.ttrss_main hr{border:0 solid #ccc;border-bottom-width:1px}body.ttrss_main a{color:#257aa7;text-decoration:none}body.ttrss_main a:hover{color:#133d54;text-decoration:underline}body.ttrss_main #notify.visible{transform:translate(0, -35px)}body.ttrss_main #notify{bottom:-35px;right:0;height:20px;left:0;border-width:1px 0 0 0;border-style:solid;position:fixed;font-size:12px;z-index:99;padding:5px;box-shadow:0 -2px 2px rgba(0,0,0,0.1);transition:all .5s ease-in-out}body.ttrss_main #notify img{vertical-align:middle;max-height:14px}body.ttrss_main #notify span.msg{width:100%}body.ttrss_main #notify img.close{cursor:pointer}body.ttrss_main #notify span{display:table-cell;vertical-align:middle;padding:2px}body.ttrss_main .notify{border-color:#d7c47a;background-color:#fff7d5}body.ttrss_main .notify.notify_progress{border-color:#d7c47a;background-color:#fff7d5}body.ttrss_main .notify.notify_info{border-color:#257aa7;background-color:#d5ebf6}body.ttrss_main .notify.notify_error{background-color:#fcc;border-color:#f00}body.ttrss_main .hl{border:0 solid #ddd;border-bottom-width:1px;padding:1px}body.ttrss_main .hl div.hlTitle{display:table-cell;cursor:pointer;width:100%;vertical-align:middle;overflow:hidden;white-space:nowrap;max-width:500px;text-overflow:ellipsis;padding:4px 6px}body.ttrss_main .hl div.hlLeft{display:table-cell;vertical-align:middle;white-space:nowrap}body.ttrss_main .hl div.hlRight{display:table-cell;white-space:nowrap;text-align:right;vertical-align:middle}body.ttrss_main .hl div.hlRight img{max-width:16px;max-height:16px}body.ttrss_main .hl span.hlFeed{display:table-cell;vertical-align:middle;text-align:right}body.ttrss_main .hl span.hlFeed a{border-radius:4px;display:inline-block;padding:1px 4px 1px 4px;font-size:11px;font-style:italic;font-weight:normal;color:#555;white-space:nowrap}body.ttrss_main .hl span.hlFeed a:hover{color:#257aa7}body.ttrss_main .hl span.hlUpdated{color:#555;display:table-cell;vertical-align:middle;text-align:right;font-size:11px;white-space:nowrap;padding-left:10px}body.ttrss_main .hl span.hlUpdated div{display:inline-block}body.ttrss_main .hl div.hlLeft{padding-left:8px}body.ttrss_main .hl div.hlLeft input{margin-left:4px;margin-right:4px}body.ttrss_main .hl div.hlLeft img,body.ttrss_main .hl div.hlRight img{margin:0 4px}body.ttrss_main .hl div.hlLeft img{width:16px;height:16px}body.ttrss_main .hl div.hlTitle a{font-weight:600;text-rendering:optimizelegibility;font-family:"Segoe UI Web","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif;color:#777}body.ttrss_main .hl a.title.high,body.ttrss_main .hl span.hlContent.high .contentPreview{color:#0a0}body.ttrss_main .hl.Unread a.title.high,body.ttrss_main .hl.Unread span.hlContent.high .contentPreview{color:#0d0}body.ttrss_main .hl a.title.low,body.ttrss_main span.hlContent.low .contentPreview,body.ttrss_main .hl.Unread a.title.low,body.ttrss_main .hl.Unread span.hlContent.low .contentPreview{color:#909090;text-decoration:line-through}body.ttrss_main .hl.Unread div.hlTitle a{color:#000}body.ttrss_main .hl.active{background:#d5ebf6 ! important}body.ttrss_main .hl.active div.hlTitle a{color:#257aa7}body.ttrss_main .hl.Selected{background:#f9fbff}body.ttrss_main .hl.Grayed{color:#909090}body.ttrss_main div.filterTestHolder{height:300px;overflow:auto;border-color:#ddd;border-style:solid;margin:0 0 5px 0;border-width:1px}body.ttrss_main #content-insert blockquote,body.ttrss_main #headlines-frame blockquote,body.ttrss_main .dijitContentPane blockquote{margin:5px 0 5px 0;color:#555;padding-left:10px;border:0 solid #ccc;border-left-width:4px}body.ttrss_main #content-insert code,body.ttrss_main #headlines-frame code,body.ttrss_main .dijitContentPane code{color:#090;font-family:monospace;font-size:12px}body.ttrss_main #content-insert pre,body.ttrss_main #headlines-frame pre,body.ttrss_main .dijitContentPane pre{margin:5px 0 5px 0;padding:10px;color:#555;font-family:monospace;font-size:12px;border:0 solid #ccc;background:#f5f5f5;display:block;max-width:98%;overflow:auto}body.ttrss_main .alert{padding:8px 35px 8px 14px;margin-bottom:10px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;border-radius:4px}body.ttrss_main .alert,body.ttrss_main .alert h4{color:#c09853}body.ttrss_main .alert h4{margin:0}body.ttrss_main .alert .close{position:relative;top:-2px;right:-21px;line-height:20px;cursor:pointer}body.ttrss_main .alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}body.ttrss_main .alert-success h4{color:#468847}body.ttrss_main .alert-danger,body.ttrss_main .alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}body.ttrss_main .alert-danger h4,body.ttrss_main .alert-error h4{color:#b94a48}body.ttrss_main .alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}body.ttrss_main .alert-info h4{color:#3a87ad}body.ttrss_main ul.nomarks{list-style-type:none;margin:0;padding:10px}body.ttrss_main div.prefHelp{color:#555;padding:5px}body.ttrss_main .insensitive{color:#555}body.ttrss_main .small{font-size:11px}body.ttrss_main #main-toolbar>*{white-space:nowrap;display:table-cell;color:#999;overflow:hidden}body.ttrss_main #main-toolbar>*,body.ttrss_main #main-toolbar table *,body.ttrss_main #main-toolbar .actionChooser *{text-rendering:optimizelegibility;font-family:"Segoe UI Web","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px}body.ttrss_main #main-toolbar #headlines-toolbar{padding-right:4px;width:100%}body.ttrss_main #main-toolbar #headlines-toolbar span.holder{display:table;width:100%}body.ttrss_main #main-toolbar #headlines-toolbar span.holder>*{display:table-cell}body.ttrss_main #main-toolbar #headlines-toolbar .main{text-align:right}body.ttrss_main #main-toolbar #headlines-toolbar .main,body.ttrss_main #main-toolbar #headlines-toolbar .r{line-height:24px}body.ttrss_main #main-toolbar #headlines-toolbar span.r img{margin-right:4px;position:relative;top:3px}body.ttrss_main #main-toolbar #headlines-toolbar span.r .error a{color:#f00}body.ttrss_main #main-toolbar #selected_prompt{font-style:italic;text-align:right;margin-right:4px}@media (max-width:992px){body.ttrss_main #main-toolbar #selected_prompt{display:none}}body.ttrss_main span.contentPreview{color:#999;font-weight:normal;font-size:12px;padding-left:4px}body.ttrss_main span.hlLabelRef{background-color:#fff7d5;font-size:8px;color:#063064;font-weight:normal;margin-left:2px;padding:1px 4px 1px 4px;display:inline-block;vertical-align:middle;white-space:nowrap;border-radius:4px}body.ttrss_main img.markedPic,body.ttrss_main img.pubPic{cursor:pointer;vertical-align:middle;opacity:.5;transition:opacity .25s}body.ttrss_main img.markedPic:hover,body.ttrss_main img.pubPic:hover{opacity:1}body.ttrss_main img[src*='pub_set.png'],body.ttrss_main img[src*='mark_set.png']{opacity:1}body.ttrss_main div.tagCloudContainer{border:1px solid #ddd;margin:5px 0 5px 0;padding:5px;text-align:center}body.ttrss_main div.errorExplained{border:1px solid #ddd;margin:5px 0 5px 0;padding:5px}body.ttrss_main ul.feedErrorsList{max-height:300px;overflow:auto;list-style-type:none;border:1px solid #ddd;margin:0 0 5px 0;padding:5px}body.ttrss_main ul.feedErrorsList em{color:#555}body.ttrss_main ul.browseFeedList{height:300px;overflow:auto;border-width:0 1px 1px 1px;border-color:#ddd;border-style:solid;margin:0 0 5px 0;background-color:#fff;list-style-type:none;padding:0}body.ttrss_main ul.browseFeedList li{margin:0;padding:2px 4px 2px 4px}body.ttrss_main .browseFeedList span.subscribers{color:#808080}body.ttrss_main ul.compact{list-style-type:none;margin:0;padding:0}body.ttrss_main ul.compact li{margin:0;padding:0}body.ttrss_main .noborder{border-width:0}body.ttrss_main #overlay{background:#fff;left:0;top:0;height:100%;width:100%;z-index:100;position:absolute}body.ttrss_main #overlay_inner{font-weight:bold;margin:1em}body.ttrss_main form{margin:0;padding:0}body.ttrss_main div.loadingPrompt{padding:1em;text-align:center;font-weight:bold}body.ttrss_main div.whiteBox{margin-left:1px;text-align:center;padding:1em 1em 0 1em;font-size:11px;border:0 solid #ddd;border-bottom-width:1px}body.ttrss_main div.autocomplete{position:absolute;width:250px;background-color:#fff;border:1px solid #789;margin:0;padding:0}body.ttrss_main div.autocomplete ul{list-style-type:none;margin:0;padding:0}body.ttrss_main div.autocomplete ul li.selected{background-color:#fff7d5}body.ttrss_main div.autocomplete ul li{list-style-type:none;display:block;margin:0;padding:2px;height:32px;cursor:pointer}body.ttrss_main div#headlines-frame.wide .hlTitle{max-width:none;overflow:visible;white-space:normal}body.ttrss_main div#headlines-frame.wide .hl .hlFeed{display:none}body.ttrss_main img.hlScorePic{vertical-align:middle;width:16px;height:16px}body.ttrss_main div.dlgSec{font-size:12px;color:#555;font-weight:bold;clear:both;height:20px}body.ttrss_main div.dlgSecCont{position:relative;left:150px;top:-20px;float:left;font-size:12px;font-weight:normal}body.ttrss_main div.dlgSecCont>*{position:relative;top:-2px}body.ttrss_main div.dlgSecCont hr,body.ttrss_main div.dlgSecSimple hr{height:0;line-height:0;border:0 solid transparent;margin:2px}body.ttrss_main div.dlgButtons{text-align:right;clear:both}body.ttrss_main span.labelColorIndicator{height:16px;width:16px;border-radius:4px;line-height:14px;vertical-align:middle;font-size:9px;display:inline-block;border:1px solid #ccc;background-color:#fff7d5;color:#063064;text-align:center}body.ttrss_main div#cmdline{position:absolute;left:5px;bottom:5px;font-size:11px;color:#555;font-weight:bold;background-color:#fff;border:1px solid #257aa7;padding:3px 5px 3px 5px;z-index:5}body.ttrss_main #feed_browser_spinner{vertical-align:middle;height:18px;width:18px}body.ttrss_main div.fatalError{margin-bottom:10px}body.ttrss_main div.fatalError button{margin-top:5px}body.ttrss_main div.fatalError textarea{width:565px;height:200px}body.ttrss_main #header-wrap{border-width:0;margin:0;padding:0}body.ttrss_main #content-wrap{padding:0;border-width:0;margin:0}body.ttrss_main #feeds-holder{padding:0;border:0 solid #ddd;overflow:hidden;background:#f5f5f5;box-shadow:inset -1px 0 2px -1px rgba(0,0,0,0.1);-webkit-overflow-scrolling:touch}body.ttrss_main #feeds-holder #feedTree .dijitTreeRow .dijitTreeLabel.Unread{font-weight:bold}body.ttrss_main #feeds-holder #feedTree .dijitTreeRow.Error .dijitTreeLabel{color:#f00}body.ttrss_main #feeds-holder #feedTree.dijitTree .dijitTreeNode .dijitTreeRowSelected{box-shadow:-1px 0 2px -1px rgba(0,0,0,0.1);border-right-color:#fff}body.ttrss_main #feeds-holder #feedTree.dijitTree .dijitTreeContainer{max-width:100%}body.ttrss_main #feeds-holder #feedTree.dijitTree .dijitTreeRow{overflow:hidden;text-overflow:ellipsis}body.ttrss_main #feeds-holder #feedTree.dijitTree .dijitTreeNode .dijitTreeRow{padding:4px 0 4px;border-width:1px;color:#333}body.ttrss_main #feeds-holder #feedTree.dijitTree img.tinyFeedIcon{position:relative;top:-2px}body.ttrss_main #feeds-holder #feedTree{height:100%;overflow-x:hidden;text-rendering:optimizelegibility;font-family:"Segoe UI Web","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}body.ttrss_main #feeds-holder #feedTree .counterNode.aux{background:#f0f0f0;color:#999;border-color:#f0f0f0}body.ttrss_main #feeds-holder #feedTree .counterNode{font-weight:bold;display:inline-block;font-size:9px;text-align:center;border:1px solid #2a89bc;color:#fff;background:#2a89bc;border-radius:4px;vertical-align:middle;float:right;position:relative;line-height:14px;margin-right:8px;margin-top:2px;min-width:23px;height:14px}body.ttrss_main #feeds-holder #feedTree .dijitTreeNode .loadingExpando{left:-3px;height:22px;position:relative;top:-3px}body.ttrss_main #headlines-wrap-inner{padding:0;margin:0;border-width:0}body.ttrss_main #headlines-frame{padding:0;border:0 #ddd;margin-top:0;-webkit-overflow-scrolling:touch;-webkit-transform:translateZ(0);-webkit-backface-visibility:hidden}body.ttrss_main #headlines-toolbar_splitter,body.ttrss_main #toolbar_splitter{display:none}body.ttrss_main #toolbar{padding:0;margin:0;border-width:0;white-space:nowrap;font-size:12px}body.ttrss_main #main-toolbar{background:#fff;border:0 solid #ddd;border-bottom-width:1px;padding-left:4px;height:26px}body.ttrss_main #header{border-width:0;text-align:right;color:#555;padding:5px 5px 0 0;margin:0;position:absolute;right:0;top:0;z-index:5}body.ttrss_main #footer{text-align:center;color:#555;padding:4px 4px 8px 4px;border-width:0}body.ttrss_main #content-insert{padding:0;border-color:#ddd;border-width:0;line-height:1.5;font-size:15px;overflow:auto;-webkit-overflow-scrolling:touch}body.ttrss_main img.feedIcon,body.ttrss_main img.tinyFeedIcon{width:16px;height:16px;line-height:16px;vertical-align:middle;display:inline-block}body.ttrss_main .player{display:inline-block;color:#555;font-size:11px;font-family:sans-serif;border:1px solid #555;padding:0 4px 0 4px;margin:0 2px 0 2px;width:50px;text-align:center;background:#fff}body.ttrss_main .player.playing{color:#00c000;border-color:#00c000}body.ttrss_main .player:hover{background:#f0f0f0;cursor:pointer}body.ttrss_main #headlines-spacer{height:100%;margin-left:1px;text-align:center;color:#555;font-size:11px;font-style:italic}body.ttrss_main #headlines-spacer a,body.ttrss_main #headlines-spacer span{color:#555;padding:10px;display:block}body.ttrss_main #headlines-spacer a:hover{color:#257aa7}body.ttrss_main ul#filterDlg_Matches,body.ttrss_main ul#filterDlg_Actions{max-height:100px;overflow:auto;list-style-type:none;border-style:solid;border-color:#ddd;border-width:0 1px 1px 1px;background-color:#fff;margin:0 0 5px 0;padding:0}body.ttrss_main ul#filterDlg_Matches li,body.ttrss_main ul#filterDlg_Actions li{cursor:pointer;padding:0 0 0 5px}body.ttrss_main ul.helpKbList{max-height:300px;overflow:auto;list-style-type:none;border:1px solid #ddd;margin:0 0 5px 0;padding:5px}body.ttrss_main ul.helpKbList span.hksequence{width:6em;margin-left:20px;color:#257aa7;font-weight:bold;display:inline-block}body.ttrss_main ul.helpKbList h2{margin-top:0}body.ttrss_main span.collapseBtn{cursor:pointer}body.ttrss_main span.collapseBtn img{vertical-align:middle}body.ttrss_main select.attachments{display:block;margin-top:10px;max-width:120px}body.ttrss_main #selected_prompt{margin-right:25px;vertical-align:middle}body.ttrss_main #filterDlg_feeds select{height:150px;width:410px}body.ttrss_main ul#filterDlg_Matches li div.dijitCheckBox,body.ttrss_main ul#filterDlg_Actions li div.dijitCheckBox{margin-right:5px}body.ttrss_main span.highlight{background-color:#ff0;color:#cc90cc}body.ttrss_main #headlines-frame .dijitCheckBox{border-width:0;opacity:.5}body.ttrss_main #headlines-frame .dijitCheckBoxHover,body.ttrss_main #headlines-frame .dijitCheckBoxChecked{opacity:1}body.ttrss_main #feedTree .dijitTreeRow img.dijitTreeExpandoLeaf{width:16px;height:16px;vertical-align:middle;position:relative}body.ttrss_main .dijitDropDownButton.attachments .dijitButtonText{font-size:12px}body.ttrss_main .dijitDropDownButton.attachments{display:inline-block}body.ttrss_main #editTagsDlg{overflow:visible}body.ttrss_main #feedEditDlg img.feedIcon{border:1px solid #ccc;padding:5px;margin:5px;max-width:20px;max-height:20px;height:auto;width:auto}body.ttrss_login{padding:2em;font-size:14px}body.ttrss_login fieldset{margin-left:auto;margin-right:auto;display:block;width:400px;border-width:0}body.ttrss_login label{width:120px;margin-right:20px;display:inline-block;text-align:right;color:#808080}body.ttrss_login div.header{border:0 solid #257aa7;border-bottom-width:1px;margin-bottom:1em;padding-bottom:5px}body.ttrss_login div.footer{margin-top:1em;padding-top:5px;border:0 solid #257aa7;border-top-width:1px;text-align:center;color:#808080;font-size:12px}body.ttrss_login a.forgotpass{text-align:right;font-size:11px;display:inline-block}body.ttrss_login a{color:#257aa7;text-decoration:none}body.ttrss_login a:hover,body.ttrss_login a:focus{color:#257aa7;text-decoration:underline}body.ttrss_login div.footer a{color:#808080}body.ttrss_login div.footer a:hover{color:#257aa7}body.ttrss_login div.row{padding:0 0 5px 0}body.ttrss_login div.row-error{color:#f00;text-align:center;padding:0 0 5px 0}.cdm{margin-right:4px}.cdm .cdmHeader,.cdm .cdmFooter{display:table}.cdm .cdmHeader img,.cdm .cdmHeader input,.cdm .cdmFooter img{vertical-align:middle}.cdm .cdmHeader>div,.cdm .cdmFooter>div{white-space:nowrap}.cdm .cdmHeader>span,.cdm .cdmFooter>span.left{width:100%}.cdm .cdmHeader img,.cdm .cdmFooter img{margin:0 4px}.cdm .cdmHeader>*{display:table-cell;padding:5px}.cdm .cdmHeader span.updated{color:#555;font-weight:normal;font-size:11px;white-space:nowrap;vertical-align:middle}.cdm .cdmHeader input{margin-right:5px}.cdm .cdmHeader div.updPic{width:25px;display:inline-block;text-align:center}.cdm .cdmHeader div.updPic img{vertical-align:middle}.cdm .cdmHeader input{margin-left:4px;margin-right:4px}.cdm .cdmFooter{height:30px;padding-left:5px;font-weight:normal;color:#555;clear:both}.cdm .cdmFooter>*{display:table-cell;vertical-align:middle}.cdm .cdmIntermediate{margin:10px}.cdm .cdmContentInner{margin:10px;line-height:1.5;font-size:16px}.cdm .cdmContentInner h1{font-size:16px}.cdm .cdmContentInner h2,.cdm .cdmContentInner h3,.cdm .cdmContentInner h4{font-size:15px}.cdm .cdmIntermediate img,.cdm .cdmIntermediate video,.cdm .cdmContentInner img,.cdm .cdmContentInner video{border-width:0;max-width:98%;height:auto}.cdm.expanded{margin-top:4px;margin-bottom:4px}.cdm.expanded .cdmFooter{border:0 solid #ddd;border-bottom-width:1px}.cdm.expanded>hr{margin-top:0;margin-bottom:0}.cdm.expandable{background-color:#f0f0f0;border:0 solid #ddd;border-bottom-width:1px}.cdm.expandable>hr{display:none}.cdm.expandable div.cdmHeader span.titleWrap{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;max-width:500px}.cdm.expandable.Unread{background:#fff}.cdm.expandable.Selected{background:#f9fbff}.cdm.expandable.active{background:#fff ! important}div.cdm.expandable.active div.cdmHeader span.titleWrap{white-space:normal}div.cdm.expandable div.cdmHeader a.title{font-weight:600;color:#555;font-size:14px;-webkit-transition:color .2s;transition:color .2s;text-rendering:optimizelegibility;font-family:"Segoe WP Semibold","Segoe UI Semibold","Segoe UI Web Semibold","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}div.cdm.expandable.Unread div.cdmHeader a.title{color:#000}div.cdm.expandable.active div.cdmHeader a.title{color:#257aa7;font-size:16px;font-weight:600;text-rendering:optimizelegibility;font-family:"Segoe WP Semibold","Segoe UI Semibold","Segoe UI Web Semibold","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}div.cdm.expanded div.cdmHeader{background:transparent ! important}div.cdm.expanded div.cdmHeader a.title{font-size:16px;color:#999;font-weight:600;-webkit-transition:color .2s;transition:color .2s;text-rendering:optimizelegibility;font-family:"Segoe WP Semibold","Segoe UI Semibold","Segoe UI Web Semibold","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}div.cdm.expanded.active{background:#fff}div.cdm.expanded.active div.cdmHeader a.title{color:#257aa7}div.cdm.expanded.Unread div.cdmHeader a.title{color:#000}div.cdm.expanded div.cdmContent{color:#555}div.cdm.expanded.Unread div.cdmContent{color:#000}div.cdm.active div.cdmContent{color:#000}span.cdmExcerpt{white-space:nowrap;font-size:11px;color:#999;font-weight:normal;cursor:pointer}div.cdmContent div.postEnclosures{margin-top:1em;color:#555}div.cdmFeedTitle{border:0 solid #257aa7;border-bottom-width:1px;padding:5px 3px 5px 5px}div.cdmFeedTitle a.title{color:#555;font-weight:bold}div.cdmFeedTitle a{color:#555}div.cdmFeedTitle a:hover{color:#257aa7}div.cdmHeader span.hlFeed{float:right;font-weight:normal;font-style:italic}div.cdmHeader div.hlFeed,div.cdmHeader div.hlFeed a{vertical-align:middle;color:#555;font-weight:normal;font-style:italic;font-size:11px}div.cdm .hlFeed a{border-radius:4px;display:inline-block;padding:1px 4px 1px 4px}div.cdmContentInner p{-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto}div.cdmContentInner iframe{min-width:50%;max-width:98%}div.cdmHeader span.author{white-space:nowrap;color:#555;font-size:11px;font-weight:normal}div#floatingTitle{position:absolute;z-index:5;top:0;right:0;left:0;border:0 solid #ddd;border-bottom-width:1px;background:#fff;color:#555;box-shadow:0 1px 1px -1px rgba(0,0,0,0.1)}div#floatingTitle>*{display:table-cell;white-space:nowrap;vertical-align:middle;padding:9px 5px}div#floatingTitle img{margin-right:4px;margin-left:4px}div#floatingTitle span.author{color:#555;font-size:11px;font-weight:normal}div#floatingTitle a.title{font-size:16px;color:#999;-webkit-transition:color .2s;transition:color .2s;font-weight:600;text-rendering:optimizelegibility;font-family:"Segoe WP Semibold","Segoe UI Semibold","Segoe UI Web Semibold","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}div#floatingTitle img.anchor{margin-left:0}div#floatingTitle div.hlFeed{padding-right:10px;color:#555;font-weight:normal;font-style:italic;font-size:11px;white-space:nowrap}div#floatingTitle div.hlFeed a{border-radius:4px;display:inline-block;padding:1px 4px 1px 4px}div#floatingTitle span.updated{padding-right:10px;white-space:nowrap;color:#555;font-size:11px}div#floatingTitle div.hlFeed a{color:#555}div#floatingTitle span.titleWrap{width:100%;white-space:normal}div#floatingTitle .dijit,div#floatingTitle img.hlScorePic{display:none}div#floatingTitle.Unread a.title{color:#000}.cdm.high .cdmHeader a.title.high,.cdm.high .cdmHeader .cdmExcerpt,.cdm.high .cdmHeader span.author{color:#0a0}.cdm.Unread.high .cdmHeader a.title.high,.cdm.Unread.high .cdmHeader .cdmExcerpt,.cdm.Unread.high .cdmHeader span.author{color:#0d0}.cdm .cdmHeader a.title.low,.cdm.low .cdmHeader .cdmExcerpt,.cdm.Unread .cdmHeader a.title.low,.cdm.Unread.low .cdmHeader .cdmExcerpt,.cdm.low .cdmHeader span.author{color:#909090;text-decoration:line-through}.cdmFeedTitle>*{display:table-cell;vertical-align:middle}.cdmFeedTitle a.title{width:100%}.cdmFeedTitle a.catchup{text-align:right;color:#555;padding-right:10px;font-size:11px;white-space:nowrap}.cdmFeedTitle a.catchup:hover{color:#257aa7}body.ttrss_prefs{background-color:#f5f5f5}body.ttrss_prefs #footer,body.ttrss_prefs #header{background-color:#f5f5f5;padding-left:8px;padding-right:8px}body.ttrss_prefs #header a:hover{color:#000}body.ttrss_prefs #header img{vertical-align:middle;cursor:pointer}body.ttrss_prefs div#pref-tabs .dijitContentPane{font-size:13px}body.ttrss_prefs div#pref-tabs{box-shadow:0 1px 1px -1px rgba(0,0,0,0.1);margin:0 5px 0 5px}body.ttrss_prefs div#pref-tabs .dijitContentPane h3{font-size:14px}body.ttrss_prefs #pref-filter-wrap,body.ttrss_prefs #pref-filter-header,body.ttrss_prefs #pref-filter-content,body.ttrss_prefs #pref-label-wrap,body.ttrss_prefs #pref-label-header,body.ttrss_prefs #pref-label-content,body.ttrss_prefs #pref-user-wrap,body.ttrss_prefs #pref-user-header,body.ttrss_prefs #pref-user-content,body.ttrss_prefs #pref-instance-wrap,body.ttrss_prefs #pref-instance-header,body.ttrss_prefs #pref-instance-content{margin:0;padding:0;border-width:0}body.ttrss_prefs #userConfigTab,body.ttrss_prefs #labelConfigTab,body.ttrss_prefs #filterConfigTab,body.ttrss_prefs #pref-feeds-feeds,body.ttrss_prefs #instanceConfigTab{padding:0}body.ttrss_prefs table.prefPrefsList h3{margin-top:.5em;margin-bottom:0}body.ttrss_prefs tr.title td{border:0 solid #ecf4ff;border-bottom-width:1px;color:#257aa7}body.ttrss_prefs div.prefProfileHolder,body.ttrss_prefs div.prefFeedOPMLHolder,body.ttrss_prefs div.inactiveFeedHolder{height:300px;overflow:auto;border:1px solid #ddd;border-top-width:0;margin:0 0 5px 0;background-color:#fff}body.ttrss_prefs div.filterTestHolder,body.ttrss_prefs div.prefFeedOPMLHolder{border-width:1px}body.ttrss_prefs ul.userFeedList{height:200px;overflow:auto;list-style-type:none;border:1px solid #ddd;margin:0 0 5px 0;padding:5px}body.ttrss_prefs div#feedlistLoading,body.ttrss_prefs div#filterlistLoading,body.ttrss_prefs div#labellistLoading{text-align:center;padding:5px;color:#555}body.ttrss_prefs div#feedlistLoading img,body.ttrss_prefs div#filterlistLoading img,body.ttrss_prefs div#labellistLoading{margin-right:5px}body.ttrss_prefs #errorButton{color:#f00}body.ttrss_prefs table.prefPluginsList td label,body.ttrss_prefs table.prefUserList td{cursor:pointer}body.ttrss_prefs table.prefPluginsList label{white-space:nowrap}body.ttrss_prefs table.prefPluginsList label img{vertical-align:middle}body.ttrss_prefs table.prefErrorLog tr.errrow td{font-size:10px}body.ttrss_prefs table.prefErrorLog tr.errrow td.errno{font-style:italic;font-weight:bold;white-space:nowrap}body.ttrss_prefs table.prefErrorLog td.filename,body.ttrss_prefs table.prefErrorLog td.login,body.ttrss_prefs table.prefErrorLog td.timestamp{color:#555}body.ttrss_prefs hr{border-color:#ecf4ff;max-width:100%}.claro .dijitTreeRow .dijitCheckBox{position:relative;top:-2px}.claro .dijitTreeLabel{outline:0}.claro .dijitTree .feedParam{color:#555;float:right;margin-right:1em}.claro .dijitTree .filterRules{display:block;color:#ccc;font-size:10px;margin-left:100px}.claro .dijitTree .filterRules span{display:block;color:#008000}.claro #filterDlg_Matches span.filterRule{color:#008000}.claro .dijitTree .filterRules span.inverse,.claro #filterDlg_Matches span.filterRule.inverse{color:#f00}.claro .dijitTree .labelParam{float:right;margin-right:1em}.claro .dijitTree .dijitTreeLabel.filterDisabled,.claro .dijitTree .labelParam.filterDisabled{color:#555;text-decoration:line-through}.claro .dijitTreeRow.Error{color:#f00}.claro .dijitTreeRow.Hidden{display:none}.claro .dijitTreeNode .loadingNode{margin-left:3px;height:9px}.claro .dijitFolderClosed,.claro .dijitFolderOpened{display:none}.claro .dijitTreeNode .dijitCheckBox{margin-left:4px}.claro .dijitTreeIsRoot>.dijitTreeRow>.dijitTreeExpando{margin-left:5px}.claro .dijitTree .dijitTreeExpando{margin-top:0;opacity:.6}.claro .dijitTree .dijitTreeNode{padding:0;border-width:0}.claro .dijitTree .dijitTreeRowSelected{background:#fff}.claro .dijitTree .dijitTreeRowHover{background:#f0f0f0;border-color:#ddd}.claro .dijitTree .dijitTreeRowSelected{background:#fff;border-color:#ddd}.claro .dijitTreeRowSelected .dijitTreeLabel{text-shadow:1px 1px 2px #fff}.claro .dijitTreeRow .dijitTreeExpando{background-image:url("../images/treeExpandImages.png");position:relative;top:-1px}.claro .dijitTreeRow .dijitTreeExpandoLeaf{background:none}.claro .dijitToolbar{background:#f5f5f5;border-color:#ddd}.claro .dijitDialog .dijitToolbar{border:1px solid #ddd}.claro .dijitDialog h2{margin-top:0;margin-bottom:4px;border-width:0}.claro .dijitMenu .dijitMenuItem .dijitMenuItemLabel{font-size:13px;padding-top:3px;padding-bottom:3px}.claro .dijitCheckBox{background-image:url("../images/untick.png");background-color:transparent;width:15px;height:15px;margin:1px;opacity:.7;background-position:center center;transition:opacity .25s;-webkit-transition:opacity .25s;padding:1px}.claro .dijitCheckBox:hover{opacity:1}.claro .dijitCheckBox.dijitCheckBoxDisabled:hover{opacity:.7}.claro .dijitCheckBox.dijitCheckBoxChecked{border-color:#69c671;background-image:url("../images/tick.png");opacity:1}.claro .dijitButton.danger .dijitButtonText{color:#fff}.claro .dijitButton.danger{text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(to bottom, #ee5f5b, #bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.claro .dijitButton.dijitButtonDisabled.danger,.claro .dijitButton.dijitButtonActive.danger,.claro .dijitButton.dijitButtonHover.danger,.claro .dijitButton.dijitFocused.danger{color:#fff;background-color:#bd362f;*background-color:#a9302a}.claro .dijitButton.dijitButtonActive.danger{background-color:#942a25 \9}.claro .dijitDropDownButton{margin:0}.claro .dijitDropDownButton .dijitButtonNode{padding:0}.claro .dijitButton.dijitButtonActive.danger{color:rgba(255,255,255,0.75)}.claro .dijitButton .dijitButtonNode,.claro .dijitDropDownButton .dijitButtonNode,.claro .dijitComboButton .dijitButtonNode,.claro .dijitToolbar .dijitDropDownButton .dijitButtonNode,.claro .dijitToolbar .dijitComboButton,.claro .dijitToolbar .dijitSelect.dijitDownArrowButton .dijitButtonNode,.claro .dijitToolbar .dijitComboButton .dijitButtonNode{background:none;border-color:transparent;box-shadow:none}.claro button,.claro input[type="submit"]{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px}.claro button,.claro input[type="submit"],.claro .dijitButton,.claro .dijitDropDownButton .dijitDownArrowButton,.claro .dijitComboButton{display:inline-block;padding:4px 12px;margin-bottom:0;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #fff, #e6e6e6);background-image:-webkit-linear-gradient(top, #fff, #e6e6e6);background-image:linear-gradient(to bottom, #fff, #e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#b3b3b3;-webkit-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.claro button:hover,.claro button:focus,.claro button:active,.claro input[type="submit"]:hover,.claro input[type="submit"]:focus,.claro input[type="submit"]:active,.claro .dijitButton:hover,.claro .dijitButton:focus,.claro .dijitButton:active,.claro .dijitDropDownButton .dijitDownArrowButton:hover,.claro .dijitDropDownButton .dijitDownArrowButton:focus,.claro .dijitDropDownButton .dijitDownArrowButton:active,.claro .dijitComboButton:hover,.claro .dijitComboButton:focus,.claro .dijitComboButton:active,.claro .dijitButton.dijitButtonDisabled{color:#333;background-color:#e6e6e6}.claro button:active,.claro input[type="submit"]:active,.claro .dijitButton:active,.claro .dijitDropDownButton .dijitDownArrowButton:active,.claro .dijitComboButton:active{background-color:#cccccc \9}.claro .dijitToolbar .dijitButton,.claro .dijitToolbar .dijitButton.dijitHover,.claro .dijitToolbar .dijitComboButton,.claro .dijitToolbar .dijitDropDownButton .dijitDownArrowButton,.claro .dijitToolbar .dijitComboButton.dijitHover{background:none;border-color:transparent;box-shadow:none;padding:0;margin:0;line-height:auto;text-shadow:none}.claro .dijitToolbar .dijitDropDownButton .dijitButtonText,.claro .dijitToolbar .dijitDownArrowButton .dijitButtonText,.claro .dijitToolbar .dijitComboButton .dijitButtonText{padding:0}.claro .dijitToolbar .dijitDropDownButton .dijitButtonNode{border-radius:4px}.claro .dijitToolbar .dijitButton.dijitHover,.claro .dijitToolbar .dijitDropDownButton.dijitHover .dijitButtonNode,.claro .dijitToolbar .dijitComboButton.dijitHover{border-color:#ccc}.claro .dijitToolbar .dijitButton.dijitHover .dijitButtonNode,.claro .dijitToolbar .dijitButton.dijitButtonActive .dijitButtonNode{background:none}.claro .dijitToolbar .dijitButton .dijitButtonContents,.claro .dijitToolbar .dijitDropDownButton .dijitButtonContents,.claro .dijitToolbar .dijitComboButton .dijitButtonContents{font-size:13px}.claro button:hover,.claro button:focus,.claro input[type="submit"]:hover,.claro input[type="submit"]:focus,.claro .dijitButton:hover,.claro .dijitDropDownButton .dijitDownArrowButton:hover,.claro .dijitToolbar .dijitButton:hover .dijitButtonNode,.claro .dijitToolbar .dijitButton.dijitHover .dijitButtonNode,.claro .dijitButton:focus,.claro .dijitComboButton:hover,.claro .dijitComboButton:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;transition:background-position .1s linear}.claro button:focus,.claro input[type="submit"]:focus,.claro .dijitButton:focus,.claro .dijitDropDownButton .dijitDownArrowButton:focus,.claro .dijitComboButton:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.claro button:active,.claro input[type="submit"]:active,.claro .dijitButton:active,.claro .dijitComboButton:active,.claro .dijitToolbar .dijitDropDownButton.dijitOpened,.claro .dijitToolbar .dijitComboButton.dijitOpened,.claro .dijitToolbar .dijitButton.dijitButtonActive .dijitButtonNode{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.claro input[type="submit"][disabled],.claro button[disabled],.claro .dijitButton[disabled],.claro .dijitDropDownButton .dijitDownArrowButton[disabled],.claro .dijitButton.dijitButtonDisabled,.claro .dijitComboButton.dijitButtonDisabled{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.claro .dijitButton .dijitButtonContents,.claro .dijitDropDownButton .dijitButtonContents,.claro .dijitComboButton .dijitButtonContents{font-size:14px;font-weight:normal;line-height:20px}.claro .dijitButton.small .dijitButtonText{font-size:11px}.claro .dijitMenu{border-color:#257aa7}.claro .dijitMenu .dijitMenuItemSelected,.claro .dijitMenu .dijitMenuItemSelected td{background:#257aa7;color:#fff;border-color:#257aa7}.claro .dijitButton .dijitButtonNode,.claro .dijitComboButton .dijitButtonNode{padding:0}.claro .dijitAccordionTitle.dijitAccordionTitleHover,.claro .dijitAccordionTitle.dijitAccordionTitleFocused{background:#fff;transition:background .25s}.claro .dijitAccordionTitle{background:#f0f0f0;transition:background .25s}.claro .dijitAccordionInnerContainer.dijitAccordionInnerContainerSelected,.claro .dijitAccordionTitle.dijitAccordionTitleSelected{background:#257aa7;transition:background .25s}.claro .dijitAccordionTitle.dijitAccordionTitleSelected .dijitAccordionText{color:#fff}.claro .dijitAccordionInnerContainer.dijitAccordionInnerContainerSelected{border-color:#257aa7}.claro .dijitAccordionContainer .dijitAccordionChildWrapper{border-color:#ddd}.claro .dijitTabInner.dijitTab{background:#f0f0f0}.claro .dijitTabContent{background:#eee}.claro .dijitTabContent.dijitTabChecked,.claro .dijitTabContent.dijitTabHover,.claro .dijitTabContent.dijitFocused{background:#fff}.claro .dijitTabPaneWrapper,.claro .dijitTabContainerTop-tabs,.claro .dijitTab,.claro .dijitAccordionInnerContainer{border-color:#ddd}.claro .dijitComboBox .dijitArrowButton,.claro .dijitSelect .dijitArrowButton{background:transparent;border-color:transparent}.claro .dijitSelect .dijitArrowButton .dijitArrowButtonInner{margin-right:5px;float:right}.claro select,.claro .dijitDownArrowButton.dijitSelect{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;margin-bottom:4px}.claro .dijitSelect .dijitButtonContents{display:inline-block;height:20px;padding:4px 6px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;border-width:0}.claro select,.claro textarea,.claro .input.input-text,.claro .dijitTextBox{display:inline-block;height:20px;padding:4px 6px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;margin-bottom:4px}.claro textarea{height:auto}.claro select,.claro .input.input-text{height:30px}.claro textarea,.claro select,.claro .input.input-text,.claro .dijitTextBox,.claro .dijitSelect{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}.claro select:focus,.claro .input.input-text:focus,.claro .dijitTextBox.dijitFocused,.claro .dijitSelect.dijitFocused{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(37,122,167,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(37,122,167,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(37,122,167,0.6)}.claro .dijitError .dijitValidationContainer{padding:0;width:13px;border-width:1px;display:none}.claro .dijitSelect.dijitSelectDisabled{background-color:#eee}.claro .dijitSelect.dijitSelectDisabled .dijitSelectLabel{cursor:not-allowed}.claro .dijitTextBox.dijitTextBoxDisabled,.claro .dijitTextBox.dijitTextBoxDisabled .dijitInputInner,.claro .dijitTextBox.dijitReadOnly,.claro .dijitTextBox.dijitReadOnly .dijitInputInner{cursor:not-allowed;background-color:#eee}.claro .dijitToolbar .dijitDownArrowButton.dijitSelect{border-color:rgba(0,0,0,0.1)}.claro .dijitToolbar .dijitDownArrowButton.dijitSelect .dijitButtonContents{padding:2px 2px 0 4px}.claro .dijitToolbar .dijitDownArrowButton.dijitSelect{margin:0}.claro .dijitToolbar .dijitTextBox{padding:0;margin-bottom:0;border-radius:0}.claro .dijitDialog{border-radius:6px}.claro .dijitDialog .dijitDialogCloseIcon{margin-top:5px}.claro .dijitDialog .dijitDialogTitleBar{background:#fff;padding:8px;font-weight:600;color:#555;font-size:16px;text-rendering:optimizelegibility;font-family:"Segoe WP Semibold","Segoe UI Semibold","Segoe UI Web Semibold","Segoe UI",Ubuntu,"Helvetica Neue",Helvetica,Arial,sans-serif}.claro .dijitDialog .dijitDialogPaneContent{border-color:#ddd;padding:10px}.claro .dijitProgressBar.dijitProgressBarEmpty{background:#ddd;border-color:#257aa7}.claro .dijitProgressBar.dijitProgressBarEmpty .dijitProgressBarFull .dijitProgressBarTile{background:#257aa7}.claro .dijitProgressBar .dijitProgressBarLabel{color:#fff}body.ttrss_utility.sanity_failed{background:#900}body.ttrss_utility{background:#f5f5f5;color:#000;padding:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;margin-left:auto;margin-right:auto;max-width:800px}body.ttrss_utility form{margin:10px 0 0 0;padding:0}body.ttrss_utility div.content{overflow:hidden;background:#fff;border:1px solid #ddd;padding:10px;border-radius:6px;box-shadow:0 1px 1px -1px rgba(0,0,0,0.1)}body.ttrss_utility p.warning{color:#f00}body.ttrss_utility p.query,body.ttrss_utility code{color:#008000}body.ttrss_utility p.insensitive{color:#808080}body.ttrss_utility div.insensitive-small{color:#808080;font-size:10px}body.ttrss_utility .floatingLogo{display:none}body.ttrss_utility a{color:#52a8ec;text-decoration:none}body.ttrss_utility a:hover{color:#000}body.ttrss_utility .alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}body.ttrss_utility .alert,body.ttrss_utility .alert h4{color:#c09853}body.ttrss_utility .alert h4{margin:0}body.ttrss_utility .alert .close{position:relative;top:-2px;right:-21px;line-height:20px;cursor:pointer}body.ttrss_utility .alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}body.ttrss_utility .alert-success h4{color:#468847}body.ttrss_utility .alert-danger,body.ttrss_utility .alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}body.ttrss_utility .alert-danger h4,body.ttrss_utility .alert-error h4{color:#b94a48}body.ttrss_utility .alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}body.ttrss_utility .alert-info h4{color:#3a87ad}body.ttrss_utility h1{color:#52a8ec;font-size:32px;margin:20px 0 5px 0;text-shadow:0 0 6px #fff}body.ttrss_utility h2{color:#52a8ec;font-size:14pt;border-width:0 0 1px 0;border-color:#f0f0f0;border-style:solid}body.ttrss_utility div.content>h2{margin-top:0}body.ttrss_utility div.rss h1{border-width:0 0 1px 0;border-color:#808080;border-style:dotted;color:#808080}body.ttrss_utility div.rss h2{font-size:12pt}body.ttrss_utility div.rss a.extlink{color:#808080;border-width:0 0 1px 0;border-color:#789;border-style:dotted;font-size:9pt}body.ttrss_utility div.rss img{max-width:775px}body.ttrss_utility div.rss p.description{color:#808080;font-size:9pt}body.ttrss_utility div.rss div.content{margin-top:.5em}body.ttrss_utility div.rss img.feedicon{float:right}body.ttrss_utility div.rss hr{border-width:0 0 1px 0;border-style:dashed;border-color:#e0e0e0}body.ttrss_utility div.autocomplete{position:absolute;width:250px;background-color:#fff;border:1px solid #789;margin:0;padding:0;z-index:4}body.ttrss_utility div.autocomplete ul{list-style-type:none;margin:0;padding:0;font-size:10px}body.ttrss_utility div.autocomplete ul li.selected{background-color:#fff7d5}body.ttrss_utility div.autocomplete ul li{list-style-type:none;display:block;margin:0;padding:2px;height:32px;cursor:pointer}body.ttrss_utility fieldset{border-width:0;padding:0 0 5px 0;margin:0}body.ttrss_utility fieldset input{font-family:sans-serif;font-size:medium;border-spacing:2px;border:1px solid #b5bcc7;padding:2px}body.ttrss_utility fieldset label{width:120px;margin-right:20px;display:inline-block;text-align:right;color:#808080}body.ttrss_utility body.otp{margin:1em;padding:0}body.ttrss_utility form.otpform{margin:0;padding:0}body.ttrss_utility form.otpform label{margin:0;padding:0}body.ttrss_utility body.otp div.content{display:inline-block;width:auto}body.ttrss_utility span.hint{font-size:10px;color:#808080}body.small_margins{margin:1em;max-width:none}body#sharepopup{background:#fff url("../images/toolbar.png") repeat-x bottom;margin:10px;padding:0}body#sharepopup h1{font-size:14px;margin:0;color:#52a8ec}body#sharepopup table{background:#fff;border:1px solid #52a8ec;padding:5px}body#sharepopup form{height:100%}body#sharepopup input{width:100%}body.ttrss_zoom{margin-left:auto;margin-right:auto;padding:20px;max-width:770px;background:#f5f5f5}body.ttrss_zoom div.postHeader{margin:10px;padding-bottom:10px;border:0 solid #eee;border-bottom-width:1px;background:#fff}body.ttrss_zoom div.postHeader div.postFeedTitle{float:left;text-align:right;padding-left:0;font-size:11px}body.ttrss_zoom div.postHeader a.postComments{text-align:right;padding-left:0;font-size:11px}body.ttrss_zoom div.postHeader div.postDate{float:none;text-align:right;padding-left:0;color:#777;font-size:11px}body.ttrss_zoom div.postHeader div.postTags{color:#777;font-size:11px}body.ttrss_zoom div.postHeader div.postTitle{white-space:normal;font-size:16px}body.ttrss_zoom p{-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto}body.ttrss_zoom div.postReply{border:1px solid #ddd;background:#fff;box-shadow:0 1px 1px -1px rgba(0,0,0,0.1);border-radius:6px}body.ttrss_zoom div.footer{margin-top:1em;text-align:center}body.ttrss_zoom div.postContent{font-size:15px;line-height:1.5;padding:10px}body.ttrss_zoom div.postContent img{max-width:730px;height:auto}body.ttrss_zoom div.postContent blockquote{margin:5px 0 5px 0;color:#555;padding-left:10px;border:0 solid #ccc;border-left-width:4px}body.ttrss_zoom div.postContent code{color:#090;font-family:monospace;font-size:12px}body.ttrss_zoom div.postContent pre{margin:5px 0 5px 0;padding:10px;color:#555;font-family:monospace;font-size:12px;border:0 solid #ccc;background:#f5f5f5;display:block;max-width:98%;overflow:auto}
\ No newline at end of file diff --git a/css/default.less b/css/default.less index 73ad2b055..295b24b9a 100644 --- a/css/default.less +++ b/css/default.less @@ -1,11 +1,25 @@ @fonts-ui-bold: "Segoe WP Semibold", "Segoe UI Semibold", "Segoe UI Web Semibold", "Segoe UI", Ubuntu, "Helvetica Neue", Helvetica, Arial, sans-serif; @fonts-ui: "Segoe UI Web", "Segoe UI", Ubuntu, "Helvetica Neue", Helvetica, Arial, sans-serif; -@color-accent: rgb(82, 168, 236); -@color-accent-light: #ecf4ff; -@color-link: #0088cc; +@color-accent: #257aa7; +@color-accent-light: lighten(@color-accent, 50%); +@color-link: @color-accent; +@default-text: #555; + +body.ttrss_main, +body.ttrss_prefs, +#main { + position : absolute; + width: 100%; + height: 100%; + border: 0; + padding: 0; + margin: 0; +} @import "tt-rss.less"; @import "cdm.less"; @import "prefs.less"; -@import "dijit.less";
\ No newline at end of file +@import "dijit.less"; +@import "utility.less"; +@import "zoom.less";
\ No newline at end of file diff --git a/css/dijit.less b/css/dijit.less index b350a79da..b1c097f18 100644 --- a/css/dijit.less +++ b/css/dijit.less @@ -12,7 +12,7 @@ } .dijitTree .feedParam { - color: #555; + color: @default-text; float: right; margin-right: 1em; } @@ -45,7 +45,7 @@ .dijitTree .dijitTreeLabel.filterDisabled, .dijitTree .labelParam.filterDisabled { - color: #555; + color: @default-text; text-decoration: line-through; } @@ -413,14 +413,14 @@ } .dijitMenu { - border-color: rgba(82, 168, 236, 0.8); + border-color: @color-accent; } .dijitMenu .dijitMenuItemSelected, .dijitMenu .dijitMenuItemSelected td { - background: rgb(82, 168, 236); + background: @color-accent; color: white; - border-color: rgba(82, 168, 236, 0.8); + border-color: @color-accent; } .dijitButton .dijitButtonNode, @@ -453,7 +453,7 @@ .dijitAccordionInnerContainer.dijitAccordionInnerContainerSelected, .dijitAccordionTitle.dijitAccordionTitleSelected { - background: #0088cc; + background: @color-accent; transition: background 0.25s; } @@ -462,7 +462,7 @@ } .dijitAccordionInnerContainer.dijitAccordionInnerContainerSelected { - border-color: #0088cc; + border-color: @color-accent; } .dijitAccordionContainer .dijitAccordionChildWrapper { @@ -520,7 +520,7 @@ padding: 4px 6px; font-size: 14px; line-height: 20px; - color: #555555; + color: @default-text; vertical-align: middle; border-width: 0px; } @@ -534,7 +534,7 @@ padding: 4px 6px; font-size: 14px; line-height: 20px; - color: #555555; + color: @default-text; vertical-align: middle; -webkit-border-radius: 4px; -moz-border-radius: 4px; @@ -575,9 +575,9 @@ outline: thin dotted \9; /* IE6-9 */ - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(red(@color-accent), green(@color-accent), blue(@color-accent), 0.6); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(red(@color-accent), green(@color-accent), blue(@color-accent), 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(red(@color-accent), green(@color-accent), blue(@color-accent), 0.6); } .dijitError .dijitValidationContainer { @@ -636,7 +636,7 @@ background: white; padding: 8px; font-weight: 600; - color: #555; + color: @default-text; font-size: 16px; text-rendering: optimizelegibility; font-family: @fonts-ui-bold; @@ -649,11 +649,11 @@ .dijitProgressBar.dijitProgressBarEmpty { background: #ddd; - border-color: #0088cc; + border-color: @color-accent; } .dijitProgressBar.dijitProgressBarEmpty .dijitProgressBarFull .dijitProgressBarTile { - background: rgb(82, 168, 236); + background: @color-accent; } diff --git a/css/layout.css b/css/layout.css deleted file mode 100644 index 376f9e63f..000000000 --- a/css/layout.css +++ /dev/null @@ -1,7 +0,0 @@ -html, body#ttrssMain, body#ttrssPrefs, #main { - width: 100%; - height: 100%; - border: 0; - padding: 0; - margin: 0; -} diff --git a/css/prefs.less b/css/prefs.less index a7b76bc7f..79653d7ab 100644 --- a/css/prefs.less +++ b/css/prefs.less @@ -1,4 +1,4 @@ -body#ttrssPrefs { +body.ttrss_prefs { background-color : #f5f5f5; #footer, #header { @@ -51,18 +51,16 @@ body#ttrssPrefs { } tr.title td { - border-width : 0px 0px 1px 0px; - border-color : #ecf4ff; - border-style : solid; + border: 0px solid #ecf4ff; + border-bottom-width: 1px; color : @color-link; } div.prefProfileHolder, div.prefFeedOPMLHolder, div.inactiveFeedHolder { height : 300px; overflow : auto; - border-width : 0px 1px 1px 1px; - border-color : #ddd; - border-style : solid; + border: 1px solid #ddd; + border-top-width: 0px; margin : 0px 0px 5px 0px; background-color : white; } @@ -82,7 +80,7 @@ body#ttrssPrefs { div#feedlistLoading, div#filterlistLoading, div#labellistLoading { text-align : center; padding : 5px; - color : #555; + color : @default-text; } div#feedlistLoading img, div#filterlistLoading img, div#labellistLoading { @@ -116,7 +114,7 @@ body#ttrssPrefs { } table.prefErrorLog td.filename, table.prefErrorLog td.login, table.prefErrorLog td.timestamp { - color : #555; + color : @default-text; } hr { diff --git a/css/tt-rss.less b/css/tt-rss.less index f762cc665..1f371908f 100644 --- a/css/tt-rss.less +++ b/css/tt-rss.less @@ -1,1225 +1,1189 @@ -body { +body.ttrss_main { background : white; color : black; - margin : 0px; - padding : 0px; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 14px; -} - -body#ttrssMain { overflow : hidden; - max-height : 100%; -} -:focus { - outline: none; -} + :focus { + outline: none; + } -div.postReply { - padding : 0px; + div.postReply { + padding : 0px; - div.postHeader { - padding : 5px; - margin-right : 4px; - color : #909090; - border: 0px solid #ddd; - border-bottom-width: 1px; + div.postHeader { + padding : 5px; + margin-right : 4px; + color : #909090; + border: 0px solid #ddd; + border-bottom-width: 1px; + + div.postDate { + text-align : right; + color : #909090; + float : right; + } + + div { + padding-bottom : 3px; + } + + span.author { + color : @default-text; + font-size : 11px; + font-weight : normal; + } + } + + div.postTitle { + overflow : hidden; + text-overflow: ellipsis; + white-space : nowrap; + font-weight : 600; + text-rendering: optimizelegibility; + font-family : @fonts-ui-bold; + } div.postDate { - text-align : right; - color : #909090; - float : right; + padding-left : 10px; } - div { - padding-bottom : 3px; + div.postContent { + padding : 10px; + font-size : 16px; + + img, + video { + border-width : 0px; + max-width : 98%; + height: auto; + } + + h1 { + font-size : 16px; + } + + h2, + h3, + h4 { + font-size : 15px; + } + + p { + hyphens: auto; + } + + iframe { + min-width : 50%; + max-width : 98%; + } + } + + div.postEnclosures { + color : @default-text; + } + + img.tagsPic { + width : 16px; + height : 16px; + margin-left : 4px; + vertical-align : middle; } span.author { - color : #555; - font-size : 11px; - font-weight : normal; + font-size : 12px; } } - div.postTitle { - overflow : hidden; - text-overflow: ellipsis; - white-space : nowrap; + div.articleNote { + background-color : #fff7d5; + padding : 5px; + margin : 5px; + border: 1px solid #e7d796; + color : #9a8c59; + + div.noteEdit { + float : right; + cursor : pointer; + } + } + + h1 { + font-size : 18px; font-weight : 600; text-rendering: optimizelegibility; - font-family : @fonts-ui-bold; + font-family : @fonts-ui; } - div.postDate { - padding-left : 10px; + h2 { + font-size : 16px; + font-weight : 600; + border: 0px solid @color-accent-light; + border-bottom-width: 1px; + text-rendering: optimizelegibility; + font-family : @fonts-ui; } - div.postContent { - padding : 10px; - font-size : 16px; + h3 { + font-size : 13px; + border: 0px solid @color-accent-light; + border-bottom-width: 1px; + font-weight : 600; + text-rendering: optimizelegibility; + font-family : @fonts-ui; + } - img, - video { - border-width : 0px; - max-width : 98%; - height: auto; - } + h4 { + font-size : 14px; + font-weight : 600; + text-rendering: optimizelegibility; + font-family : @fonts-ui; + } - h1 { - font-size : 16px; + hr { + border: 0px solid #ccc; + border-bottom-width: 1px; + } + + a { + color: @color-link; + text-decoration: none; + } + + a:hover { + color: darken(@color-link, 20%); + text-decoration: underline; + } + + #notify.visible { + transform: translate(0, -35px); + } + + #notify { + bottom : -35px; + right : 0px; + height : 20px; + left : 0px; + border-width : 1px 0px 0px 0px; + border-style : solid; + position : fixed; + font-size : 12px; + z-index : 99; + padding : 5px; + box-shadow : 0px -2px 2px rgba(0,0,0,0.1); + + transition: all 0.5s ease-in-out; + + img { + vertical-align : middle; + max-height : 14px; } - h2, - h3, - h4 { - font-size : 15px; + span.msg { + width : 100%; } - p { - hyphens: auto; + img.close { + cursor : pointer; } - iframe { - min-width : 50%; - max-width : 98%; + span { + display : table-cell; + vertical-align : middle; + padding : 2px; } } - div.postEnclosures { - color : #555; + .notify { + border-color : #d7c47a; + background-color : #fff7d5; } - img.tagsPic { - width : 16px; - height : 16px; - margin-left : 4px; - vertical-align : middle; + .notify.notify_progress { + border-color : #d7c47a; + background-color : #fff7d5; } - span.author { - font-size : 12px; + .notify.notify_info { + border-color : @color-accent; + background-color : @color-accent-light; } -} -div.articleNote { - background-color : #fff7d5; - padding : 5px; - margin : 5px; - border: 1px solid #e7d796; - color : #9a8c59; - - div.noteEdit { - float : right; - cursor : pointer; + .notify.notify_error { + background-color : #ffcccc; + border-color : #ff0000; } -} -h1 { - font-size : 18px; - font-weight : 600; - text-rendering: optimizelegibility; - font-family : @fonts-ui; -} + .hl { + border: 0px solid #ddd; + border-bottom-width: 1px; + padding : 1px; -h2 { - font-size : 16px; - font-weight : 600; - border: 0px solid @color-accent-light; - border-bottom-width: 1px; - text-rendering: optimizelegibility; - font-family : @fonts-ui; -} + div.hlTitle { + display : table-cell; + cursor : pointer; + width : 100%; + vertical-align : middle; + overflow : hidden; + white-space : nowrap; + max-width : 500px; + text-overflow : ellipsis; + padding: 4px 6px; + } -h3 { - font-size : 13px; - border: 0px solid @color-accent-light; - border-bottom-width: 1px; - font-weight : 600; - text-rendering: optimizelegibility; - font-family : @fonts-ui; -} + div.hlLeft { + display : table-cell; + vertical-align : middle; + white-space: nowrap; + } -h4 { - font-size : 14px; - font-weight : 600; - text-rendering: optimizelegibility; - font-family : @fonts-ui; -} + div.hlRight { + display : table-cell; + white-space: nowrap; + text-align : right; + vertical-align : middle; + } -hr { - border: 0px solid #ccc; - border-bottom-width: 1px; -} + div.hlRight img { + max-width : 16px; + max-height : 16px; + } -a { - color: @color-link; - text-decoration: none; -} + span.hlFeed { + display : table-cell; + vertical-align : middle; + text-align : right; + } -a:hover { - color: darken(@color-link, 20%); - text-decoration: underline; -} + span.hlFeed a { + border-radius : 4px; + display : inline-block; + padding : 1px 4px 1px 4px; + font-size : 11px; + font-style : italic; + font-weight : normal; + color : @default-text; + white-space : nowrap; + } -#notify.visible { - transform: translate(0, -35px); -} + span.hlFeed a:hover { + color : @color-accent; + } -#notify { - bottom : -35px; - right : 0px; - height : 20px; - left : 0px; - border-width : 1px 0px 0px 0px; - border-style : solid; - position : fixed; - font-size : 12px; - z-index : 99; - padding : 5px; - box-shadow : 0px -2px 2px rgba(0,0,0,0.1); - - transition: all 0.5s ease-in-out; - - img { - vertical-align : middle; - max-height : 14px; - } + span.hlUpdated { + color : @default-text; + display : table-cell; + vertical-align : middle; + text-align : right; + font-size : 11px; + white-space : nowrap; + padding-left : 10px; + } - span.msg { - width : 100%; - } + span.hlUpdated div { + display : inline-block; + } - img.close { - cursor : pointer; - } + div.hlLeft { + padding-left : 8px; + } - span { - display : table-cell; - vertical-align : middle; - padding : 2px; - } -} + div.hlLeft input { + margin-left : 4px; + margin-right : 4px; + } -.notify { - border-color : #d7c47a; - background-color : #fff7d5; -} + div.hlLeft img, div.hlRight img { + margin : 0px 4px; + } -.notify.notify_progress { - border-color : #d7c47a; - background-color : #fff7d5; -} + div.hlLeft img { + width : 16px; + height : 16px; + } -.notify.notify_info { - border-color : @color-accent; - background-color : @color-accent-light; -} + div.hlTitle a { + font-weight : 600; + text-rendering: optimizelegibility; + font-family : @fonts-ui; + color : #777; + } -.notify.notify_error { - background-color : #ffcccc; - border-color : #ff0000; -} + a.title.high, span.hlContent.high .contentPreview { + color : #00aa00; + } + } -.hl { - border: 0px solid #ddd; - border-bottom-width: 1px; - padding : 1px; + .hl.Unread a.title.high, .hl.Unread span.hlContent.high .contentPreview { + color : #00dd00; + } - div.hlTitle { - display : table-cell; - cursor : pointer; - width : 100%; - vertical-align : middle; - overflow : hidden; - white-space : nowrap; - max-width : 500px; - text-overflow : ellipsis; - padding: 4px 6px; + .hl a.title.low, span.hlContent.low .contentPreview, + .hl.Unread a.title.low, .hl.Unread span.hlContent.low .contentPreview { + color : #909090; + text-decoration : line-through; } - div.hlLeft { - display : table-cell; - vertical-align : middle; - white-space: nowrap; + .hl.Unread div.hlTitle a { + color : black; } - div.hlRight { - display : table-cell; - white-space: nowrap; - text-align : right; - vertical-align : middle; + .hl.active { + background : @color-accent-light ! important; } - div.hlRight img { - max-width : 16px; - max-height : 16px; + .hl.active div.hlTitle a { + color : @color-accent; + /* text-shadow : 1px 1px 2px #fff; */ } - span.hlFeed { - display : table-cell; - vertical-align : middle; - text-align : right; + .hl.Selected { + background : #f9fbff; } - span.hlFeed a { - border-radius : 4px; - display : inline-block; - padding : 1px 4px 1px 4px; - font-size : 11px; - font-style : italic; - font-weight : normal; - color : #555; - white-space : nowrap; + .hl.Grayed { + color : #909090; } - span.hlFeed a:hover { - color : @color-accent; + div.filterTestHolder { + height : 300px; + overflow : auto; + border-color : #ddd; + border-style : solid; + margin : 0px 0px 5px 0px; + border-width : 1px; } - span.hlUpdated { - color : #555; - display : table-cell; - vertical-align : middle; - text-align : right; - font-size : 11px; - white-space : nowrap; + #content-insert blockquote, + #headlines-frame blockquote, + .dijitContentPane blockquote { + margin : 5px 0px 5px 0px; + color : @default-text; padding-left : 10px; + border: 0px solid #ccc; + border-left-width: 4px; } - span.hlUpdated div { - display : inline-block; + #content-insert code, + #headlines-frame code, + .dijitContentPane code { + color : #009900; + font-family : monospace; + font-size : 12px; } - div.hlLeft { - padding-left : 8px; + #content-insert pre, + #headlines-frame pre, + .dijitContentPane pre { + margin: 5px 0px 5px 0px; + padding: 10px; + color: @default-text; + font-family: monospace; + font-size: 12px; + border: 0px solid #ccc; + background: #f5f5f5; + display: block; + max-width: 98%; + overflow: auto; } - div.hlLeft input { - margin-left : 4px; - margin-right : 4px; + .alert { + padding: 8px 35px 8px 14px; + margin-bottom: 10px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + background-color: #fcf8e3; + border: 1px solid #fbeed5; + border-radius: 4px; } - div.hlLeft img, div.hlRight img { - margin : 0px 4px; + .alert, + .alert h4 { + color: #c09853; } - div.hlLeft img { - width : 16px; - height : 16px; + .alert h4 { + margin: 0; } - div.hlTitle a { - font-weight : 600; - text-rendering: optimizelegibility; - font-family : @fonts-ui; - color : #777; + .alert .close { + position: relative; + top: -2px; + right: -21px; + line-height: 20px; + cursor : pointer; } - a.title.high, span.hlContent.high .contentPreview { - color : #00aa00; + .alert-success { + color: #468847; + background-color: #dff0d8; + border-color: #d6e9c6; } -} - -.hl.Unread a.title.high, .hl.Unread span.hlContent.high .contentPreview { - color : #00dd00; -} - -.hl a.title.low, span.hlContent.low .contentPreview, -.hl.Unread a.title.low, .hl.Unread span.hlContent.low .contentPreview { - color : #909090; - text-decoration : line-through; -} - -.hl.Unread div.hlTitle a { - color : black; -} - -.hl.active { - background : @color-accent-light ! important; -} -.hl.active div.hlTitle a { - color : @color-accent; - /* text-shadow : 1px 1px 2px #fff; */ -} - -.hl.Selected { - background : #f9fbff; -} - -.hl.Grayed { - color : #909090; -} - -div.filterTestHolder { - height : 300px; - overflow : auto; - border-color : #ddd; - border-style : solid; - margin : 0px 0px 5px 0px; - border-width : 1px; -} + .alert-success h4 { + color: #468847; + } -#content-insert blockquote, -#headlines-frame blockquote, -.dijitContentPane blockquote { - margin : 5px 0px 5px 0px; - color : #555; - padding-left : 10px; - border: 0px solid #ccc; - border-left-width: 4px; -} + .alert-danger, + .alert-error { + color: #b94a48; + background-color: #f2dede; + border-color: #eed3d7; + } -#content-insert code, -#headlines-frame code, -.dijitContentPane code { - color : #009900; - font-family : monospace; - font-size : 12px; -} + .alert-danger h4, + .alert-error h4 { + color: #b94a48; + } -#content-insert pre, -#headlines-frame pre, -.dijitContentPane pre { - margin: 5px 0px 5px 0px; - padding: 10px; - color: #555; - font-family: monospace; - font-size: 12px; - border: 0px solid #ccc; - background: #f5f5f5; - display: block; - max-width: 98%; - overflow: auto; -} + .alert-info { + color: #3a87ad; + background-color: #d9edf7; + border-color: #bce8f1; + } -.alert { - padding: 8px 35px 8px 14px; - margin-bottom: 10px; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - background-color: #fcf8e3; - border: 1px solid #fbeed5; - border-radius: 4px; -} + .alert-info h4 { + color: #3a87ad; + } -.alert, -.alert h4 { - color: #c09853; -} + ul.nomarks { + list-style-type : none; + margin : 0px; + padding : 10px; + } -.alert h4 { - margin: 0; -} + div.prefHelp { + color : @default-text; + padding : 5px; + } -.alert .close { - position: relative; - top: -2px; - right: -21px; - line-height: 20px; - cursor : pointer; -} + .insensitive { + color : @default-text; + } -.alert-success { - color: #468847; - background-color: #dff0d8; - border-color: #d6e9c6; -} + .small { + font-size : 11px; + } -.alert-success h4 { - color: #468847; -} + #main-toolbar { -.alert-danger, -.alert-error { - color: #b94a48; - background-color: #f2dede; - border-color: #eed3d7; -} + > * { + white-space : nowrap; + display : table-cell; + color : #999; + overflow : hidden; + } -.alert-danger h4, -.alert-error h4 { - color: #b94a48; -} + > *, + table *, + .actionChooser * { + text-rendering: optimizelegibility; + font-family : @fonts-ui; + font-size : 12px; -.alert-info { - color: #3a87ad; - background-color: #d9edf7; - border-color: #bce8f1; -} + } -.alert-info h4 { - color: #3a87ad; -} + #headlines-toolbar { + padding-right : 4px; + width : 100%; -ul.nomarks { - list-style-type : none; - margin : 0px; - padding : 10px; -} + span.holder { + display : table; + width : 100%; + } -div.prefHelp { - color : #555; - padding : 5px; -} + span.holder > * { + display : table-cell; + } -.insensitive { - color : #555; -} + .main { + text-align : right; + } -.small { - font-size : 11px; -} + .main, + .r { + line-height : 24px; + } -#main-toolbar { + span.r img { + margin-right : 4px; + position : relative; + top : 3px; + } - > * { - white-space : nowrap; - display : table-cell; - color : #999; - overflow : hidden; - } + span.r .error a { + color : red; + } - > *, - table *, - .actionChooser * { - text-rendering: optimizelegibility; - font-family : @fonts-ui; - font-size : 12px; - - } - - #headlines-toolbar { - padding-right : 4px; - width : 100%; - - span.holder { - display : table; - width : 100%; - } - - span.holder > * { - display : table-cell; } - .main { + #selected_prompt { + font-style : italic; text-align : right; - } - - .main, - .r { - line-height : 24px; - } - - span.r img { margin-right : 4px; - position : relative; - top : 3px; } - span.r .error a { - color : red; + @media (max-width: 992px) { + #selected_prompt { + display : none; + } } - } - #selected_prompt { - font-style : italic; - text-align : right; - margin-right : 4px; - } - @media (max-width: 992px) { - #selected_prompt { - display : none; - } + span.contentPreview { + color : #999; + font-weight : normal; + font-size : 12px; + padding-left : 4px; } -} - -span.contentPreview { - color : #999; - font-weight : normal; - font-size : 12px; - padding-left : 4px; -} - -span.hlLabelRef { - background-color : #fff7d5; - font-size : 8px; - color : #063064; - font-weight : normal; - margin-left : 2px; - padding : 1px 4px 1px 4px; - display : inline-block; - vertical-align : middle; - white-space: nowrap; - border-radius : 4px; -} + span.hlLabelRef { + background-color : #fff7d5; + font-size : 8px; + color : #063064; + font-weight : normal; + margin-left : 2px; + padding : 1px 4px 1px 4px; + display : inline-block; + vertical-align : middle; + white-space: nowrap; + border-radius : 4px; + } -img.markedPic, img.pubPic { - cursor : pointer; - vertical-align : middle; - opacity : 0.5; - transition : opacity 0.25s; -} + img.markedPic, img.pubPic { + cursor : pointer; + vertical-align : middle; + opacity : 0.5; + transition : opacity 0.25s; + } -img.markedPic:hover, img.pubPic:hover { - opacity : 1; -} + img.markedPic:hover, img.pubPic:hover { + opacity : 1; + } -img[src*='pub_set.png'], img[src*='mark_set.png'] { - opacity : 1; -} + img[src*='pub_set.png'], img[src*='mark_set.png'] { + opacity : 1; + } -div.tagCloudContainer { - border : 1px solid #ddd; - margin : 5px 0px 5px 0px; - padding : 5px; - text-align : center; -} + div.tagCloudContainer { + border : 1px solid #ddd; + margin : 5px 0px 5px 0px; + padding : 5px; + text-align : center; + } -div.errorExplained { - border : 1px solid #ddd; - margin : 5px 0px 5px 0px; - padding : 5px; -} + div.errorExplained { + border : 1px solid #ddd; + margin : 5px 0px 5px 0px; + padding : 5px; + } -ul.feedErrorsList { - max-height : 300px; - overflow : auto; - list-style-type : none; - border : 1px solid #ddd; - margin : 0px 0px 5px 0px; - padding : 5px; + ul.feedErrorsList { + max-height : 300px; + overflow : auto; + list-style-type : none; + border : 1px solid #ddd; + margin : 0px 0px 5px 0px; + padding : 5px; - em { - color : #555; + em { + color : @default-text; + } } -} -ul.browseFeedList { - height : 300px; - overflow : auto; - border-width : 0px 1px 1px 1px; - border-color : #ddd; - border-style : solid; - margin : 0px 0px 5px 0px; - background-color : white; - list-style-type : none; - padding : 0px; + ul.browseFeedList { + height : 300px; + overflow : auto; + border-width : 0px 1px 1px 1px; + border-color : #ddd; + border-style : solid; + margin : 0px 0px 5px 0px; + background-color : white; + list-style-type : none; + padding : 0px; - li { - margin : 0px; - padding : 2px 4px 2px 4px; + li { + margin : 0px; + padding : 2px 4px 2px 4px; + } } -} -.browseFeedList span.subscribers { - color : #808080; -} - -ul.compact { - list-style-type : none; - margin : 0px; - padding : 0px; + .browseFeedList span.subscribers { + color : #808080; + } - li { + ul.compact { + list-style-type : none; margin : 0px; padding : 0px; - } -} - -.noborder { - border-width : 0px; -} - -#overlay { - background : white; - left : 0; - top : 0; - height : 100%; - width : 100%; - z-index : 100; - position : absolute; -} -#overlay_inner { - font-weight : bold; - margin : 1em; -} - -form { - margin : 0px; - padding : 0px; -} + li { + margin : 0px; + padding : 0px; + } + } -div.loadingPrompt { - padding : 1em; - text-align : center; - font-weight : bold; -} + .noborder { + border-width : 0px; + } -div.whiteBox { - margin-left : 1px; - text-align : center; - padding : 1em 1em 0px 1em; - font-size : 11px; - border: 0px solid #ddd; - border-bottom-width: 1px; -} + #overlay { + background : white; + left : 0; + top : 0; + height : 100%; + width : 100%; + z-index : 100; + position : absolute; + } -div.autocomplete { - position : absolute; - width : 250px; - background-color : white; - border :1px solid #778899; - margin : 0px; - padding : 0px; + #overlay_inner { + font-weight : bold; + margin : 1em; + } - ul { - list-style-type : none; + form { margin : 0px; padding : 0px; } - ul li.selected { - background-color : #fff7d5; + div.loadingPrompt { + padding : 1em; + text-align : center; + font-weight : bold; } - ul li { - list-style-type : none; - display : block; - margin : 0; - padding : 2px; - height : 32px; - cursor : pointer; + div.whiteBox { + margin-left : 1px; + text-align : center; + padding : 1em 1em 0px 1em; + font-size : 11px; + border: 0px solid #ddd; + border-bottom-width: 1px; } -} + div.autocomplete { + position : absolute; + width : 250px; + background-color : white; + border :1px solid #778899; + margin : 0px; + padding : 0px; -div#headlines-frame.wide .hlTitle { - max-width : none; - overflow : visible; - white-space : normal; -} - -div#headlines-frame.wide .hl .hlFeed { - display : none; -} + ul { + list-style-type : none; + margin : 0px; + padding : 0px; + } -img.hlScorePic { - vertical-align : middle; - width : 16px; - height : 16px; -} + ul li.selected { + background-color : #fff7d5; + } -div.dlgSec { - font-size : 12px; - color : #555; - font-weight : bold; - clear : both; - height : 20px; -} + ul li { + list-style-type : none; + display : block; + margin : 0; + padding : 2px; + height : 32px; + cursor : pointer; + } + } -div.dlgSecCont { - position : relative; - left : 150px; - top : -20px; - float : left; - font-size : 12px; - font-weight : normal; - > * { - position : relative; - top : -2px; + div#headlines-frame.wide .hlTitle { + max-width : none; + overflow : visible; + white-space : normal; } -} - -div.dlgSecCont hr, div.dlgSecSimple hr { - height : 0px; - line-height : 0px; - border : 0px solid transparent; - margin : 2px; -} -div.dlgButtons { - text-align : right; - clear : both; -} + div#headlines-frame.wide .hl .hlFeed { + display : none; + } -span.labelColorIndicator { - height : 16px; - width : 16px; - border-radius : 4px; - line-height : 14px; - vertical-align : middle; - font-size : 9px; - display : inline-block; - border : 1px solid #ccc; - background-color : #fff7d5; - color : #063064; - text-align : center; -} + img.hlScorePic { + vertical-align : middle; + width : 16px; + height : 16px; + } -div#cmdline { - position : absolute; - left : 5px; - bottom : 5px; - font-size : 11px; - color : #555; - font-weight : bold; - background-color : white; - border : 1px solid @color-accent; - padding : 3px 5px 3px 5px; - z-index : 5; -} + div.dlgSec { + font-size : 12px; + color : @default-text; + font-weight : bold; + clear : both; + height : 20px; + } -#feed_browser_spinner { - vertical-align : middle; - height : 18px; - width : 18px; -} + div.dlgSecCont { + position : relative; + left : 150px; + top : -20px; + float : left; + font-size : 12px; + font-weight : normal; -div.fatalError { - margin-bottom : 10px; + > * { + position : relative; + top : -2px; + } + } - button { - margin-top : 5px; + div.dlgSecCont hr, div.dlgSecSimple hr { + height : 0px; + line-height : 0px; + border : 0px solid transparent; + margin : 2px; } - textarea { - width : 565px; - height : 200px; + div.dlgButtons { + text-align : right; + clear : both; } -} -#ttrssMain #main { - border-width : 0px; - margin : 0px; - padding : 0px; -} + span.labelColorIndicator { + height : 16px; + width : 16px; + border-radius : 4px; + line-height : 14px; + vertical-align : middle; + font-size : 9px; + display : inline-block; + border : 1px solid #ccc; + background-color : #fff7d5; + color : #063064; + text-align : center; + } -#header-wrap { - border-width : 0px; - margin : 0px; - padding : 0px; -} + div#cmdline { + position : absolute; + left : 5px; + bottom : 5px; + font-size : 11px; + color : @default-text; + font-weight : bold; + background-color : white; + border : 1px solid @color-accent; + padding : 3px 5px 3px 5px; + z-index : 5; + } -#content-wrap { - padding : 0px; - border-width : 0px; - margin : 0px; -} + #feed_browser_spinner { + vertical-align : middle; + height : 18px; + width : 18px; + } -#feeds-holder { - padding : 0px; - border: 0px solid #ddd; - overflow : hidden; - background : #f5f5f5; - box-shadow : inset -1px 0px 2px -1px rgba(0,0,0,0.1); - -webkit-overflow-scrolling : touch; -} + div.fatalError { + margin-bottom : 10px; -#headlines-wrap-inner { - padding : 0px; - margin : 0px; - border-width : 0px; -} + button { + margin-top : 5px; + } -#headlines-frame { - padding : 0px; - border: 0px #ddd; - margin-top : 0px; - -webkit-overflow-scrolling : touch; - -webkit-transform: translateZ(0); - -webkit-backface-visibility: hidden; -} + textarea { + width : 565px; + height : 200px; + } + } -#headlines-toolbar_splitter, #toolbar_splitter { - display : none; -} + #header-wrap { + border-width : 0px; + margin : 0px; + padding : 0px; + } -#toolbar { - padding : 0px; - margin : 0px; - border-width : 0px; - white-space: nowrap; - font-size : 12px; -} + #content-wrap { + padding : 0px; + border-width : 0px; + margin : 0px; + } -#main-toolbar { - background : white; - border: 0px solid #ddd; - border-bottom-width: 1px; - padding-left : 4px; - height : 26px; + #feeds-holder { + padding : 0px; + border: 0px solid #ddd; + overflow : hidden; + background : #f5f5f5; + box-shadow : inset -1px 0px 2px -1px rgba(0,0,0,0.1); + -webkit-overflow-scrolling : touch; -} + #feedTree .dijitTreeRow .dijitTreeLabel.Unread { + font-weight : bold; + } -#header { - border-width : 0px; - text-align : right; - color : #555; - padding : 5px 5px 0px 0px; - margin : 0px; - position : absolute; - right : 0px; - top : 0px; - z-index : 5; -} + #feedTree .dijitTreeRow.Error .dijitTreeLabel { + color : red; + } -#footer { - text-align : center; - color : #555; - padding : 4px 4px 8px 4px; - border-width : 0px; -} + #feedTree.dijitTree .dijitTreeNode .dijitTreeRowSelected { + box-shadow : -1px 0px 2px -1px rgba(0,0,0,0.1); + border-right-color : white; + } -#content-insert { - padding : 0px; - border-color : #ddd; - border-width : 0px; - line-height: 1.5; - font-size : 15px; - overflow : auto; - -webkit-overflow-scrolling : touch; -} + #feedTree.dijitTree .dijitTreeContainer { + max-width : 100%; + } -#feedTree .dijitTreeRow .dijitTreeLabel.Unread { - font-weight : bold; -} + #feedTree.dijitTree .dijitTreeRow { + overflow: hidden; + text-overflow: ellipsis; + } -#feedTree .dijitTreeRow.Error .dijitTreeLabel { - color : red; -} + #feedTree.dijitTree .dijitTreeNode .dijitTreeRow { + padding : 4px 0px 4px; + border-width : 1px; + color : #333; + } -img.feedIcon, img.tinyFeedIcon { - width : 16px; - height : 16px; - line-height : 16px; - vertical-align : middle; - display : inline-block; -} + #feedTree.dijitTree img.tinyFeedIcon { + position : relative; + top : -2px; + } -.player { - display : inline-block; - color : #555; - font-size : 11px; - font-family : sans-serif; - border : 1px solid #555; - padding : 0px 4px 0px 4px; - margin : 0px 2px 0px 2px; - width : 50px; - text-align : center; - background : white; -} + #feedTree { + height : 100%; + overflow-x : hidden; + text-rendering: optimizelegibility; + font-family : @fonts-ui; + + .counterNode.aux { + background : #f0f0f0; + color : #999; + border-color : #f0f0f0; + } + + .counterNode { + font-weight : bold; + display : inline-block; + font-size : 9px; + text-align : center; + border : 1px solid lighten(@color-accent, 5%);; + color : white; + background : lighten(@color-accent, 5%); + border-radius : 4px; + vertical-align : middle; + float : right; + position : relative; + line-height : 14px; + margin-right : 8px; + margin-top : 2px; + min-width : 23px; + height : 14px; + } + + .dijitTreeNode .loadingExpando { + left : -3px; + height : 22px; + position : relative; + top : -3px; + } -.player.playing { - color : #00c000; - border-color : #00c000; -} + } + } -.player:hover { - background : #f0f0f0; - cursor : pointer; -} + #headlines-wrap-inner { + padding : 0px; + margin : 0px; + border-width : 0px; + } -#headlines-spacer { - height : 100%; - margin-left : 1px; - text-align : center; - color : #555; - font-size : 11px; - font-style : italic; + #headlines-frame { + padding : 0px; + border: 0px #ddd; + margin-top : 0px; + -webkit-overflow-scrolling : touch; + -webkit-transform: translateZ(0); + -webkit-backface-visibility: hidden; + } - a, span { - color : #555; - padding : 10px; - display : block; + #headlines-toolbar_splitter, #toolbar_splitter { + display : none; } - a:hover { - color : @color-accent; + #toolbar { + padding : 0px; + margin : 0px; + border-width : 0px; + white-space: nowrap; + font-size : 12px; } -} -ul#filterDlg_Matches, ul#filterDlg_Actions { - max-height : 100px; - overflow : auto; - list-style-type : none; - border-style : solid; - border-color : #ddd; - border-width : 0px 1px 1px 1px; - background-color : white; - margin : 0px 0px 5px 0px; - padding : 0px; -} + #main-toolbar { + background : white; + border: 0px solid #ddd; + border-bottom-width: 1px; + padding-left : 4px; + height : 26px; -ul#filterDlg_Matches li, ul#filterDlg_Actions li { - cursor : pointer; - padding : 0px 0px 0px 5px; -} + } -ul.helpKbList { - max-height : 300px; - overflow : auto; - list-style-type : none; - border : 1px solid #ddd; - margin : 0px 0px 5px 0px; - padding : 5px; - - span.hksequence { - width : 6em; - margin-left : 20px; - color : @color-accent; - font-weight : bold; - display : inline-block; + #header { + border-width : 0px; + text-align : right; + color : @default-text; + padding : 5px 5px 0px 0px; + margin : 0px; + position : absolute; + right : 0px; + top : 0px; + z-index : 5; } - h2 { - margin-top : 0px; + #footer { + text-align : center; + color : @default-text; + padding : 4px 4px 8px 4px; + border-width : 0px; } -} -span.collapseBtn { - cursor : pointer; + #content-insert { + padding : 0px; + border-color : #ddd; + border-width : 0px; + line-height: 1.5; + font-size : 15px; + overflow : auto; + -webkit-overflow-scrolling : touch; + } - img { + img.feedIcon, img.tinyFeedIcon { + width : 16px; + height : 16px; + line-height : 16px; vertical-align : middle; + display : inline-block; } -} -select.attachments { - display : block; - margin-top : 10px; - max-width : 120px; -} + .player { + display : inline-block; + color : @default-text; + font-size : 11px; + font-family : sans-serif; + border : 1px solid @default-text; + padding : 0px 4px 0px 4px; + margin : 0px 2px 0px 2px; + width : 50px; + text-align : center; + background : white; + } -#selected_prompt { - margin-right : 25px; - vertical-align : middle; -} + .player.playing { + color : #00c000; + border-color : #00c000; + } -body#ttrssMain.claro #feedTree.dijitTree .dijitTreeNode .dijitTreeRowSelected { - box-shadow : -1px 0px 2px -1px rgba(0,0,0,0.1); - border-right-color : white; -} + .player:hover { + background : #f0f0f0; + cursor : pointer; + } -body#ttrssMain #feedTree.dijitTree .dijitTreeContainer { - max-width : 100%; -} + #headlines-spacer { + height : 100%; + margin-left : 1px; + text-align : center; + color : @default-text; + font-size : 11px; + font-style : italic; -body#ttrssMain #feedTree.dijitTree .dijitTreeRow { - overflow: hidden; - text-overflow: ellipsis; -} + a, span { + color : @default-text; + padding : 10px; + display : block; + } -body#ttrssMain #feedTree.dijitTree .dijitTreeNode .dijitTreeRow { - padding : 4px 0px 4px; - border-width : 1px; - color : #333; -} + a:hover { + color : @color-accent; + } + } -body#ttrssMain #feedTree.dijitTree img.tinyFeedIcon { - position : relative; - top : -2px; -} + ul#filterDlg_Matches, ul#filterDlg_Actions { + max-height : 100px; + overflow : auto; + list-style-type : none; + border-style : solid; + border-color : #ddd; + border-width : 0px 1px 1px 1px; + background-color : white; + margin : 0px 0px 5px 0px; + padding : 0px; + } -#filterDlg_feeds select { - height : 150px; - width : 410px; -} + ul#filterDlg_Matches li, ul#filterDlg_Actions li { + cursor : pointer; + padding : 0px 0px 0px 5px; + } -ul#filterDlg_Matches li div.dijitCheckBox, ul#filterDlg_Actions li div.dijitCheckBox { - margin-right : 5px; -} + ul.helpKbList { + max-height : 300px; + overflow : auto; + list-style-type : none; + border : 1px solid #ddd; + margin : 0px 0px 5px 0px; + padding : 5px; -body#ttrssMain #feedTree { - height : 100%; - overflow-x : hidden; - text-rendering: optimizelegibility; - font-family : @fonts-ui; + span.hksequence { + width : 6em; + margin-left : 20px; + color : @color-accent; + font-weight : bold; + display : inline-block; + } - .counterNode.aux { - background : #f0f0f0; - color : #999; - border-color : #f0f0f0; + h2 { + margin-top : 0px; + } } - .counterNode { - font-weight : bold; - display : inline-block; - font-size : 9px; - text-align : center; - border : 1px solid @color-accent; - color : white; - background : @color-accent; - border-radius : 4px; - vertical-align : middle; - float : right; - position : relative; - line-height : 14px; - margin-right : 8px; - margin-top : 2px; - min-width : 23px; - height : 14px; - } + span.collapseBtn { + cursor : pointer; - .dijitTreeNode .loadingExpando { - left : -3px; - height : 22px; - position : relative; - top : -3px; + img { + vertical-align : middle; + } } -} - -span.highlight { - background-color : #ffff00; - color : #cc90cc; -} - -div.enclosure_title { - -} + select.attachments { + display : block; + margin-top : 10px; + max-width : 120px; + } -body#ttrssMain #headlines-frame .dijitCheckBox { - border-width : 0px; - opacity : 0.5; -} + #selected_prompt { + margin-right : 25px; + vertical-align : middle; + } -body#ttrssMain #headlines-frame .dijitCheckBoxHover, -body#ttrssMain #headlines-frame .dijitCheckBoxChecked { - opacity : 1; -} + #filterDlg_feeds select { + height : 150px; + width : 410px; + } -body#ttrssMain #feedTree .dijitTreeRow img.dijitTreeExpandoLeaf { - width : 16px; - height : 16px; - vertical-align : middle; - position : relative; -} + ul#filterDlg_Matches li div.dijitCheckBox, ul#filterDlg_Actions li div.dijitCheckBox { + margin-right : 5px; + } -.dijitDropDownButton.attachments .dijitButtonText { - font-size : 12px; -} + span.highlight { + background-color : #ffff00; + color : #cc90cc; + } -.dijitDropDownButton.attachments { - display : inline-block; -} + div.enclosure_title { -#editTagsDlg{ - overflow: visible; -} + } -body#ttrssZoom { - margin-left : auto; - margin-right : auto; - padding : 20px; - max-width : 770px; - background : #f5f5f5; + #headlines-frame .dijitCheckBox { + border-width : 0px; + opacity : 0.5; + } - div.postHeader div.postFeedTitle { - float : left; - text-align : right; - padding-left : 0px; - font-size : 11px; + #headlines-frame .dijitCheckBoxHover, + #headlines-frame .dijitCheckBoxChecked { + opacity : 1; } - div.postHeader a.postComments { - text-align : right; - padding-left : 0px; - font-size : 11px; + #feedTree .dijitTreeRow img.dijitTreeExpandoLeaf { + width : 16px; + height : 16px; + vertical-align : middle; + position : relative; } - div.postHeader div.postDate { - float : none; - text-align : right; - padding-left : 0px; - color : #777; - font-size : 11px; + .dijitDropDownButton.attachments .dijitButtonText { + font-size : 12px; } - div.postHeader div.postTags { - color : #777; - font-size : 11px; + .dijitDropDownButton.attachments { + display : inline-block; } - div.postHeader div.postTitle { - white-space : normal; - font-size : 16px; + #editTagsDlg{ + overflow: visible; } - div.postContent { - font-size : 15px; - line-height : 1.5; + #feedEditDlg img.feedIcon { + border : 1px solid #ccc; + padding : 5px; + margin : 5px; + max-width : 20px; + max-height : 20px; + height : auto; + width : auto; } +} - div.postContent p { - -webkit-hyphens: auto; - -moz-hyphens: auto; - hyphens: auto; +body.ttrss_login { + padding : 2em; + font-size : 14px; + + fieldset { + margin-left : auto; + margin-right : auto; + display : block; + width : 400px; + border-width : 0px; } - div.postHeader { - margin : 10px; - border-width : 0px 0px 1px 0px; - border-style : solid; - border-color : #eee; - background : white; + label { + width : 120px; + margin-right : 20px; + display : inline-block; + text-align : right; + color : gray; } - div.postReply { - border : 1px solid #ddd; - background : white; - box-shadow : 0px 1px 1px -1px rgba(0,0,0,0.1); - border-radius : 6px; + div.header { + border: 0px solid @color-accent; + border-bottom-width: 1px; + margin-bottom : 1em; + padding-bottom : 5px; } div.footer { margin-top : 1em; + padding-top : 5px; + border: 0px solid @color-accent; + border-top-width: 1px; text-align : center; + color : gray; + font-size : 12px; } - div.postContent img { - max-width : 730px; - height : auto; + a.forgotpass { + text-align : right; + font-size : 11px; + display : inline-block; } - div.postContent blockquote { - margin : 5px 0px 5px 0px; - color : #555; - padding-left : 10px; - border-width : 0px 0px 0px 4px; - border-color : #ccc; - border-style : solid; + a { + color: @color-accent; + text-decoration: none; } - div.postContent code { - color : #009900; - font-family : monospace; - font-size : 12px; + a:hover, + a:focus { + color: @color-accent; + text-decoration: underline; } - div.postContent pre { - margin : 5px 0px 5px 0px; - padding : 10px; - color : #555; - font-family : monospace; - font-size : 12px; - border-width : 0px; - border-color : #ccc; - border-style : solid; - background : #f5f5f5; - display : block; - max-width : 98%; - overflow : auto; + div.footer a { + color : gray; } + div.footer a:hover { + color : @color-accent; + } + div.row { + padding : 0px 0px 5px 0px; + } + + div.row-error { + color : red; + text-align : center; + padding : 0px 0px 5px 0px; + } } diff --git a/css/utility.css b/css/utility.css deleted file mode 100644 index 4afdb067a..000000000 --- a/css/utility.css +++ /dev/null @@ -1,290 +0,0 @@ -body.sanity_failed { - background : #900; -} - -body { - background : #f5f5f5; - color : black; - padding : 0px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - margin-left : auto; - margin-right : auto; - max-width : 800px; -} - -body.small_margins { - margin : 1em; - max-width : none; -} - -form { - margin : 10px 0px 0px 0px; - padding : 0px; -} - -div.content { - overflow : hidden; - background : white; - border : 1px solid #ddd; - padding : 10px; - border-radius : 6px; - box-shadow : 0px 1px 1px -1px rgba(0,0,0,0.1); -} - -p.warning { - color : red; -} - -p.query, code { - color : green; -} - -p.insensitive { - color : gray; -} - -div.insensitive-small { - color : gray; - font-size : 10px; -} - -.floatingLogo { - display : none; -} - -a { - color : rgb(82, 168, 236); - text-decoration : none; -} - -a:hover { - color : black; -} - -.alert { - padding: 8px 35px 8px 14px; - margin-bottom: 20px; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - background-color: #fcf8e3; - border: 1px solid #fbeed5; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.alert, -.alert h4 { - color: #c09853; -} - -.alert h4 { - margin: 0; -} - -.alert .close { - position: relative; - top: -2px; - right: -21px; - line-height: 20px; - cursor : pointer; -} - -.alert-success { - color: #468847; - background-color: #dff0d8; - border-color: #d6e9c6; -} - -.alert-success h4 { - color: #468847; -} - -.alert-danger, -.alert-error { - color: #b94a48; - background-color: #f2dede; - border-color: #eed3d7; -} - -.alert-danger h4, -.alert-error h4 { - color: #b94a48; -} - -.alert-info { - color: #3a87ad; - background-color: #d9edf7; - border-color: #bce8f1; -} - -.alert-info h4 { - color: #3a87ad; -} - -h1 { - color : rgb(82, 168, 236); - font-size : 32px; - margin : 20px 0px 5px 0px; - text-shadow : 0 0 6px #fff; -} - -h2 { - color : rgb(82, 168, 236); - font-size : 14pt; - border-width : 0px 0px 1px 0px; - border-color : #f0f0f0; - border-style : solid; -} - -div.content > h2 { - margin-top : 0px; -} - -div.rss h1 { - border-width : 0px 0px 1px 0px; - border-color : gray; - border-style : dotted; - color : gray; -} - -div.rss h2 { - font-size : 12pt; -} - -div.rss a.extlink { - color : gray; - border-width : 0px 0px 1px 0px; - border-color : #778899; - border-style : dotted; - font-size : 9pt; -} - -div.rss img { - max-width : 775px; -} - -div.rss p.description { - color : gray; - font-size : 9pt; -} - -div.rss div.content { - margin-top : 0.5em; -} - -div.rss img.feedicon { - float : right; -} - -div.rss hr { - border-width : 0px 0px 1px 0px; - border-style : dashed; - border-color : #e0e0e0; -} - -body#sharepopup { - background-color : white; - background-image : url("../images/toolbar.png"); - background-repeat : repeat-x; - background-position : bottom; - margin : 10px; - padding : 0px; -} - -body#sharepopup h1 { - font-size : 14px; - margin : 0px; - color : rgb(82, 168, 236); -} - -body#sharepopup table { - background : white; - border : 1px solid rgb(82, 168, 236); - padding : 5px; -} - -body#sharepopup form { - height : 100%; -} - -body#sharepopup input { - width : 100%; -} - -div.autocomplete { - position : absolute; - width : 250px; - background-color : white; - border :1px solid #778899; - margin : 0px; - padding : 0px; - z-index : 4; -} - -div.autocomplete ul { - list-style-type : none; - margin : 0px; - padding : 0px; - font-size : 10px; -} - -div.autocomplete ul li.selected { - background-color : #fff7d5; -} - -div.autocomplete ul li { - list-style-type : none; - display : block; - margin : 0; - padding : 2px; - height : 32px; - cursor : pointer; -} - -fieldset { - border-width : 0px; - padding : 0px 0px 5px 0px; - margin : 0px; -} - -fieldset input { - font-family : sans-serif; - font-size : medium; - border-spacing : 2px; - border : 1px solid #b5bcc7; - padding : 2px; -} - -fieldset label { - width : 120px; - margin-right : 20px; - display : inline-block; - text-align : right; - color : gray; -} - -body.otp { - margin : 1em; - padding : 0px; -} - -form.otpform { - margin : 0px; - padding : 0px; -} - -form.otpform label { - margin : 0px; - padding : 0px; -} - -body.otp div.content { - display : inline-block; - width : auto; -} - -span.hint { - font-size : 10px; - color : gray; -} diff --git a/css/utility.less b/css/utility.less new file mode 100644 index 000000000..d574086c8 --- /dev/null +++ b/css/utility.less @@ -0,0 +1,288 @@ +body.ttrss_utility.sanity_failed { + background : #900; +} + +body.ttrss_utility { + background : #f5f5f5; + color : black; + padding : 0px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + margin-left : auto; + margin-right : auto; + max-width : 800px; + + form { + margin : 10px 0px 0px 0px; + padding : 0px; + } + + div.content { + overflow : hidden; + background : white; + border : 1px solid #ddd; + padding : 10px; + border-radius : 6px; + box-shadow : 0px 1px 1px -1px rgba(0,0,0,0.1); + } + + p.warning { + color : red; + } + + p.query, code { + color : green; + } + + p.insensitive { + color : gray; + } + + div.insensitive-small { + color : gray; + font-size : 10px; + } + + .floatingLogo { + display : none; + } + + a { + color : rgb(82, 168, 236); + text-decoration : none; + } + + a:hover { + color : black; + } + + .alert { + padding: 8px 35px 8px 14px; + margin-bottom: 20px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + background-color: #fcf8e3; + border: 1px solid #fbeed5; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + } + + .alert, + .alert h4 { + color: #c09853; + } + + .alert h4 { + margin: 0; + } + + .alert .close { + position: relative; + top: -2px; + right: -21px; + line-height: 20px; + cursor : pointer; + } + + .alert-success { + color: #468847; + background-color: #dff0d8; + border-color: #d6e9c6; + } + + .alert-success h4 { + color: #468847; + } + + .alert-danger, + .alert-error { + color: #b94a48; + background-color: #f2dede; + border-color: #eed3d7; + } + + .alert-danger h4, + .alert-error h4 { + color: #b94a48; + } + + .alert-info { + color: #3a87ad; + background-color: #d9edf7; + border-color: #bce8f1; + } + + .alert-info h4 { + color: #3a87ad; + } + + h1 { + color : rgb(82, 168, 236); + font-size : 32px; + margin : 20px 0px 5px 0px; + text-shadow : 0 0 6px #fff; + } + + h2 { + color : rgb(82, 168, 236); + font-size : 14pt; + border-width : 0px 0px 1px 0px; + border-color : #f0f0f0; + border-style : solid; + } + + div.content > h2 { + margin-top : 0px; + } + + div.rss h1 { + border-width : 0px 0px 1px 0px; + border-color : gray; + border-style : dotted; + color : gray; + } + + div.rss h2 { + font-size : 12pt; + } + + div.rss a.extlink { + color : gray; + border-width : 0px 0px 1px 0px; + border-color : #778899; + border-style : dotted; + font-size : 9pt; + } + + div.rss img { + max-width : 775px; + } + + div.rss p.description { + color : gray; + font-size : 9pt; + } + + div.rss div.content { + margin-top : 0.5em; + } + + div.rss img.feedicon { + float : right; + } + + div.rss hr { + border-width : 0px 0px 1px 0px; + border-style : dashed; + border-color : #e0e0e0; + } + + div.autocomplete { + position : absolute; + width : 250px; + background-color : white; + border :1px solid #778899; + margin : 0px; + padding : 0px; + z-index : 4; + } + + div.autocomplete ul { + list-style-type : none; + margin : 0px; + padding : 0px; + font-size : 10px; + } + + div.autocomplete ul li.selected { + background-color : #fff7d5; + } + + div.autocomplete ul li { + list-style-type : none; + display : block; + margin : 0; + padding : 2px; + height : 32px; + cursor : pointer; + } + + fieldset { + border-width : 0px; + padding : 0px 0px 5px 0px; + margin : 0px; + } + + fieldset input { + font-family : sans-serif; + font-size : medium; + border-spacing : 2px; + border : 1px solid #b5bcc7; + padding : 2px; + } + + fieldset label { + width : 120px; + margin-right : 20px; + display : inline-block; + text-align : right; + color : gray; + } + + body.otp { + margin : 1em; + padding : 0px; + } + + form.otpform { + margin : 0px; + padding : 0px; + } + + form.otpform label { + margin : 0px; + padding : 0px; + } + + body.otp div.content { + display : inline-block; + width : auto; + } + + span.hint { + font-size : 10px; + color : gray; + } +} + +body.small_margins { + margin : 1em; + max-width : none; +} + +body#sharepopup { + background: white url("../images/toolbar.png") repeat-x bottom; + margin : 10px; + padding : 0px; + + h1 { + font-size : 14px; + margin : 0px; + color : rgb(82, 168, 236); + } + + table { + background : white; + border : 1px solid rgb(82, 168, 236); + padding : 5px; + } + + form { + height : 100%; + } + + input { + width : 100%; + } + +} diff --git a/css/zoom.css b/css/zoom.css deleted file mode 100644 index 9d9e8948a..000000000 --- a/css/zoom.css +++ /dev/null @@ -1,105 +0,0 @@ -body#ttrssZoom { - margin-left : auto; - margin-right : auto; - padding : 20px; - max-width : 770px; - background : #f5f5f5; -} - -body#ttrssZoom div.postHeader div.postFeedTitle { - float : left; - text-align : right; - padding-left : 0px; - font-size : 11px; -} - -body#ttrssZoom div.postHeader a.postComments { - text-align : right; - padding-left : 0px; - font-size : 11px; -} - -body#ttrssZoom div.postHeader div.postDate { - float : none; - text-align : right; - padding-left : 0px; - color : #777; - font-size : 11px; -} - -body#ttrssZoom div.postHeader div.postTags { - color : #777; - font-size : 11px; -} - -body#ttrssZoom div.postHeader div.postTitle { - white-space : normal; - font-size : 16px; -} - -body#ttrssZoom div.postContent { - font-size : 15px; - line-height : 1.5; -} - -body#ttrssZoom div.postContent p { - -webkit-hyphens: auto; - -moz-hyphens: auto; - hyphens: auto; -} - -body#ttrssZoom div.postHeader { - margin : 10px; - border-width : 0px 0px 1px 0px; - border-style : solid; - border-color : #eee; - background : white; -} - -body#ttrssZoom div.postReply { - border : 1px solid #ddd; - background : white; - box-shadow : 0px 1px 1px -1px rgba(0,0,0,0.1); - border-radius : 6px; -} - -body#ttrssZoom div.footer { - margin-top : 1em; - text-align : center; -} - -body#ttrssZoom div.postContent img { - max-width : 730px; - height : auto; -} - -body#ttrssZoom div.postContent blockquote { - margin : 5px 0px 5px 0px; - color : #555; - padding-left : 10px; - border-width : 0px 0px 0px 4px; - border-color : #ccc; - border-style : solid; -} - -body#ttrssZoom div.postContent code { - color : #009900; - font-family : monospace; - font-size : 12px; -} - -body#ttrssZoom div.postContent pre { - margin : 5px 0px 5px 0px; - padding : 10px; - color : #555; - font-family : monospace; - font-size : 12px; - border-width : 0px; - border-color : #ccc; - border-style : solid; - background : #f5f5f5; - display : block; - max-width : 98%; - overflow : auto; -} - diff --git a/css/zoom.less b/css/zoom.less new file mode 100644 index 000000000..18d80f922 --- /dev/null +++ b/css/zoom.less @@ -0,0 +1,105 @@ +body.ttrss_zoom { + margin-left : auto; + margin-right : auto; + padding : 20px; + max-width : 770px; + background : #f5f5f5; + + div.postHeader { + margin : 10px; + padding-bottom : 10px; + border: 0px solid #eee; + border-bottom-width: 1px; + background : white; + + div.postFeedTitle { + float : left; + text-align : right; + padding-left : 0px; + font-size : 11px; + } + + a.postComments { + text-align : right; + padding-left : 0px; + font-size : 11px; + } + + div.postDate { + float : none; + text-align : right; + padding-left : 0px; + color : #777; + font-size : 11px; + } + + div.postTags { + color : #777; + font-size : 11px; + } + + div.postTitle { + white-space : normal; + font-size : 16px; + } + + } + + p { + -webkit-hyphens: auto; + -moz-hyphens: auto; + hyphens: auto; + } + + div.postReply { + border : 1px solid #ddd; + background : white; + box-shadow : 0px 1px 1px -1px rgba(0,0,0,0.1); + border-radius : 6px; + } + + div.footer { + margin-top : 1em; + text-align : center; + } + + + div.postContent { + font-size : 15px; + line-height : 1.5; + padding : 10px; + + img { + max-width : 730px; + height : auto; + } + + blockquote { + margin : 5px 0px 5px 0px; + color : @default-text; + padding-left : 10px; + border: 0px solid #ccc; + border-left-width: 4px; + } + + code { + color : #009900; + font-family : monospace; + font-size : 12px; + } + + pre { + margin : 5px 0px 5px 0px; + padding : 10px; + color : @default-text; + font-family : monospace; + font-size : 12px; + border: 0px solid #ccc; + background : #f5f5f5; + display : block; + max-width : 98%; + overflow : auto; + } + } +} + diff --git a/include/controls.php b/include/controls.php index c3dbbfe18..931ccdd52 100644 --- a/include/controls.php +++ b/include/controls.php @@ -72,7 +72,9 @@ function print_radio($id, $default, $true_is, $values, $attributes = "") { function print_feed_multi_select($id, $default_ids = [], $attributes = "", $include_all_feeds = true, - $root_id = false, $nest_level = 0) { + $root_id = null, $nest_level = 0) { + + $pdo = DB::pdo(); print_r(in_array("CAT:6",$default_ids)); @@ -86,18 +88,18 @@ function print_feed_multi_select($id, $default_ids = [], if (get_pref('ENABLE_FEED_CATS')) { - if ($root_id) - $parent_qpart = "parent_cat = '$root_id'"; - else - $parent_qpart = "parent_cat IS NULL"; + if (!$root_id) $root_id = null; - $result = db_query("SELECT id,title, + $sth = $pdo->prepare("SELECT id,title, (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE c2.parent_cat = ttrss_feed_categories.id) AS num_children FROM ttrss_feed_categories - WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title"); + WHERE owner_uid = :uid AND + (parent_cat = :root_id OR (:root_id IS NULL AND parent_cat IS NULL)) ORDER BY title"); + + $sth->execute([":uid" => $_SESSION['uid'], ":root_id" => $root_id]); - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { for ($i = 0; $i < $nest_level; $i++) $line["title"] = " - " . $line["title"]; @@ -111,10 +113,12 @@ function print_feed_multi_select($id, $default_ids = [], print_feed_multi_select($id, $default_ids, $attributes, $include_all_feeds, $line["id"], $nest_level+1); - $feed_result = db_query("SELECT id,title FROM ttrss_feeds - WHERE cat_id = '".$line["id"]."' AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title"); + $f_sth = $pdo->prepare("SELECT id,title FROM ttrss_feeds + WHERE cat_id = ? AND owner_uid = ? ORDER BY title"); - while ($fline = db_fetch_assoc($feed_result)) { + $f_sth->execute([$line['id'], $_SESSION['uid']]); + + while ($fline = $f_sth->fetch()) { $is_selected = (in_array($fline["id"], $default_ids)) ? "selected=\"1\"" : ""; $fline["title"] = " + " . $fline["title"]; @@ -133,10 +137,11 @@ function print_feed_multi_select($id, $default_ids = [], printf("<option $is_selected value='CAT:0'>%s</option>", __("Uncategorized")); - $feed_result = db_query("SELECT id,title FROM ttrss_feeds - WHERE cat_id IS NULL AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title"); + $f_sth = $pdo->prepare("SELECT id,title FROM ttrss_feeds + WHERE cat_id IS NULL AND owner_uid = ? ORDER BY title"); + $f_sth->execute([$_SESSION['uid']]); - while ($fline = db_fetch_assoc($feed_result)) { + while ($fline = $f_sth->fetch()) { $is_selected = in_array($fline["id"], $default_ids) ? "selected=\"1\"" : ""; $fline["title"] = " + " . $fline["title"]; @@ -150,10 +155,11 @@ function print_feed_multi_select($id, $default_ids = [], } } else { - $result = db_query("SELECT id,title FROM ttrss_feeds - WHERE owner_uid = ".$_SESSION["uid"]." ORDER BY title"); + $sth = $pdo->prepare("SELECT id,title FROM ttrss_feeds + WHERE owner_uid = ? ORDER BY title"); + $sth->execute([$_SESSION['uid']]); - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { $is_selected = (in_array($line["id"], $default_ids)) ? "selected=\"1\"" : ""; @@ -167,122 +173,30 @@ function print_feed_multi_select($id, $default_ids = [], } } - -/*function print_feed_select($id, $default_id = "", - $attributes = "", $include_all_feeds = true, - $root_id = false, $nest_level = 0) { +function print_feed_cat_select($id, $default_id, + $attributes, $include_all_cats = true, $root_id = null, $nest_level = 0) { if (!$root_id) { - print "<select id=\"$id\" name=\"$id\" $attributes>"; - if ($include_all_feeds) { - $is_selected = ("0" == $default_id) ? "selected=\"1\"" : ""; - print "<option $is_selected value=\"0\">".__('All feeds')."</option>"; - } + print "<select id=\"$id\" name=\"$id\" default=\"$default_id\" $attributes>"; } - if (get_pref('ENABLE_FEED_CATS')) { + $pdo = DB::pdo(); - if ($root_id) - $parent_qpart = "parent_cat = '$root_id'"; - else - $parent_qpart = "parent_cat IS NULL"; + if (!$root_id) $root_id = null; - $result = db_query("SELECT id,title, + $sth = $pdo->prepare("SELECT id,title, (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE c2.parent_cat = ttrss_feed_categories.id) AS num_children FROM ttrss_feed_categories - WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title"); - - while ($line = db_fetch_assoc($result)) { - - for ($i = 0; $i < $nest_level; $i++) - $line["title"] = " - " . $line["title"]; - - $is_selected = ("CAT:".$line["id"] == $default_id) ? "selected=\"1\"" : ""; - - printf("<option $is_selected value='CAT:%d'>%s</option>", - $line["id"], htmlspecialchars($line["title"])); - - if ($line["num_children"] > 0) - print_feed_select($id, $default_id, $attributes, - $include_all_feeds, $line["id"], $nest_level+1); - - $feed_result = db_query("SELECT id,title FROM ttrss_feeds - WHERE cat_id = '".$line["id"]."' AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title"); - - while ($fline = db_fetch_assoc($feed_result)) { - $is_selected = ($fline["id"] == $default_id) ? "selected=\"1\"" : ""; - - $fline["title"] = " + " . $fline["title"]; - - for ($i = 0; $i < $nest_level; $i++) - $fline["title"] = " - " . $fline["title"]; - - printf("<option $is_selected value='%d'>%s</option>", - $fline["id"], htmlspecialchars($fline["title"])); - } - } - - if (!$root_id) { - $default_is_cat = ($default_id == "CAT:0"); - $is_selected = $default_is_cat ? "selected=\"1\"" : ""; - - printf("<option $is_selected value='CAT:0'>%s</option>", - __("Uncategorized")); - - $feed_result = db_query("SELECT id,title FROM ttrss_feeds - WHERE cat_id IS NULL AND owner_uid = ".$_SESSION["uid"] . " ORDER BY title"); - - while ($fline = db_fetch_assoc($feed_result)) { - $is_selected = ($fline["id"] == $default_id && !$default_is_cat) ? "selected=\"1\"" : ""; - - $fline["title"] = " + " . $fline["title"]; - - for ($i = 0; $i < $nest_level; $i++) - $fline["title"] = " - " . $fline["title"]; + WHERE owner_uid = :uid AND + (parent_cat = :root_id OR (:root_id IS NULL AND parent_cat IS NULL)) ORDER BY title"); + $sth->execute([":uid" => $_SESSION['uid'], ":root_id" => $root_id]); - printf("<option $is_selected value='%d'>%s</option>", - $fline["id"], htmlspecialchars($fline["title"])); - } - } - - } else { - $result = db_query("SELECT id,title FROM ttrss_feeds - WHERE owner_uid = ".$_SESSION["uid"]." ORDER BY title"); - - while ($line = db_fetch_assoc($result)) { - - $is_selected = ($line["id"] == $default_id) ? "selected=\"1\"" : ""; - - printf("<option $is_selected value='%d'>%s</option>", - $line["id"], htmlspecialchars($line["title"])); - } - } + $found = 0; - if (!$root_id) { - print "</select>"; - } -}*/ + while ($line = $sth->fetch()) { + ++$found; -function print_feed_cat_select($id, $default_id, - $attributes, $include_all_cats = true, $root_id = false, $nest_level = 0) { - - if (!$root_id) { - print "<select id=\"$id\" name=\"$id\" default=\"$default_id\" $attributes>"; - } - - if ($root_id) - $parent_qpart = "parent_cat = '$root_id'"; - else - $parent_qpart = "parent_cat IS NULL"; - - $result = db_query("SELECT id,title, - (SELECT COUNT(id) FROM ttrss_feed_categories AS c2 WHERE - c2.parent_cat = ttrss_feed_categories.id) AS num_children - FROM ttrss_feed_categories - WHERE owner_uid = ".$_SESSION["uid"]." AND $parent_qpart ORDER BY title"); - - while ($line = db_fetch_assoc($result)) { if ($line["id"] == $default_id) { $is_selected = "selected=\"1\""; } else { @@ -303,7 +217,7 @@ function print_feed_cat_select($id, $default_id, if (!$root_id) { if ($include_all_cats) { - if (db_num_rows($result) > 0) { + if ($found > 0) { print "<option disabled=\"1\">--------</option>"; } @@ -401,13 +315,16 @@ function format_inline_player($url, $ctype) { function print_label_select($name, $value, $attributes = "") { - $result = db_query("SELECT caption FROM ttrss_labels2 - WHERE owner_uid = '".$_SESSION["uid"]."' ORDER BY caption"); + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT caption FROM ttrss_labels2 + WHERE owner_uid = ? ORDER BY caption"); + $sth->execute([$_SESSION['uid']]); print "<select default=\"$value\" name=\"" . htmlspecialchars($name) . "\" $attributes>"; - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { $issel = ($line["caption"] == $value) ? "selected=\"1\"" : ""; @@ -421,5 +338,4 @@ function print_label_select($name, $value, $attributes = "") { print "</select>"; -} - +}
\ No newline at end of file diff --git a/include/feedbrowser.php b/include/feedbrowser.php index b70e47fdf..a5a9f3dca 100644 --- a/include/feedbrowser.php +++ b/include/feedbrowser.php @@ -3,34 +3,32 @@ if (defined('_DISABLE_FEED_BROWSER') && _DISABLE_FEED_BROWSER) return; - $owner_uid = $_SESSION["uid"]; $rv = ''; + $pdo = Db::pdo(); + if ($search) { - $search_qpart = "AND (UPPER(feed_url) LIKE UPPER('%$search%') OR + $search = $pdo->quote($search); + + $search_qpart = "AND (UPPER(feed_url) LIKE UPPER('%$search%') OR UPPER(title) LIKE UPPER('%$search%'))"; } else { $search_qpart = ""; } if ($mode == 1) { - /* $result = db_query("SELECT feed_url, subscribers FROM - ttrss_feedbrowser_cache WHERE (SELECT COUNT(id) = 0 FROM ttrss_feeds AS tf - WHERE tf.feed_url = ttrss_feedbrowser_cache.feed_url - AND owner_uid = '$owner_uid') $search_qpart - ORDER BY subscribers DESC LIMIT $limit"); */ - - $result = db_query("SELECT feed_url, site_url, title, SUM(subscribers) AS subscribers FROM + $sth = $pdo->prepare("SELECT feed_url, site_url, title, SUM(subscribers) AS subscribers FROM (SELECT feed_url, site_url, title, subscribers FROM ttrss_feedbrowser_cache UNION ALL SELECT feed_url, site_url, title, subscribers FROM ttrss_linked_feeds) AS qqq WHERE (SELECT COUNT(id) = 0 FROM ttrss_feeds AS tf WHERE tf.feed_url = qqq.feed_url - AND owner_uid = '$owner_uid') $search_qpart - GROUP BY feed_url, site_url, title ORDER BY subscribers DESC LIMIT $limit"); + AND owner_uid = ?) $search_qpart + GROUP BY feed_url, site_url, title ORDER BY subscribers DESC LIMIT ?"); + $sth->execute([$_SESSION['uid'], $limit]); } else if ($mode == 2) { - $result = db_query("SELECT *, + $sth = $pdo->prepare("SELECT *, (SELECT COUNT(*) FROM ttrss_user_entries WHERE orig_feed_id = ttrss_archived_feeds.id) AS articles_archived FROM @@ -38,14 +36,16 @@ WHERE (SELECT COUNT(*) FROM ttrss_feeds WHERE ttrss_feeds.feed_url = ttrss_archived_feeds.feed_url AND - owner_uid = '$owner_uid') = 0 AND - owner_uid = '$owner_uid' $search_qpart - ORDER BY id DESC LIMIT $limit"); + owner_uid = :uid) = 0 AND + owner_uid = :uid $search_qpart + ORDER BY id DESC LIMIT :limit"); + + $sth->execute([":uid" => $_SESSION['uid'], ":limit" => $limit]); } $feedctr = 0; - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { if ($mode == 1) { diff --git a/include/functions.php b/include/functions.php index 5667ac416..e1e63c2a3 100644 --- a/include/functions.php +++ b/include/functions.php @@ -226,15 +226,15 @@ if (!$purge_interval) $purge_interval = feed_purge_interval($feed_id); - $rows = -1; + $pdo = Db::pdo(); - $result = db_query( - "SELECT owner_uid FROM ttrss_feeds WHERE id = '$feed_id'"); + $sth = $pdo->prepare("SELECT owner_uid FROM ttrss_feeds WHERE id = ?"); + $sth->execute([$feed_id]); $owner_uid = false; - if (db_num_rows($result) == 1) { - $owner_uid = db_fetch_result($result, 0, "owner_uid"); + if ($row = $sth->fetch()) { + $owner_uid = $row["owner_uid"]; } if ($purge_interval == -1 || !$purge_interval) { @@ -254,34 +254,36 @@ $purge_interval = FORCE_ARTICLE_PURGE; } - if (!$purge_unread) $query_limit = " unread = false AND "; + if (!$purge_unread) + $query_limit = " unread = false AND "; + else + $query_limit = ""; + + $purge_interval = (int) $purge_interval; if (DB_TYPE == "pgsql") { - $result = db_query("DELETE FROM ttrss_user_entries + $sth = $pdo->prepare("DELETE FROM ttrss_user_entries USING ttrss_entries WHERE ttrss_entries.id = ref_id AND marked = false AND - feed_id = '$feed_id' AND + feed_id = ? AND $query_limit ttrss_entries.date_updated < NOW() - INTERVAL '$purge_interval days'"); + $sth->execute([$feed_id]); } else { - -/* $result = db_query("DELETE FROM ttrss_user_entries WHERE - marked = false AND feed_id = '$feed_id' AND - (SELECT date_updated FROM ttrss_entries WHERE - id = ref_id) < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); */ - - $result = db_query("DELETE FROM ttrss_user_entries + $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 = '$feed_id' AND + feed_id = ? AND $query_limit ttrss_entries.date_updated < DATE_SUB(NOW(), INTERVAL $purge_interval DAY)"); + $sth->execute([$feed_id]); + } - $rows = db_affected_rows($result); + $rows = $sth->rowCount(); CCache::update($feed_id, $owner_uid); @@ -294,12 +296,15 @@ function feed_purge_interval($feed_id) { - $result = db_query("SELECT purge_interval, owner_uid FROM ttrss_feeds - WHERE id = '$feed_id'"); + $pdo = DB::pdo(); + + $sth = $pdo->prepare("SELECT purge_interval, owner_uid FROM ttrss_feeds + WHERE id = ?"); + $sth->execute([$feed_id]); - if (db_num_rows($result) == 1) { - $purge_interval = db_fetch_result($result, 0, "purge_interval"); - $owner_uid = db_fetch_result($result, 0, "owner_uid"); + if ($row = $sth->fetch()) { + $purge_interval = $row["purge_interval"]; + $owner_uid = $row["owner_uid"]; if ($purge_interval == 0) $purge_interval = get_pref( 'PURGE_OLD_DAYS', $owner_uid); @@ -311,25 +316,6 @@ } } - /*function get_feed_update_interval($feed_id) { - $result = db_query("SELECT owner_uid, update_interval FROM - ttrss_feeds WHERE id = '$feed_id'"); - - if (db_num_rows($result) == 1) { - $update_interval = db_fetch_result($result, 0, "update_interval"); - $owner_uid = db_fetch_result($result, 0, "owner_uid"); - - if ($update_interval != 0) { - return $update_interval; - } else { - return get_pref('DEFAULT_UPDATE_INTERVAL', $owner_uid, false); - } - - } else { - return -1; - } - }*/ - // TODO: multiple-argument way is deprecated, first parameter is a hash now function fetch_file_contents($options /* previously: 0: $url , 1: $type = false, 2: $login = false, 3: $pass = false, 4: $post_query = false, 5: $timeout = false, 6: $timestamp = 0, 7: $useragent = false*/) { @@ -603,52 +589,53 @@ function initialize_user_prefs($uid, $profile = false) { - $uid = db_escape_string($uid); + if (get_schema_version() < 63) $profile_qpart = ""; - if (!$profile) { - $profile = "NULL"; - $profile_qpart = "AND profile IS NULL"; - } else { - $profile_qpart = "AND profile = '$profile'"; - } + $pdo = DB::pdo(); + $in_nested_tr = false; - if (get_schema_version() < 63) $profile_qpart = ""; + try { + $pdo->beginTransaction(); + } catch (Exception $e) { + $in_nested_tr = true; + } - db_query("BEGIN"); + $sth = $pdo->query("SELECT pref_name,def_value FROM ttrss_prefs"); - $result = db_query("SELECT pref_name,def_value FROM ttrss_prefs"); + $profile = $profile ? $profile : null; - $u_result = db_query("SELECT pref_name - FROM ttrss_user_prefs WHERE owner_uid = '$uid' $profile_qpart"); + $u_sth = $pdo->prepare("SELECT pref_name + FROM ttrss_user_prefs WHERE owner_uid = :uid AND + (profile = :profile OR (:profile IS NULL AND profile IS NULL))"); + $u_sth->execute([':uid' => $uid, ':profile' => $profile]); $active_prefs = array(); - while ($line = db_fetch_assoc($u_result)) { + while ($line = $u_sth->fetch()) { array_push($active_prefs, $line["pref_name"]); } - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { if (array_search($line["pref_name"], $active_prefs) === FALSE) { // print "adding " . $line["pref_name"] . "<br>"; - $line["def_value"] = db_escape_string($line["def_value"]); - $line["pref_name"] = db_escape_string($line["pref_name"]); - if (get_schema_version() < 63) { - db_query("INSERT INTO ttrss_user_prefs + $i_sth = $pdo->prepare("INSERT INTO ttrss_user_prefs (owner_uid,pref_name,value) VALUES - ('$uid', '".$line["pref_name"]."','".$line["def_value"]."')"); + (?, ?, ?)"); + $i_sth->execute([$uid, $line["pref_name"], $line["def_value"]]); } else { - db_query("INSERT INTO ttrss_user_prefs + $i_sth = $pdo->prepare("INSERT INTO ttrss_user_prefs (owner_uid,pref_name,value, profile) VALUES - ('$uid', '".$line["pref_name"]."','".$line["def_value"]."', $profile)"); + (?, ?, ?, ?)"); + $i_sth->execute([$uid, $line["pref_name"], $line["def_value"], $profile]); } } } - db_query("COMMIT"); + if (!$in_nested_tr) $pdo->commit(); } @@ -689,19 +676,22 @@ $_SESSION["uid"] = $user_id; $_SESSION["version"] = VERSION_STATIC; - $result = db_query("SELECT login,access_level,pwd_hash FROM ttrss_users - WHERE id = '$user_id'"); + $pdo = DB::pdo(); + $sth = $pdo->prepare("SELECT login,access_level,pwd_hash FROM ttrss_users + WHERE id = ?"); + $sth->execute([$user_id]); + $row = $sth->fetch(); - $_SESSION["name"] = db_fetch_result($result, 0, "login"); - $_SESSION["access_level"] = db_fetch_result($result, 0, "access_level"); + $_SESSION["name"] = $row["login"]; + $_SESSION["access_level"] = $row["access_level"]; $_SESSION["csrf_token"] = uniqid_short(); - db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " . - $_SESSION["uid"]); + $usth = $pdo->prepare("UPDATE ttrss_users SET last_login = NOW() WHERE id = ?"); + $usth->execute([$user_id]); $_SESSION["ip_address"] = $_SERVER["REMOTE_ADDR"]; $_SESSION["user_agent"] = sha1($_SERVER['HTTP_USER_AGENT']); - $_SESSION["pwd_hash"] = db_fetch_result($result, 0, "pwd_hash"); + $_SESSION["pwd_hash"] = $row["pwd_hash"]; $_SESSION["last_version_check"] = time(); @@ -735,6 +725,17 @@ } } + // this is used for user http parameters unless HTML code is actually needed + function clean($param) { + if (is_array($param)) { + return array_map(strip_tags, $param); + } else if (is_string($param)) { + return strip_tags($param); + } else { + return $param; + } + } + function make_password($length = 8) { $password = ""; @@ -760,9 +761,12 @@ function initialize_user($uid) { - db_query("insert into ttrss_feeds (owner_uid,title,feed_url) - values ('$uid', 'Tiny Tiny RSS: Forum', + $pdo = DB::pdo(); + + $sth = $pdo->prepare("insert into ttrss_feeds (owner_uid,title,feed_url) + values (?, 'Tiny Tiny RSS: Forum', 'http://tt-rss.org/forum/rss.php')"); + $sth->execute([$uid]); } function logout_user() { @@ -792,6 +796,8 @@ } function login_sequence() { + $pdo = Db::pdo(); + if (SINGLE_USER_MODE) { @session_start(); authenticate_user("admin", null); @@ -818,8 +824,9 @@ } else { /* bump login timestamp */ - db_query("UPDATE ttrss_users SET last_login = NOW() WHERE id = " . - $_SESSION["uid"]); + $sth = $pdo->prepare("UPDATE ttrss_users SET last_login = NOW() WHERE id = ?"); + $sth->execute([$_SESSION['uid']]); + $_SESSION["last_login_update"] = time(); } @@ -829,16 +836,19 @@ /* cleanup ccache */ - db_query("DELETE FROM ttrss_counters_cache WHERE owner_uid = ". - $_SESSION["uid"] . " AND + $sth = $pdo->prepare("DELETE FROM ttrss_counters_cache WHERE owner_uid = ? + AND (SELECT COUNT(id) FROM ttrss_feeds WHERE ttrss_feeds.id = feed_id) = 0"); - db_query("DELETE FROM ttrss_cat_counters_cache WHERE owner_uid = ". - $_SESSION["uid"] . " AND + $sth->execute([$_SESSION['uid']]); + + $sth = $pdo->prepare("DELETE FROM ttrss_cat_counters_cache WHERE owner_uid = ? + AND (SELECT COUNT(id) FROM ttrss_feed_categories WHERE ttrss_feed_categories.id = feed_id) = 0"); + $sth->execute([$_SESSION['uid']]); } } @@ -942,19 +952,11 @@ } function sql_bool_to_bool($s) { - if ($s == "t" || $s == "1" || strtolower($s) == "true") { - return true; - } else { - return false; - } + return $s && ($s !== "f" && $s !== "false"); //no-op for PDO, backwards compat for legacy layer } function bool_to_sql_bool($s) { - if ($s) { - return "true"; - } else { - return "false"; - } + return $s ? 1 : 0; } // Session caching removed due to causing wrong redirects to upgrade @@ -963,9 +965,11 @@ function get_schema_version($nocache = false) { global $schema_version; + $pdo = DB::pdo(); + if (!$schema_version && !$nocache) { - $result = db_query("SELECT schema_version FROM ttrss_version"); - $version = db_fetch_result($result, 0, "schema_version"); + $row = $pdo->query("SELECT schema_version FROM ttrss_version")->fetch(); + $version = $row["schema_version"]; $schema_version = $version; return $version; } else { @@ -984,17 +988,6 @@ $error_code = 5; } - if (DB_TYPE == "mysql") { - $result = db_query("SELECT true", false); - if (db_num_rows($result) != 1) { - $error_code = 10; - } - } - - if (db_escape_string("testTEST") != "testTEST") { - $error_code = 12; - } - return array("code" => $error_code, "message" => $ERRORS[$error_code]); } @@ -1070,36 +1063,9 @@ return Feeds::getFeedArticles($feed, $is_cat, true, $_SESSION["uid"]); } - - /*function get_pgsql_version() { - $result = db_query("SELECT version() AS version"); - $version = explode(" ", db_fetch_result($result, 0, "version")); - return $version[1]; - }*/ - function checkbox_to_sql_bool($val) { - return ($val == "on") ? "true" : "false"; - } - - /*function getFeedCatTitle($id) { - if ($id == -1) { - return __("Special"); - } else if ($id < LABEL_BASE_INDEX) { - return __("Labels"); - } else if ($id > 0) { - $result = db_query("SELECT ttrss_feed_categories.title - FROM ttrss_feeds, ttrss_feed_categories WHERE ttrss_feeds.id = '$id' AND - cat_id = ttrss_feed_categories.id"); - if (db_num_rows($result) == 1) { - return db_fetch_result($result, 0, "title"); - } else { - return __("Uncategorized"); - } - } else { - return "getFeedCatTitle($id) failed"; - } - - }*/ + return ($val == "on") ? 1 : 0; + } function uniqid_short() { return uniqid(base_convert(rand(), 10, 36)); @@ -1122,6 +1088,7 @@ $params["default_view_limit"] = (int) get_pref("_DEFAULT_VIEW_LIMIT"); $params["default_view_order_by"] = get_pref("_DEFAULT_VIEW_ORDER_BY"); $params["bw_limit"] = (int) $_SESSION["bw_limit"]; + $params["is_default_pw"] = Pref_Prefs::isdefaultpassword(); $params["label_base_index"] = (int) LABEL_BASE_INDEX; $theme = get_pref( "USER_CSS_THEME", false, false); @@ -1134,11 +1101,15 @@ $params["sanity_checksum"] = sha1(file_get_contents("include/sanity_check.php")); - $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM - ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]); + $pdo = Db::pdo(); - $max_feed_id = db_fetch_result($result, 0, "mid"); - $num_feeds = db_fetch_result($result, 0, "nf"); + $sth = $pdo->prepare("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM + ttrss_feeds WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + $row = $sth->fetch(); + + $max_feed_id = $row["mid"]; + $num_feeds = $row["nf"]; $params["max_feed_id"] = (int) $max_feed_id; $params["num_feeds"] = (int) $num_feeds; @@ -1339,11 +1310,15 @@ function make_runtime_info($disable_update_check = false) { $data = array(); - $result = db_query("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM - ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]); + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT MAX(id) AS mid, COUNT(*) AS nf FROM + ttrss_feeds WHERE owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); + $row = $sth->fetch(); - $max_feed_id = db_fetch_result($result, 0, "mid"); - $num_feeds = db_fetch_result($result, 0, "nf"); + $max_feed_id = $row['mid']; + $num_feeds = $row['nf']; $data["max_feed_id"] = (int) $max_feed_id; $data["num_feeds"] = (int) $num_feeds; @@ -1401,8 +1376,10 @@ $search_words = array(); $search_query_leftover = array(); + $pdo = Db::pdo(); + if ($search_language) - $search_language = db_escape_string(mb_strtolower($search_language)); + $search_language = $pdo->quote(mb_strtolower($search_language)); else $search_language = "english"; @@ -1419,21 +1396,21 @@ switch ($commandpair[0]) { case "title": if ($commandpair[1]) { - array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE '%". - db_escape_string(mb_strtolower($commandpair[1]))."%'))"); + array_push($query_keywords, "($not (LOWER(ttrss_entries.title) LIKE ". + $pdo->quote('%' . mb_strtolower($commandpair[1]) . '%') ."))"); } else { array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); array_push($search_words, $k); } break; case "author": if ($commandpair[1]) { - array_push($query_keywords, "($not (LOWER(author) LIKE '%". - db_escape_string(mb_strtolower($commandpair[1]))."%'))"); + array_push($query_keywords, "($not (LOWER(author) LIKE ". + $pdo->quote('%' . mb_strtolower($commandpair[1]) . '%')."))"); } else { array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); array_push($search_words, $k); } break; @@ -1444,11 +1421,11 @@ else if ($commandpair[1] == "false") array_push($query_keywords, "($not (note IS NULL OR note = ''))"); else - array_push($query_keywords, "($not (LOWER(note) LIKE '%". - db_escape_string(mb_strtolower($commandpair[1]))."%'))"); + array_push($query_keywords, "($not (LOWER(note) LIKE ". + $pdo->quote('%' . mb_strtolower($commandpair[1]) . '%')."))"); } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER(".$pdo->quote("%$k%").") + OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); if (!$not) array_push($search_words, $k); } break; @@ -1460,8 +1437,8 @@ else array_push($query_keywords, "($not (marked = false))"); } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER(".$pdo->quote("%$k%").") + OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); if (!$not) array_push($search_words, $k); } break; @@ -1474,7 +1451,7 @@ } else { array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); if (!$not) array_push($search_words, $k); } break; @@ -1486,8 +1463,8 @@ array_push($query_keywords, "($not (unread = false))"); } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER(".$pdo->quote("%$k%").") + OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); if (!$not) array_push($search_words, $k); } break; @@ -1507,8 +1484,8 @@ $k = mb_strtolower($k); array_push($search_query_leftover, $not ? "!$k" : $k); } else { - array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER('%$k%') - OR UPPER(ttrss_entries.content) $not LIKE UPPER('%$k%'))"); + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER(".$pdo->quote("%$k%").") + OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); } if (!$not) array_push($search_words, $k); @@ -1517,11 +1494,11 @@ } if (count($search_query_leftover) > 0) { - $search_query_leftover = db_escape_string(implode(" & ", $search_query_leftover)); + $search_query_leftover = $pdo->quote(implode(" & ", $search_query_leftover)); if (DB_TYPE == "pgsql") { array_push($query_keywords, - "(tsvector_combined @@ to_tsquery('$search_language', '$search_query_leftover'))"); + "(tsvector_combined @@ to_tsquery($search_language, $search_query_leftover))"); } } @@ -1846,6 +1823,7 @@ function load_filters($feed_id, $owner_uid) { $filters = array(); + $feed_id = (int) $feed_id; $cat_id = (int)Feeds::getFeedCategory($feed_id); if ($cat_id == 0) @@ -1853,8 +1831,11 @@ else $null_cat_qpart = ""; - $result = db_query("SELECT * FROM ttrss_filters2 WHERE - owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title"); + $pdo = Db::pdo(); + + $sth = $pdo->prepare("SELECT * FROM ttrss_filters2 WHERE + owner_uid = ? AND enabled = true ORDER BY order_id, title"); + $sth->execute([$owner_uid]); $check_cats = array_merge( Feeds::getParentCategories($cat_id, $owner_uid), @@ -1863,25 +1844,26 @@ $check_cats_str = join(",", $check_cats); $check_cats_fullids = array_map(function($a) { return "CAT:$a"; }, $check_cats); - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { $filter_id = $line["id"]; $match_any_rule = sql_bool_to_bool($line["match_any_rule"]); - $result2 = db_query("SELECT + $sth2 = $pdo->prepare("SELECT r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, r.match_on, t.name AS type_name FROM ttrss_filters2_rules AS r, ttrss_filter_types AS t WHERE (match_on IS NOT NULL OR (($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats_str)) AND - (feed_id IS NULL OR feed_id = '$feed_id'))) AND - filter_type = t.id AND filter_id = '$filter_id'"); + (feed_id IS NULL OR feed_id = ?))) AND + filter_type = t.id AND filter_id = ?"); + $sth2->execute([$feed_id, $filter_id]); $rules = array(); $actions = array(); - while ($rule_line = db_fetch_assoc($result2)) { + while ($rule_line = $sth2->fetch()) { # print_r($rule_line); if ($rule_line["match_on"]) { @@ -1915,13 +1897,14 @@ } if (count($rules) > 0) { - $result2 = db_query("SELECT a.action_param,t.name AS type_name + $sth2 = $pdo->prepare("SELECT a.action_param,t.name AS type_name FROM ttrss_filters2_actions AS a, ttrss_filter_actions AS t WHERE - action_id = t.id AND filter_id = '$filter_id'"); + action_id = t.id AND filter_id = ?"); + $sth2->execute([$filter_id]); - while ($action_line = db_fetch_assoc($result2)) { + while ($action_line = $sth2->fetch()) { # print_r($action_line); $action = array(); @@ -1960,10 +1943,6 @@ } } - function feed_has_icon($id) { - return is_file(ICONS_DIR . "/$id.ico") && filesize(ICONS_DIR . "/$id.ico") > 0; - } - function init_plugins() { PluginHost::getInstance()->load(PLUGINS, PluginHost::KIND_ALL); @@ -1974,33 +1953,36 @@ if (!$feed_cat) return false; - db_query("BEGIN"); + $feed_cat = mb_substr($feed_cat, 0, 250); + if (!$parent_cat_id) $parent_cat_id = null; - if ($parent_cat_id) { - $parent_qpart = "parent_cat = '$parent_cat_id'"; - $parent_insert = "'$parent_cat_id'"; - } else { - $parent_qpart = "parent_cat IS NULL"; - $parent_insert = "NULL"; - } + $pdo = Db::pdo(); + $tr_in_progress = false; - $feed_cat = mb_substr($feed_cat, 0, 250); + try { + $pdo->beginTransaction(); + } catch (Exception $e) { + $tr_in_progress = true; + } - $result = db_query( - "SELECT id FROM ttrss_feed_categories - WHERE $parent_qpart AND title = '$feed_cat' AND owner_uid = ".$_SESSION["uid"]); + $sth = $pdo->prepare("SELECT id FROM ttrss_feed_categories + WHERE (parent_cat = :parent OR (:parent IS NULL AND parent_cat IS NULL)) + AND title = :title AND owner_uid = :uid"); + $sth->execute([':parent' => $parent_cat_id, ':title' => $feed_cat, ':uid' => $_SESSION['uid']]); - if (db_num_rows($result) == 0) { + if (!$sth->fetch()) { - $result = db_query( - "INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat) - VALUES ('".$_SESSION["uid"]."', '$feed_cat', $parent_insert)"); + $sth = $pdo->prepare("INSERT INTO ttrss_feed_categories (owner_uid,title,parent_cat) + VALUES (?, ?, ?)"); + $sth->execute([$_SESSION['uid'], $feed_cat, $parent_cat_id]); - db_query("COMMIT"); + if (!$tr_in_progress) $pdo->commit(); return true; } + $pdo->commit(); + return false; } @@ -2070,24 +2052,28 @@ if (!$owner_uid) $owner_uid = $_SESSION["uid"]; - $sql_is_cat = bool_to_sql_bool($is_cat); + $is_cat = bool_to_sql_bool($is_cat); + + $pdo = Db::pdo(); - $result = db_query("SELECT access_key FROM ttrss_access_keys - WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat - AND owner_uid = " . $owner_uid); + $sth = $pdo->prepare("SELECT access_key FROM ttrss_access_keys + WHERE feed_id = ? AND is_cat = ? + AND owner_uid = ?"); + $sth->execute([$feed_id, (int)$is_cat, $owner_uid]); - if (db_num_rows($result) == 1) { - return db_fetch_result($result, 0, "access_key"); + if ($row = $sth->fetch()) { + return $row["access_key"]; } else { - $key = db_escape_string(uniqid_short()); + $key = uniqid_short(); - $result = db_query("INSERT INTO ttrss_access_keys + $sth = $pdo->prepare("INSERT INTO ttrss_access_keys (access_key, feed_id, is_cat, owner_uid) - VALUES ('$key', '$feed_id', $sql_is_cat, '$owner_uid')"); + VALUES (?, ?, ?, ?)"); + + $sth->execute([$key, $feed_id, (int)$is_cat, $owner_uid]); return $key; } - return false; } function get_feeds_from_html($url, $content) @@ -2180,6 +2166,8 @@ function cleanup_tags($days = 14, $limit = 1000) { + $days = (int) $days; + if (DB_TYPE == "pgsql") { $interval_query = "date_updated < NOW() - INTERVAL '$days days'"; } else if (DB_TYPE == "mysql") { @@ -2188,27 +2176,28 @@ $tags_deleted = 0; - while ($limit > 0) { + $pdo = Db::pdo(); + + while ($limit > 0) { $limit_part = 500; - $query = "SELECT ttrss_tags.id AS id + $sth = $pdo->prepare("SELECT ttrss_tags.id AS id FROM ttrss_tags, ttrss_user_entries, ttrss_entries WHERE post_int_id = int_id AND $interval_query AND - ref_id = ttrss_entries.id AND tag_cache != '' LIMIT $limit_part"; - - $result = db_query($query); + ref_id = ttrss_entries.id AND tag_cache != '' LIMIT ?"); + $sth->execute([$limit]); $ids = array(); - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { array_push($ids, $line['id']); } if (count($ids) > 0) { $ids = join(",", $ids); - $tmp_result = db_query("DELETE FROM ttrss_tags WHERE id IN ($ids)"); - $tags_deleted += db_affected_rows($tmp_result); + $usth = $pdo->query("DELETE FROM ttrss_tags WHERE id IN ($ids)"); + $tags_deleted = $usth->rowCount(); } else { break; } @@ -2233,6 +2222,8 @@ function filter_to_sql($filter, $owner_uid) { $query = array(); + $pdo = Db::pdo(); + if (DB_TYPE == "pgsql") $reg_qpart = "~"; else @@ -2245,7 +2236,7 @@ if ($regexp_valid) { - $rule['reg_exp'] = db_escape_string($rule['reg_exp']); + $rule['reg_exp'] = $pdo->quote($rule['reg_exp']); switch ($rule["type"]) { case "title": @@ -2278,7 +2269,7 @@ if (isset($rule['inverse'])) $qpart = "NOT ($qpart)"; if (isset($rule["feed_id"]) && $rule["feed_id"] > 0) { - $qpart .= " AND feed_id = " . db_escape_string($rule["feed_id"]); + $qpart .= " AND feed_id = " . $pdo->quote($rule["feed_id"]); } if (isset($rule["cat_id"])) { @@ -2358,9 +2349,9 @@ foreach ($files as $js) { if (!isset($_GET['debug'])) { - $cached_file = CACHE_DIR . "/js/".basename($js).".js"; + $cached_file = CACHE_DIR . "/js/".basename($js); - if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js.js")) { + if (file_exists($cached_file) && is_readable($cached_file) && filemtime($cached_file) >= filemtime("js/$js")) { list($header, $contents) = explode("\n", file_get_contents($cached_file), 2); @@ -2374,12 +2365,12 @@ } } - $minified = JShrink\Minifier::minify(file_get_contents("js/$js.js")); + $minified = JShrink\Minifier::minify(file_get_contents("js/$js")); file_put_contents($cached_file, "tt-rss:" . VERSION . "\n" . $minified); $rv .= $minified; } else { - $rv .= file_get_contents("js/$js.js"); // no cache in debug mode + $rv .= file_get_contents("js/$js"); // no cache in debug mode } } @@ -2442,6 +2433,9 @@ } function get_theme_path($theme) { + if ($theme == "default.php") + return "css/default.css"; + $check = "themes/$theme"; if (file_exists($check)) return $check; @@ -2550,17 +2544,28 @@ } function check_mysql_tables() { - $schema = db_escape_string(DB_NAME); + $pdo = Db::pdo(); - $result = db_query("SELECT engine, table_name FROM information_schema.tables WHERE - table_schema = '$schema' AND table_name LIKE 'ttrss_%' AND engine != 'InnoDB'"); + $sth = $pdo->prepare("SELECT engine, table_name FROM information_schema.tables WHERE + table_schema = ? AND table_name LIKE 'ttrss_%' AND engine != 'InnoDB'"); + $sth->execute([DB_NAME]); $bad_tables = []; - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { array_push($bad_tables, $line); } return $bad_tables; } + function validate_field($string, $allowed, $default = "") { + if (in_array($string, $allowed)) + return $string; + else + return $default; + } + + function arr_qmarks($arr) { + return str_repeat('?,', count($arr) - 1) . '?'; + } diff --git a/include/login_form.php b/include/login_form.php index 41c3f173e..bb142f6c5 100644 --- a/include/login_form.php +++ b/include/login_form.php @@ -23,98 +23,9 @@ init(); }); </script> - <style type="text/css"> - body#ttrssLogin { - padding : 2em; - font-size : 14px; - } - - fieldset { - margin-left : auto; - margin-right : auto; - display : block; - width : 400px; - border-width : 0px; - } - - /*input.input { - font-family : sans-serif; - font-size : medium; - border-spacing : 2px; - border : 1px solid #b5bcc7; - padding : 2px; - }*/ - - label { - width : 120px; - margin-right : 20px; - display : inline-block; - text-align : right; - color : gray; - } - - div.header { - border-width : 0px 0px 1px 0px; - border-style : solid; - border-color : #0088cc; - margin-bottom : 1em; - padding-bottom : 5px; - } - - div.footer { - margin-top : 1em; - padding-top : 5px; - border-width : 1px 0px 0px 0px; - border-style : solid; - border-color : #0088cc; - text-align : center; - color : gray; - font-size : 12px; - } - - a.forgotpass { - text-align : right; - font-size : 11px; - display : inline-block; - } - - a { - color : #0088cc; - } - - a { - color: #0088cc; - text-decoration: none; - } - - a:hover, - a:focus { - color: #005580; - text-decoration: underline; - } - - div.footer a { - color : gray; - } - - div.footer a:hover { - color : #0088cc; - } - - div.row { - padding : 0px 0px 5px 0px; - } - - div.row-error { - color : red; - text-align : center; - padding : 0px 0px 5px 0px; - } - - </style> </head> -<body id="ttrssLogin" class="claro"> +<body class="claro ttrss_main ttrss_login"> <script type="text/javascript"> function init() { @@ -143,7 +54,7 @@ function fetchProfiles() { onComplete: function(transport) { if (transport.responseText.match("select")) { $('profile_box').innerHTML = transport.responseText; - dojo.parser.parse('profile_box'); + //dojo.parser.parse('profile_box'); } } }); } diff --git a/include/sanity_check.php b/include/sanity_check.php index a84959f8e..94578b404 100755 --- a/include/sanity_check.php +++ b/include/sanity_check.php @@ -90,10 +90,12 @@ } } - if (SINGLE_USER_MODE) { - $result = db_query("SELECT id FROM ttrss_users WHERE id = 1"); + if (SINGLE_USER_MODE && class_exists("PDO")) { + $pdo = DB::pdo(); - if (db_num_rows($result) != 1) { + $res = $pdo->query("SELECT id FROM ttrss_users WHERE id = 1"); + + if (!$res->fetch()) { array_push($errors, "SINGLE_USER_MODE is enabled in config.php but default admin account is not found."); } } @@ -137,6 +139,10 @@ array_push($errors, "PHP support for PostgreSQL is required for configured DB_TYPE in config.php"); } + if (!class_exists("PDO")) { + array_push($errors, "PHP support for PDO is required but was not found."); + } + if (!function_exists("mb_strlen")) { array_push($errors, "PHP support for mbstring functions is required but was not found."); } @@ -186,9 +192,9 @@ <head> <title>Startup failed</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> - <link rel="stylesheet" type="text/css" href="css/utility.css"> + <link rel="stylesheet" type="text/css" href="css/default.css"> </head> - <body class='sanity_failed'> + <body class='sanity_failed claro ttrss_utility'> <div class="floatingLogo"><img src="images/logo_small.png"></div> <div class="content"> diff --git a/include/sessions.php b/include/sessions.php index 7ea9052bc..c80c21de3 100644 --- a/include/sessions.php +++ b/include/sessions.php @@ -27,8 +27,10 @@ global $schema_version; if (!$schema_version) { - $result = Db::get()->query("SELECT schema_version FROM ttrss_version"); - $version = Db::get()->fetch_result($result, 0, "schema_version"); + $row = Db::pdo()->query("SELECT schema_version FROM ttrss_version")->fetch(); + + $version = $row["schema_version"]; + $schema_version = $version; return $version; } else { @@ -44,28 +46,30 @@ __("Session failed to validate (schema version changed)"); return false; } + $pdo = Db::pdo(); if ($_SESSION["uid"]) { - $result = Db::get()->query( - "SELECT pwd_hash FROM ttrss_users WHERE id = '".$_SESSION["uid"]."'"); + $sth = $pdo->prepare("SELECT pwd_hash FROM ttrss_users WHERE id = ?"); + $sth->execute([$_SESSION['uid']]); // user not found - if (Db::get()->num_rows($result) == 0) { + if ($row = $sth->fetch()) { + $pwd_hash = $row["pwd_hash"]; + + if ($pwd_hash != $_SESSION["pwd_hash"]) { - $_SESSION["login_error_msg"] = - __("Session failed to validate (user not found)"); + $_SESSION["login_error_msg"] = + __("Session failed to validate (password changed)"); - return false; + return false; + } } else { - $pwd_hash = Db::get()->fetch_result($result, 0, "pwd_hash"); - if ($pwd_hash != $_SESSION["pwd_hash"]) { + $_SESSION["login_error_msg"] = + __("Session failed to validate (user not found)"); - $_SESSION["login_error_msg"] = - __("Session failed to validate (password changed)"); + return false; - return false; - } } } @@ -82,18 +86,21 @@ function ttrss_read ($id){ global $session_expire; - $res = Db::get()->query("SELECT data FROM ttrss_sessions WHERE id='$id'"); + $sth = Db::pdo()->prepare("SELECT data FROM ttrss_sessions WHERE id=?"); + $sth->execute([$id]); - if (Db::get()->num_rows($res) != 1) { + if ($row = $sth->fetch()) { + return base64_decode($row["data"]); - $expire = time() + $session_expire; + } else { + $expire = time() + $session_expire; - Db::get()->query("INSERT INTO ttrss_sessions (id, data, expire) - VALUES ('$id', '', '$expire')"); + $sth = Db::pdo()->prepare("INSERT INTO ttrss_sessions (id, data, expire) + VALUES (?, '', ?)"); + $sth->execute([$id, $expire]); + + return ""; - return ""; - } else { - return base64_decode(Db::get()->fetch_result($res, 0, "data")); } } @@ -104,7 +111,8 @@ $data = base64_encode($data); $expire = time() + $session_expire; - Db::get()->query("UPDATE ttrss_sessions SET data='$data', expire='$expire' WHERE id='$id'"); + $sth = Db::pdo()->prepare("UPDATE ttrss_sessions SET data=?, expire=? WHERE id=?"); + $sth->execute([$data, $expire, $id]); return true; } @@ -114,7 +122,8 @@ } function ttrss_destroy($id) { - Db::get()->query("DELETE FROM ttrss_sessions WHERE id = '$id'"); + $sth = Db::pdo()->prepare("DELETE FROM ttrss_sessions WHERE id = ?"); + $sth->execute([$id]); return true; } @@ -123,7 +132,7 @@ * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ function ttrss_gc ($expire) { - Db::get()->query("DELETE FROM ttrss_sessions WHERE expire < " . time()); + Db::pdo()->query("DELETE FROM ttrss_sessions WHERE expire < " . time()); return true; } diff --git a/include/version.php b/include/version.php index 02bc7f3be..1d7567bb1 100644 --- a/include/version.php +++ b/include/version.php @@ -1,5 +1,5 @@ <?php - define('VERSION_STATIC', '17.4'); + define('VERSION_STATIC', '17.12'); function get_version() { date_default_timezone_set('UTC'); @@ -62,14 +62,13 @@ </script> <?php echo stylesheet_tag("lib/dijit/themes/claro/claro.css"); ?> - <?php echo stylesheet_tag("css/layout.css"); ?> <?php if ($_SESSION["uid"]) { $theme = get_pref( "USER_CSS_THEME", $_SESSION["uid"], false); if ($theme && theme_valid("$theme")) { echo stylesheet_tag(get_theme_path($theme)); } else { - echo stylesheet_tag("themes/default.php"); + echo stylesheet_tag("css/default.css"); } } ?> @@ -115,8 +114,8 @@ <?php require_once 'lib/jshrink/Minifier.php'; - print get_minified_js(array("tt-rss", - "functions", "feedlist", "viewfeed", "PluginHost")); + print get_minified_js(["tt-rss.js", + "functions.js", "feedlist.js", "viewfeed.js", "PluginHost.js"]); foreach (PluginHost::getInstance()->get_plugins() as $n => $p) { if (method_exists($p, "get_js")) { @@ -143,7 +142,7 @@ </script> </head> -<body id="ttrssMain" class="claro"> +<body class="claro ttrss_main"> <div id="overlay" style="display : block"> <div id="overlay_inner"> diff --git a/install/index.php b/install/index.php index 9f817db17..f1547982c 100755 --- a/install/index.php +++ b/install/index.php @@ -2,13 +2,12 @@ <head> <title>Tiny Tiny RSS - Installer</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> - <link rel="stylesheet" type="text/css" href="../css/utility.css"> <link rel="stylesheet" type="text/css" href="../css/default.css"> <style type="text/css"> textarea { font-size : 12px; } </style> </head> -<body class="claro"> +<body class="claro ttrss_utility"> <?php @@ -51,12 +50,8 @@ array_push($errors, "PHP support for JSON is required, but was not found."); } - if ($db_type == "mysql" && !function_exists("mysqli_connect")) { - array_push($errors, "PHP support for MySQL is required for configured $db_type in config.php."); - } - - if ($db_type == "pgsql" && !function_exists("pg_connect")) { - array_push($errors, "PHP support for PostgreSQL is required for configured $db_type in config.php"); + if (!class_exists("PDO")) { + array_push($errors, "PHP support for PDO is required but was not found."); } if (!function_exists("mb_strlen")) { @@ -90,33 +85,21 @@ print "<div class=\"alert alert-info\">$msg</div>"; } - function db_connect($host, $user, $pass, $db, $type, $port = false) { - if ($type == "pgsql") { - - $string = "dbname=$db user=$user"; + function pdo_connect($host, $user, $pass, $db, $type, $port = false) { - if ($pass) { - $string .= " password=$pass"; - } + $db_port = $port ? ';port=' . $port : ''; + $db_host = $host ? ';host=' . $host : ''; - if ($host) { - $string .= " host=$host"; - } + try { + $pdo = new PDO($type . ':dbname=' . $db . $db_host . $db_port, + $user, + $pass); - if ($port) { - $string = "$string port=" . $port; - } - - $link = pg_connect($string); - - return $link; - - } else if ($type == "mysql") { - if ($port) - return mysqli_connect($host, $user, $pass, $db, $port); - else - return mysqli_connect($host, $user, $pass, $db); - } + return $pdo; + } catch (Exception $e) { + print "<div class='alert alert-danger'>" . $e->getMessage() . "</div>"; + return null; + } } function make_config($DB_TYPE, $DB_HOST, $DB_USER, $DB_NAME, $DB_PASS, @@ -155,30 +138,6 @@ return $rv; } - function db_query($link, $query, $type, $die_on_error = true) { - if ($type == "pgsql") { - $result = pg_query($link, $query); - if (!$result) { - $query = htmlspecialchars($query); // just in case - if ($die_on_error) { - die("Query <i>$query</i> failed [$result]: " . ($link ? pg_last_error($link) : "No connection")); - } - } - return $result; - } else if ($type == "mysql") { - - $result = mysqli_query($link, $query); - - if (!$result) { - $query = htmlspecialchars($query); - if ($die_on_error) { - die("Query <i>$query</i> failed: " . ($link ? mysqli_error($link) : "No connection")); - } - } - return $result; - } - } - function is_server_https() { return (!empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] != 'off')) || $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'; } @@ -317,6 +276,14 @@ array_push($notices, "PHP support for Internationalization Functions is required to handle Internationalized Domain Names."); } + if ($DB_TYPE == "mysql" && !function_exists("mysqli_connect")) { + array_push($notices, "PHP extension for MySQL (mysqli) is missing. This may prevent legacy plugins from working."); + } + + if ($DB_TYPE == "pgsql" && !function_exists("pg_connect")) { + array_push($notices, "PHP extension for PostgreSQL is missing. This may prevent legacy plugins from working."); + } + if (count($notices) > 0) { print_notice("Configuration check succeeded with minor problems:"); @@ -336,9 +303,9 @@ <h2>Checking database</h2> <?php - $link = db_connect($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME, $DB_TYPE, $DB_PORT); + $pdo = pdo_connect($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME, $DB_TYPE, $DB_PORT); - if (!$link) { + if (!$pdo) { print_error("Unable to connect to database using specified parameters."); exit; } @@ -350,10 +317,10 @@ <p>Before you can start using tt-rss, database needs to be initialized. Click on the button below to do that now.</p> <?php - $result = @db_query($link, "SELECT true FROM ttrss_feeds", $DB_TYPE, false); + $res = $pdo->query("SELECT true FROM ttrss_feeds"); - if ($result) { - print_error("Existing tt-rss tables will be removed from the database. If you would like to keep your data, skip database initialization."); + if ($res && $res->fetch()) { + print_error("Some tt-rss data already exists in this database. If you continue with database initialization your current data will be lost."); $need_confirm = true; } else { $need_confirm = false; @@ -399,9 +366,9 @@ } else if ($op == 'installschema' || $op == 'skipschema') { - $link = db_connect($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME, $DB_TYPE, $DB_PORT); + $pdo = pdo_connect($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME, $DB_TYPE, $DB_PORT); - if (!$link) { + if (!$pdo) { print_error("Unable to connect to database using specified parameters."); exit; } @@ -410,11 +377,17 @@ print "<h2>Initializing database...</h2>"; - $lines = explode(";", preg_replace("/[\r\n]/", "", file_get_contents("../schema/ttrss_schema_".basename($DB_TYPE).".sql"))); + $lines = explode(";", preg_replace("/[\r\n]/", "", + file_get_contents("../schema/ttrss_schema_".basename($DB_TYPE).".sql"))); foreach ($lines as $line) { if (strpos($line, "--") !== 0 && $line) { - db_query($link, $line, $DB_TYPE); + $res = $pdo->query($line); + + if (!$res) { + print_notice("Query: $line"); + print_error("Error: " . implode(", ", $this->pdo->errorInfo())); + } } } diff --git a/js/deprecated.js b/js/deprecated.js deleted file mode 100644 index dd286aa7f..000000000 --- a/js/deprecated.js +++ /dev/null @@ -1,23 +0,0 @@ -function selectTableRow(r, do_select) { - - if (do_select) { - r.addClassName("Selected"); - } else { - r.removeClassName("Selected"); - } -} - -function selectTableRowById(elem_id, check_id, do_select) { - var row = $(elem_id); - - if (row) { - selectTableRow(row, do_select); - } - - var check = $(check_id); - - if (check) { - check.checked = do_select; - } -} - diff --git a/js/feedlist.js b/js/feedlist.js index 887d84453..1e783f3b3 100644 --- a/js/feedlist.js +++ b/js/feedlist.js @@ -198,6 +198,28 @@ function feedlist_init() { hideOrShowFeeds(getInitParam("hide_read_feeds") == 1); + if (getInitParam("is_default_pw")) { + console.warn("user password is at default value"); + + var dialog = new dijit.Dialog({ + title: __("Your password is at default value"), + href: "backend.php?op=dlg&method=defaultpasswordwarning", + id: 'infoBox', + style: "width: 600px", + onCancel: function() { + return true; + }, + onExecute: function() { + return true; + }, + onClose: function() { + return true; + } + }); + + dialog.show(); + } + // bw_limit disables timeout() so we request initial counters separately if (getInitParam("bw_limit") == "1") { request_counters(true); @@ -304,7 +326,7 @@ function parse_counters(elems) { if (id > 0) { if (has_img) { setFeedIcon(id, false, - getInitParam("icons_url") + "/" + id + ".ico"); + getInitParam("icons_url") + "/" + id + ".ico?" + has_img); } else { setFeedIcon(id, false, 'images/blank_icon.gif'); } diff --git a/js/functions.js b/js/functions.js index aff20a33c..4c5d67f6c 100755 --- a/js/functions.js +++ b/js/functions.js @@ -96,17 +96,7 @@ function exception_error(e, e_compat, filename, lineno, colno) { } function param_escape(arg) { - if (typeof encodeURIComponent != 'undefined') - return encodeURIComponent(arg); - else - return escape(arg); -} - -function param_unescape(arg) { - if (typeof decodeURIComponent != 'undefined') - return decodeURIComponent(arg); - else - return unescape(arg); + return encodeURIComponent(arg); } function notify_real(msg, no_hide, n_type) { @@ -255,50 +245,11 @@ function gotoMain() { document.location.href = "index.php"; } -/** * @(#)isNumeric.js * * Copyright (c) 2000 by Sundar Dorai-Raj - * * @author Sundar Dorai-Raj - * * Email: [email protected] - * * This program is free software; you can redistribute it and/or - * * modify it under the terms of the GNU General Public License - * * as published by the Free Software Foundation; either version 2 - * * of the License, or (at your option) any later version, - * * provided that any use properly credits the author. - * * This program is distributed in the hope that it will be useful, - * * but WITHOUT ANY WARRANTY; without even the implied warranty of - * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * * GNU General Public License for more details at http://www.gnu.org * * */ - - var numbers=".0123456789"; - function isNumeric(x) { - // is x a String or a character? - if(x.length>1) { - // remove negative sign - x=Math.abs(x)+""; - for(var j=0;j<x.length;j++) { - // call isNumeric recursively for each character - number=isNumeric(x.substring(j,j+1)); - if(!number) return number; - } - return number; - } - else { - // if x is number return true - if(numbers.indexOf(x)>=0) return true; - return false; - } - } - - function toggleSelectRowById(sender, id) { var row = $(id); return toggleSelectRow(sender, row); } -function toggleSelectListRow(sender) { - var row = sender.parentNode; - return toggleSelectRow(sender, row); -} - /* this is for dijit Checkbox */ function toggleSelectListRow2(sender) { var row = sender.domNode.parentNode; @@ -345,20 +296,11 @@ function checkboxToggleElement(elem, id) { } } -function dropboxSelect(e, v) { - for (var i = 0; i < e.length; i++) { - if (e[i].value == v) { - e.selectedIndex = i; - break; - } - } -} - function getURLParam(param){ return String(window.location.href).parseQuery()[param]; } -function closeInfoBox(cleanup) { +function closeInfoBox() { dialog = dijit.byId("infoBox"); if (dialog) dialog.hide(); @@ -530,13 +472,6 @@ function strip_tags(s) { return s.replace(/<\/?[^>]+(>|$)/g, ""); } -function truncate_string(s, length) { - if (!length) length = 30; - var tmp = s.substring(0, length); - if (s.length > length) tmp += "…"; - return tmp; -} - function hotkey_prefix_timeout() { var date = new Date(); @@ -739,7 +674,6 @@ function quickAddFeed() { dialog.show_error(__("XML validation failed: %s"). replace("%s", rc['message'])); break; - break; case 0: dialog.show_error(__("You are already subscribed to this feed.")); break; @@ -1200,31 +1134,6 @@ function backend_sanity_check_callback(transport) { } -function quickAddCat(elem) { - var cat = prompt(__("Please enter category title:")); - - if (cat) { - - var query = "?op=rpc&method=quickAddCat&cat=" + param_escape(cat); - - notify_progress("Loading, please wait...", true); - - new Ajax.Request("backend.php", { - parameters: query, - onComplete: function (transport) { - var response = transport.responseXML; - var select = response.getElementsByTagName("select")[0]; - var options = select.getElementsByTagName("option"); - - dropbox_replace_options(elem, options); - - notify(''); - - } }); - - } -} - function genUrlChangeKey(feed, is_cat) { var ok = confirm(__("Generate new syndication address for this feed?")); @@ -1263,34 +1172,6 @@ function genUrlChangeKey(feed, is_cat) { return false; } -function dropbox_replace_options(elem, options) { - while (elem.hasChildNodes()) - elem.removeChild(elem.firstChild); - - var sel_idx = -1; - - for (var i = 0; i < options.length; i++) { - var text = options[i].firstChild.nodeValue; - var value = options[i].getAttribute("value"); - - if (value == undefined) value = text; - - var issel = options[i].getAttribute("selected") == "1"; - - var option = new Option(text, value, issel); - - if (options[i].getAttribute("disabled")) - option.setAttribute("disabled", true); - - elem.insert(option); - - if (issel) sel_idx = i; - } - - // Chrome doesn't seem to just select stuff when you pass new Option(x, y, true) - if (sel_idx >= 0) elem.selectedIndex = sel_idx; -} - // mode = all, none, invert function selectTableRows(id, mode) { var rows = $(id).rows; @@ -1361,7 +1242,7 @@ function getSelectedTableRowIds(id) { return rows; } -function editFeed(feed, event) { +function editFeed(feed) { if (feed <= 0) return alert(__("You can't edit this kind of feed.")); diff --git a/js/prefs.js b/js/prefs.js index 34ccbac90..a3f32dcbc 100755 --- a/js/prefs.js +++ b/js/prefs.js @@ -9,7 +9,7 @@ function notify_callback2(transport, sticky) { notify_info(transport.responseText, sticky); } -function updateFeedList(sort_key) { +function updateFeedList() { var user_search = $("feed_search"); var search = ""; @@ -83,7 +83,7 @@ function addUser() { } -function editUser(id, event) { +function editUser(id) { var query = "backend.php?op=pref-users&method=edit&id=" + param_escape(id); @@ -401,65 +401,6 @@ function removeSelectedFeeds() { return false; } -function clearSelectedFeeds() { - - var sel_rows = getSelectedFeeds(); - - if (sel_rows.length > 1) { - alert(__("Please select only one feed.")); - return; - } - - if (sel_rows.length > 0) { - - var ok = confirm(__("Erase all non-starred articles in selected feed?")); - - if (ok) { - notify_progress("Clearing selected feed..."); - clearFeedArticles(sel_rows[0]); - } - - } else { - - alert(__("No feeds are selected.")); - - } - - return false; -} - -function purgeSelectedFeeds() { - - var sel_rows = getSelectedFeeds(); - - if (sel_rows.length > 0) { - - var pr = prompt(__("How many days of articles to keep (0 - use default)?"), "0"); - - if (pr != undefined) { - notify_progress("Purging selected feed..."); - - var query = "?op=rpc&method=purge&ids="+ - param_escape(sel_rows.toString()) + "&days=" + pr; - - console.log(query); - - new Ajax.Request("prefs.php", { - parameters: query, - onComplete: function(transport) { - notify(''); - } }); - } - - } else { - - alert(__("No feeds are selected.")); - - } - - return false; -} - function editSelectedUser() { var rows = getSelectedUsers(); @@ -808,7 +749,7 @@ function updateSystemList() { } }); } -function selectTab(id, noupdate, method) { +function selectTab(id, noupdate) { if (!noupdate) { notify_progress("Loading, please wait..."); @@ -1177,107 +1118,6 @@ function opmlRegenKey() { return false; } -function feedActionChange() { - var chooser = $("feedActionChooser"); - var opid = chooser[chooser.selectedIndex].value; - - chooser.selectedIndex = 0; - feedActionGo(opid); -} - -function feedActionGo(op) { - if (op == "facEdit") { - - var rows = getSelectedFeeds(); - - if (rows.length > 1) { - editSelectedFeeds(); - } else { - editSelectedFeed(); - } - } - - if (op == "facClear") { - clearSelectedFeeds(); - } - - if (op == "facPurge") { - purgeSelectedFeeds(); - } - - if (op == "facEditCats") { - editFeedCats(); - } - - if (op == "facRescore") { - rescoreSelectedFeeds(); - } - - if (op == "facUnsubscribe") { - removeSelectedFeeds(); - } -} - -function clearFeedArticles(feed_id) { - - notify_progress("Clearing feed..."); - - var query = "?op=pref-feeds&quiet=1&method=clear&id=" + feed_id; - - new Ajax.Request("backend.php", { - parameters: query, - onComplete: function(transport) { - notify(''); - } }); - - return false; -} - -function rescoreSelectedFeeds() { - - var sel_rows = getSelectedFeeds(); - - if (sel_rows.length > 0) { - - //var ok = confirm(__("Rescore last 100 articles in selected feeds?")); - var ok = confirm(__("Rescore articles in selected feeds?")); - - if (ok) { - notify_progress("Rescoring selected feeds...", true); - - var query = "?op=pref-feeds&method=rescore&quiet=1&ids="+ - param_escape(sel_rows.toString()); - - new Ajax.Request("backend.php", { - parameters: query, - onComplete: function(transport) { - notify_callback2(transport); - } }); - - } - } else { - alert(__("No feeds are selected.")); - } - - return false; -} - -function rescore_all_feeds() { - var ok = confirm(__("Rescore all articles? This operation may take a lot of time.")); - - if (ok) { - notify_progress("Rescoring feeds...", true); - - var query = "?op=pref-feeds&method=rescoreAll&quiet=1"; - - new Ajax.Request("backend.php", { - parameters: query, - onComplete: function(transport) { - notify_callback2(transport); - } }); - } -} - function labelColorReset() { var labels = getSelectedLabels(); @@ -1479,7 +1319,7 @@ function resetCatOrder() { }); } -function editCat(id, item, event) { +function editCat(id, item) { var new_name = prompt(__('Rename category to:'), item.name); if (new_name && new_name != item.name) { @@ -1500,7 +1340,7 @@ function editCat(id, item, event) { } } -function editLabel(id, event) { +function editLabel(id) { var query = "backend.php?op=pref-labels&method=edit&id=" + param_escape(id); diff --git a/js/tt-rss.js b/js/tt-rss.js index b498b5e99..4df1b04ee 100644 --- a/js/tt-rss.js +++ b/js/tt-rss.js @@ -365,7 +365,6 @@ function init_hotkey_actions() { hotkey_actions["open_in_new_window"] = function() { if (getActiveArticleId()) { openArticleInNewWindow(getActiveArticleId()); - return; } }; hotkey_actions["catchup_below"] = function() { @@ -375,13 +374,9 @@ function init_hotkey_actions() { catchupRelativeToArticle(0); }; hotkey_actions["article_scroll_down"] = function() { - var ctr = $("content_insert") ? $("content_insert") : $("headlines-frame"); - scrollArticle(40); }; hotkey_actions["article_scroll_up"] = function() { - var ctr = $("content_insert") ? $("content_insert") : $("headlines-frame"); - scrollArticle(-40); }; hotkey_actions["close_article"] = function() { diff --git a/js/viewfeed.js b/js/viewfeed.js index e0ad2dd32..ffd8fcf46 100755 --- a/js/viewfeed.js +++ b/js/viewfeed.js @@ -423,11 +423,9 @@ function toggleMark(id, client_only) { if (!row.hasClassName("marked")) { img.src = img.src.replace("mark_unset", "mark_set"); - img.alt = __("Unstar article"); query = query + "&mark=1"; } else { img.src = img.src.replace("mark_set", "mark_unset"); - img.alt = __("Star article"); query = query + "&mark=0"; } } @@ -477,11 +475,9 @@ function togglePub(id, client_only, no_effects, note) { if (!row.hasClassName("published") || note != undefined) { img.src = img.src.replace("pub_unset", "pub_set"); - img.alt = __("Unpublish article"); query = query + "&pub=1"; } else { img.src = img.src.replace("pub_set", "pub_unset"); - img.alt = __("Publish article"); query = query + "&pub=0"; } } @@ -15,14 +15,16 @@ $op = $_REQUEST['op']; if ($op == "publish"){ - $key = db_escape_string( $_REQUEST["key"]); + $key = $_REQUEST["key"]; + $pdo = Db::pdo(); - $result = db_query( "SELECT owner_uid + $sth = $pdo->prepare( "SELECT owner_uid FROM ttrss_access_keys WHERE - access_key = '$key' AND feed_id = 'OPML:Publish'"); + access_key = ? AND feed_id = 'OPML:Publish'"); + $sth->execute([$key]); - if (db_num_rows($result) == 1) { - $owner_uid = db_fetch_result($result, 0, "owner_uid"); + if ($row = $sth->fetch()) { + $owner_uid = $row['owner_uid']; $opml = new Opml($_REQUEST); $opml->opml_export("", $owner_uid, true, false); diff --git a/plugins/af_psql_trgm/init.php b/plugins/af_psql_trgm/init.php index 175176e0b..19653c0f3 100644 --- a/plugins/af_psql_trgm/init.php +++ b/plugins/af_psql_trgm/init.php @@ -1,6 +1,7 @@ <?php class Af_Psql_Trgm extends Plugin { + /* @var PluginHost $host */ private $host; function about() { @@ -10,9 +11,9 @@ class Af_Psql_Trgm extends Plugin { } function save() { - $similarity = (float) db_escape_string($_POST["similarity"]); - $min_title_length = (int) db_escape_string($_POST["min_title_length"]); - $enable_globally = checkbox_to_sql_bool($_POST["enable_globally"]) == "true"; + $similarity = (float) $_POST["similarity"]; + $min_title_length = (int) $_POST["min_title_length"]; + $enable_globally = checkbox_to_sql_bool($_POST["enable_globally"]); if ($similarity < 0) $similarity = 0; if ($similarity > 1) $similarity = 1; @@ -44,18 +45,20 @@ class Af_Psql_Trgm extends Plugin { } function showrelated() { - $id = (int) db_escape_string($_REQUEST['param']); + $id = (int) $_REQUEST['param']; $owner_uid = $_SESSION["uid"]; - $result = db_query("SELECT title FROM ttrss_entries, ttrss_user_entries - WHERE ref_id = id AND id = $id AND owner_uid = $owner_uid"); + $sth = $this->pdo->prepare("SELECT title FROM ttrss_entries, ttrss_user_entries + WHERE ref_id = id AND id = ? AND owner_uid = ?"); + $sth->execute([$id, $owner_uid]); - $title = db_fetch_result($result, 0, "title"); + if ($row = $sth->fetch()) { - print "<h2>$title</h2>"; + $title = $row['title']; - $title = db_escape_string($title); - $result = db_query("SELECT ttrss_entries.id AS id, + print "<h2>$title</h2>"; + + $sth = $this->pdo->prepare("SELECT ttrss_entries.id AS id, feed_id, ttrss_entries.title AS title, updated, link, @@ -65,38 +68,42 @@ class Af_Psql_Trgm extends Plugin { ttrss_entries, ttrss_user_entries LEFT JOIN ttrss_feeds ON (ttrss_feeds.id = feed_id) WHERE ttrss_entries.id = ref_id AND - ttrss_user_entries.owner_uid = $owner_uid AND - ttrss_entries.id != $id AND + ttrss_user_entries.owner_uid = ? AND + ttrss_entries.id != ? AND date_entered >= NOW() - INTERVAL '2 weeks' ORDER BY sm DESC, date_entered DESC LIMIT 10"); - print "<ul class=\"browseFeedList\" style=\"border-width : 1px\">"; + $sth->execute([$owner_uid, $id]); + + print "<ul class=\"browseFeedList\" style=\"border-width : 1px\">"; - while ($line = db_fetch_assoc($result)) { - print "<li>"; - print "<div class='insensitive small' style='margin-left : 20px; float : right'>" . - smart_date_time(strtotime($line["updated"])) - . "</div>"; + while ($line = $sth->fetch()) { + print "<li>"; + print "<div class='insensitive small' style='margin-left : 20px; float : right'>" . + smart_date_time(strtotime($line["updated"])) + . "</div>"; - $sm = sprintf("%.2f", $line['sm']); - print "<img src='images/score_high.png' title='$sm' + $sm = sprintf("%.2f", $line['sm']); + print "<img src='images/score_high.png' title='$sm' style='vertical-align : middle'>"; - $article_link = htmlspecialchars($line["link"]); - print " <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"$article_link\">". - $line["title"]."</a>"; + $article_link = htmlspecialchars($line["link"]); + print " <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"$article_link\">". + $line["title"]."</a>"; - print " (<a href=\"#\" onclick=\"viewfeed({feed:".$line["feed_id"]."})\">". - htmlspecialchars($line["feed_title"])."</a>)"; + print " (<a href=\"#\" onclick=\"viewfeed({feed:".$line["feed_id"]."})\">". + htmlspecialchars($line["feed_title"])."</a>)"; - print " <span class='insensitive'>($sm)</span>"; + print " <span class='insensitive'>($sm)</span>"; - print "</li>"; - } + print "</li>"; + } - print "</ul>"; + print "</ul>"; + + } print "<div style='text-align : center'>"; print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('trgmRelatedDlg').hide()\">".__('Close this window')."</button>"; @@ -121,9 +128,9 @@ class Af_Psql_Trgm extends Plugin { print_error("Database type not supported."); } else { - $result = db_query("select 'similarity'::regproc"); + $res = $this->pdo->query("select 'similarity'::regproc"); - if (db_num_rows($result) == 0) { + if (!$res->fetch()) { print_error("pg_trgm extension not found."); } @@ -226,7 +233,7 @@ class Af_Psql_Trgm extends Plugin { $enabled_feeds = $this->host->get($this, "enabled_feeds"); if (!is_array($enabled_feeds)) $enabled_feeds = array(); - $enable = checkbox_to_sql_bool($_POST["trgm_similarity_enabled"]) == 'true'; + $enable = checkbox_to_sql_bool($_POST["trgm_similarity_enabled"]); $key = array_search($feed_id, $enabled_feeds); if ($enable) { @@ -246,8 +253,8 @@ class Af_Psql_Trgm extends Plugin { if (DB_TYPE != "pgsql") return $article; - $result = db_query("select 'similarity'::regproc"); - if (db_num_rows($result) == 0) return $article; + $res = $this->pdo->query("select 'similarity'::regproc"); + if (!$res->fetch()) return $article; $enable_globally = $this->host->get($this, "enable_globally"); @@ -265,18 +272,21 @@ class Af_Psql_Trgm extends Plugin { $owner_uid = $article["owner_uid"]; $entry_guid = $article["guid_hashed"]; - $title_escaped = db_escape_string($article["title"]); + $title_escaped = $article["title"]; // trgm does not return similarity=1 for completely equal strings - $result = db_query("SELECT COUNT(id) AS nequal + $sth = $this->pdo->prepare("SELECT COUNT(id) AS nequal FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id AND date_entered >= NOW() - interval '3 days' AND - title = '$title_escaped' AND - guid != '$entry_guid' AND - owner_uid = $owner_uid"); + title = ? AND + guid != ? AND + owner_uid = ?"); + $sth->execute([$title_escaped, $entry_guid, $owner_uid]); + + $row = $sth->fetch(); + $nequal = $row['nequal']; - $nequal = db_fetch_result($result, 0, "nequal"); _debug("af_psql_trgm: num equals: $nequal"); if ($nequal != 0) { @@ -284,13 +294,15 @@ class Af_Psql_Trgm extends Plugin { return $article; } - $result = db_query("SELECT MAX(SIMILARITY(title, '$title_escaped')) AS ms + $sth = $this->pdo->prepare("SELECT MAX(SIMILARITY(title, ?)) AS ms FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id AND date_entered >= NOW() - interval '1 day' AND - guid != '$entry_guid' AND - owner_uid = $owner_uid"); + guid != ? AND + owner_uid = ?"); + $sth->execute([$title_escaped, $entry_guid, $owner_uid]); - $similarity_result = db_fetch_result($result, 0, "ms"); + $row = $sth->fetch(); + $similarity_result = $row['ms']; _debug("af_psql_trgm: similarity result: $similarity_result"); @@ -311,9 +323,10 @@ class Af_Psql_Trgm extends Plugin { foreach ($enabled_feeds as $feed) { - $result = db_query("SELECT id FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE id = ? AND owner_uid = ?"); + $sth->execute([$feed, $_SESSION['uid']]); - if (db_num_rows($result) != 0) { + if ($row = $sth->fetch()) { array_push($tmp, $feed); } } diff --git a/plugins/af_readability/init.php b/plugins/af_readability/init.php index 192ad90b6..10de118f9 100755 --- a/plugins/af_readability/init.php +++ b/plugins/af_readability/init.php @@ -1,6 +1,7 @@ <?php class Af_Readability extends Plugin { + /* @var PluginHost $host */ private $host; function about() { @@ -14,7 +15,7 @@ class Af_Readability extends Plugin { } function save() { - $enable_share_anything = checkbox_to_sql_bool($_POST["enable_share_anything"]) == "true"; + $enable_share_anything = checkbox_to_sql_bool($_POST["enable_share_anything"]); $this->host->set($this, "enable_share_anything", $enable_share_anything); @@ -112,7 +113,7 @@ class Af_Readability extends Plugin { $enabled_feeds = $this->host->get($this, "enabled_feeds"); if (!is_array($enabled_feeds)) $enabled_feeds = array(); - $enable = checkbox_to_sql_bool($_POST["af_readability_enabled"]) == 'true'; + $enable = checkbox_to_sql_bool($_POST["af_readability_enabled"]); $key = array_search($feed_id, $enabled_feeds); if ($enable) { @@ -235,9 +236,10 @@ class Af_Readability extends Plugin { foreach ($enabled_feeds as $feed) { - $result = db_query("SELECT id FROM ttrss_feeds WHERE id = '$feed' AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE id = ? AND owner_uid = ?"); + $sth->execute([$feed, $_SESSION['uid']]); - if (db_num_rows($result) != 0) { + if ($row = $sth->fetch()) { array_push($tmp, $feed); } } diff --git a/plugins/af_redditimgur/init.php b/plugins/af_redditimgur/init.php index 8074894fd..59e3a760c 100755 --- a/plugins/af_redditimgur/init.php +++ b/plugins/af_redditimgur/init.php @@ -1,5 +1,7 @@ <?php class Af_RedditImgur extends Plugin { + + /* @var PluginHost $host */ private $host; function about() { @@ -64,8 +66,8 @@ class Af_RedditImgur extends Plugin { } function save() { - $enable_readability = checkbox_to_sql_bool($_POST["enable_readability"]) == "true"; - $enable_content_dupcheck = checkbox_to_sql_bool($_POST["enable_content_dupcheck"]) == "true"; + $enable_readability = checkbox_to_sql_bool($_POST["enable_readability"]); + $enable_content_dupcheck = checkbox_to_sql_bool($_POST["enable_content_dupcheck"]); $this->host->set($this, "enable_readability", $enable_readability, false); $this->host->set($this, "enable_content_dupcheck", $enable_content_dupcheck); @@ -379,8 +381,8 @@ class Af_RedditImgur extends Plugin { if ($this->host->get($this, "enable_content_dupcheck")) { if ($content_link) { - $content_href = db_escape_string($content_link->getAttribute("href")); - $entry_guid = db_escape_string($article["guid_hashed"]); + $content_href = $content_link->getAttribute("href"); + $entry_guid = $article["guid_hashed"]; $owner_uid = $article["owner_uid"]; if (DB_TYPE == "pgsql") { @@ -389,16 +391,18 @@ class Af_RedditImgur extends Plugin { $interval_qpart = "date_entered < DATE_SUB(NOW(), INTERVAL 1 DAY)"; } - $result = db_query("SELECT COUNT(id) AS cid + $sth = $this->pdo->prepare("SELECT COUNT(id) AS cid FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id AND $interval_qpart AND - guid != '$entry_guid' AND - owner_uid = '$owner_uid' AND - content LIKE '%href=\"$content_href\">[link]%'"); + guid != ? AND + owner_uid = ? AND + content LIKE ?"); + + $sth->execute([$entry_guid, $owner_uid, "%href=\"$content_href\">[link]%"]); - if ($result) { - $num_found = db_fetch_result($result, 0, "cid"); + if ($row = $sth->fetch()) { + $num_found = $row['cid']; if ($num_found > 0) $article["force_catchup"] = true; } diff --git a/plugins/af_zz_imgproxy/init.php b/plugins/af_zz_imgproxy/init.php index 4cfca4180..935cbdbcf 100644 --- a/plugins/af_zz_imgproxy/init.php +++ b/plugins/af_zz_imgproxy/init.php @@ -1,5 +1,7 @@ <?php class Af_Zz_ImgProxy extends Plugin { + + /* @var PluginHost $host */ private $host; function about() { @@ -241,8 +243,8 @@ class Af_Zz_ImgProxy extends Plugin { } function save() { - $proxy_all = checkbox_to_sql_bool($_POST["proxy_all"]) == "true"; - $disable_cache = checkbox_to_sql_bool($_POST["disable_cache"]) == "true"; + $proxy_all = checkbox_to_sql_bool($_POST["proxy_all"]); + $disable_cache = checkbox_to_sql_bool($_POST["disable_cache"]); $this->host->set($this, "proxy_all", $proxy_all, false); $this->host->set($this, "disable_cache", $disable_cache); diff --git a/plugins/auth_internal/init.php b/plugins/auth_internal/init.php index dc49229f6..4cc473304 100644 --- a/plugins/auth_internal/init.php +++ b/plugins/auth_internal/init.php @@ -1,5 +1,6 @@ <?php class Auth_Internal extends Plugin implements IAuthModule { + private $host; function about() { @@ -9,8 +10,10 @@ class Auth_Internal extends Plugin implements IAuthModule { true); } - function init($host) { + /* @var PluginHost $host */ + function init($host) { $this->host = $host; + $this->pdo = Db::pdo(); $host->add_hook($host::HOOK_AUTH_USER, $this); } @@ -19,16 +22,16 @@ class Auth_Internal extends Plugin implements IAuthModule { $pwd_hash1 = encrypt_password($password); $pwd_hash2 = encrypt_password($password, $login); - $login = db_escape_string($login); - $otp = db_escape_string($_REQUEST["otp"]); + $otp = $_REQUEST["otp"]; if (get_schema_version() > 96) { if (!defined('AUTH_DISABLE_OTP') || !AUTH_DISABLE_OTP) { - $result = db_query("SELECT otp_enabled,salt FROM ttrss_users WHERE - login = '$login'"); + $sth = $this->pdo->prepare("SELECT otp_enabled,salt FROM ttrss_users WHERE + login = ?"); + $sth->execute([$login]); - if (db_num_rows($result) > 0) { + if ($row = $sth->fetch()) { require_once "lib/otphp/vendor/base32.php"; require_once "lib/otphp/lib/otp.php"; @@ -36,8 +39,8 @@ class Auth_Internal extends Plugin implements IAuthModule { $base32 = new Base32(); - $otp_enabled = sql_bool_to_bool(db_fetch_result($result, 0, "otp_enabled")); - $secret = $base32->encode(sha1(db_fetch_result($result, 0, "salt"))); + $otp_enabled = $row['otp_enabled']; + $secret = $base32->encode(sha1($row['salt'])); $topt = new \OTPHP\TOTP($secret); $otp_check = $topt->now(); @@ -50,9 +53,12 @@ class Auth_Internal extends Plugin implements IAuthModule { } else { $return = urlencode($_REQUEST["return"]); ?><html> - <head><title>Tiny Tiny RSS</title></head> - <?php echo stylesheet_tag("css/utility.css") ?> - <body class="otp"><div class="content"> + <head> + <title>Tiny Tiny RSS</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + </head> + <?php echo stylesheet_tag("css/default.css") ?> + <body class="ttrss_utility otp"><div class="content"> <form action="public.php?return=<?php echo $return ?>" method="POST" class="otpform"> <input type="hidden" name="op" value="login"> @@ -79,109 +85,127 @@ class Auth_Internal extends Plugin implements IAuthModule { if (get_schema_version() > 87) { - $result = db_query("SELECT salt FROM ttrss_users WHERE - login = '$login'"); + $sth = $this->pdo->prepare("SELECT salt FROM ttrss_users WHERE login = ?"); + $sth->execute([$login]); - if (db_num_rows($result) != 1) { - return false; - } + if ($row = $sth->fetch()) { + $salt = $row['salt']; - $salt = db_fetch_result($result, 0, "salt"); + if ($salt == "") { - if ($salt == "") { + $sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE + login = ? AND (pwd_hash = ? OR pwd_hash = ?)"); - $query = "SELECT id - FROM ttrss_users WHERE - login = '$login' AND (pwd_hash = '$pwd_hash1' OR - pwd_hash = '$pwd_hash2')"; + $sth->execute([$login, $pwd_hash1, $pwd_hash2]); - // verify and upgrade password to new salt base + // verify and upgrade password to new salt base - $result = db_query($query); + if ($row = $sth->fetch()) { + // upgrade password to MODE2 - if (db_num_rows($result) == 1) { - // upgrade password to MODE2 + $user_id = $row['id']; - $salt = substr(bin2hex(get_random_bytes(125)), 0, 250); - $pwd_hash = encrypt_password($password, $salt, true); + $salt = substr(bin2hex(get_random_bytes(125)), 0, 250); + $pwd_hash = encrypt_password($password, $salt, true); + + $sth = $this->pdo->prepare("UPDATE ttrss_users SET + pwd_hash = ?, salt = ? WHERE login = ?"); - db_query("UPDATE ttrss_users SET - pwd_hash = '$pwd_hash', salt = '$salt' WHERE login = '$login'"); + $sth->execute([$pwd_hash, $salt, $login]); - $query = "SELECT id - FROM ttrss_users WHERE - login = '$login' AND pwd_hash = '$pwd_hash'"; + return $user_id; + + } else { + return false; + } } else { - return false; + $pwd_hash = encrypt_password($password, $salt, true); + + $sth = $this->pdo->prepare("SELECT id + FROM ttrss_users WHERE + login = ? AND pwd_hash = ?"); + $sth->execute([$login, $pwd_hash]); + + if ($row = $sth->fetch()) { + return $row['id']; + } } } else { + $sth = $this->pdo->prepare("SELECT id + FROM ttrss_users WHERE + login = ? AND (pwd_hash = ? OR pwd_hash = ?)"); - $pwd_hash = encrypt_password($password, $salt, true); - - $query = "SELECT id - FROM ttrss_users WHERE - login = '$login' AND pwd_hash = '$pwd_hash'"; + $sth->execute([$login, $pwd_hash1, $pwd_hash2]); + if ($row = $sth->fetch()) { + return $row['id']; + } } - } else { - $query = "SELECT id - FROM ttrss_users WHERE - login = '$login' AND (pwd_hash = '$pwd_hash1' OR - pwd_hash = '$pwd_hash2')"; - } + $sth = $this->pdo->prepare("SELECT id + FROM ttrss_users WHERE + login = ? AND (pwd_hash = ? OR pwd_hash = ?)"); - $result = db_query($query); + $sth->execute([$login, $pwd_hash1, $pwd_hash2]); - if (db_num_rows($result) == 1) { - return db_fetch_result($result, 0, "id"); - } + if ($row = $sth->fetch()) { + return $row['id']; + } + } return false; } function check_password($owner_uid, $password) { - $owner_uid = db_escape_string($owner_uid); - $result = db_query("SELECT salt,login FROM ttrss_users WHERE - id = '$owner_uid'"); + $sth = $this->pdo->prepare("SELECT salt,login FROM ttrss_users WHERE + id = ?"); + $sth->execute([$owner_uid]); - $salt = db_fetch_result($result, 0, "salt"); - $login = db_fetch_result($result, 0, "login"); + if ($row = $sth->fetch()) { - if (!$salt) { - $password_hash1 = encrypt_password($password); - $password_hash2 = encrypt_password($password, $login); + $salt = $row['salt']; + $login = $row['login']; - $query = "SELECT id FROM ttrss_users WHERE - id = '$owner_uid' AND (pwd_hash = '$password_hash1' OR - pwd_hash = '$password_hash2')"; + if (!$salt) { + $password_hash1 = encrypt_password($password); + $password_hash2 = encrypt_password($password, $login); - } else { - $password_hash = encrypt_password($password, $salt, true); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE + id = ? AND (pwd_hash = ? OR pwd_hash = ?)"); - $query = "SELECT id FROM ttrss_users WHERE - id = '$owner_uid' AND pwd_hash = '$password_hash'"; - } + $sth->execute([$owner_uid, $password_hash1, $password_hash2]); + + return $sth->fetch(); - $result = db_query($query); + } else { + $password_hash = encrypt_password($password, $salt, true); + + $sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE + id = ? AND pwd_hash = ?"); + + $sth->execute([$owner_uid, $password_hash]); + + return $sth->fetch(); + } + } - return db_num_rows($result) != 0; + return false; } function change_password($owner_uid, $old_password, $new_password) { - $owner_uid = db_escape_string($owner_uid); if ($this->check_password($owner_uid, $old_password)) { $new_salt = substr(bin2hex(get_random_bytes(125)), 0, 250); $new_password_hash = encrypt_password($new_password, $new_salt, true); - db_query("UPDATE ttrss_users SET - pwd_hash = '$new_password_hash', salt = '$new_salt', otp_enabled = false - WHERE id = '$owner_uid'"); + $sth = $this->pdo->prepare("UPDATE ttrss_users SET + pwd_hash = ?, salt = ?, otp_enabled = false + WHERE id = ?"); + $sth->execute([$new_password_hash, $new_salt, $owner_uid]); $_SESSION["pwd_hash"] = $new_password_hash; diff --git a/plugins/auth_remote/init.php b/plugins/auth_remote/init.php index dd04dcd10..bad1af65e 100644 --- a/plugins/auth_remote/init.php +++ b/plugins/auth_remote/init.php @@ -2,6 +2,7 @@ class Auth_Remote extends Plugin implements IAuthModule { private $host; + /* @var Auth_Base $base */ private $base; function about() { @@ -11,7 +12,8 @@ class Auth_Remote extends Plugin implements IAuthModule { true); } - function init($host) { + /* @var PluginHost $host */ + function init($host ) { $this->host = $host; $this->base = new Auth_Base(); @@ -19,15 +21,16 @@ class Auth_Remote extends Plugin implements IAuthModule { } function get_login_by_ssl_certificate() { - $cert_serial = db_escape_string(get_ssl_certificate_id()); + $cert_serial = get_ssl_certificate_id(); if ($cert_serial) { - $result = db_query("SELECT login FROM ttrss_user_prefs, ttrss_users - WHERE pref_name = 'SSL_CERT_SERIAL' AND value = '$cert_serial' AND + $sth = $this->pdo->prepare("SELECT login FROM ttrss_user_prefs, ttrss_users + WHERE pref_name = 'SSL_CERT_SERIAL' AND value = ? AND owner_uid = ttrss_users.id"); + $sth->execute([$cert_serial]); - if (db_num_rows($result) != 0) { - return db_escape_string(db_fetch_result($result, 0, "login")); + if ($row = $sth->fetch()) { + return $row['login']; } } @@ -38,11 +41,11 @@ class Auth_Remote extends Plugin implements IAuthModule { * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ function authenticate($login, $password) { - $try_login = db_escape_string($_SERVER["REMOTE_USER"]); + $try_login = $_SERVER["REMOTE_USER"]; // php-cgi - if (!$try_login) $try_login = db_escape_string($_SERVER["REDIRECT_REMOTE_USER"]); - if (!$try_login) $try_login = db_escape_string($_SERVER["PHP_AUTH_USER"]); + if (!$try_login) $try_login = $_SERVER["REDIRECT_REMOTE_USER"]; + if (!$try_login) $try_login = $_SERVER["PHP_AUTH_USER"]; if (!$try_login) $try_login = $this->get_login_by_ssl_certificate(); @@ -60,16 +63,14 @@ class Auth_Remote extends Plugin implements IAuthModule { // update user name $fullname = $_SERVER['HTTP_USER_NAME'] ? $_SERVER['HTTP_USER_NAME'] : $_SERVER['AUTHENTICATE_CN']; if ($fullname){ - $fullname = db_escape_string($fullname); - db_query("UPDATE ttrss_users SET full_name = '$fullname' WHERE id = " . - $user_id); + $sth = $this->pdo->prepare("UPDATE ttrss_users SET full_name = ? WHERE id = ?"); + $sth->execute([$fullname, $user_id]); } // update user mail $email = $_SERVER['HTTP_USER_MAIL'] ? $_SERVER['HTTP_USER_MAIL'] : $_SERVER['AUTHENTICATE_MAIL']; if ($email){ - $email = db_escape_string($email); - db_query("UPDATE ttrss_users SET email = '$email' WHERE id = " . - $user_id); + $sth = $this->pdo->prepare("UPDATE ttrss_users SET email = ? WHERE id = ?"); + $sth->execute([$email, $user_id]); } } diff --git a/plugins/auto_assign_labels/init.php b/plugins/auto_assign_labels/init.php index 911adb6b8..cc70e2dff 100644 --- a/plugins/auto_assign_labels/init.php +++ b/plugins/auto_assign_labels/init.php @@ -1,6 +1,7 @@ <?php class Auto_Assign_Labels extends Plugin { + /* @var PluginHost $host */ private $host; function about() { @@ -18,9 +19,10 @@ class Auto_Assign_Labels extends Plugin { function get_all_labels_filter_format($owner_uid) { $rv = array(); - $result = db_query("SELECT id, fg_color, bg_color, caption FROM ttrss_labels2 WHERE owner_uid = " . $owner_uid); + $sth = $this->pdo->prepare("SELECT id, fg_color, bg_color, caption FROM ttrss_labels2 WHERE owner_uid = ?"); + $sth->execute([$owner_uid]); - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { array_push($rv, array(Labels::label_to_feed_id($line["id"]), $line["caption"], $line["fg_color"], $line["bg_color"])); } diff --git a/plugins/cache_starred_images/init.php b/plugins/cache_starred_images/init.php index 82e7d1fbd..6c32ff57f 100644 --- a/plugins/cache_starred_images/init.php +++ b/plugins/cache_starred_images/init.php @@ -1,6 +1,7 @@ <?php class Cache_Starred_Images extends Plugin implements IHandler { + /* @var PluginHost $host */ private $host; private $cache_dir; @@ -92,11 +93,11 @@ class Cache_Starred_Images extends Plugin implements IHandler { if ($article_id != $last_article_id) { $last_article_id = $article_id; - $article_id = db_escape_string($article_id); - $result = db_query("SELECT id FROM ttrss_entries WHERE id = " . $article_id); + $sth = $this->pdo->prepare("SELECT id FROM ttrss_entries WHERE id = ?"); + $sth->execute([$article_id]); - $article_exists = db_num_rows($result) > 0; + $article_exists = $sth->fetch(); } if (!$article_exists) { @@ -135,7 +136,7 @@ class Cache_Starred_Images extends Plugin implements IHandler { } function hook_update_task() { - $result = db_query("SELECT content, ttrss_user_entries.owner_uid, link, site_url, ttrss_entries.id, plugin_data + $res = $this->pdo->query("SELECT content, ttrss_user_entries.owner_uid, link, site_url, ttrss_entries.id, plugin_data FROM ttrss_entries, ttrss_user_entries LEFT JOIN ttrss_feeds ON (ttrss_user_entries.feed_id = ttrss_feeds.id) WHERE ref_id = ttrss_entries.id AND @@ -145,14 +146,16 @@ class Cache_Starred_Images extends Plugin implements IHandler { plugin_data NOT LIKE '%starred_cache_images%' ORDER BY ".sql_random_function()." LIMIT 100"); - while ($line = db_fetch_assoc($result)) { + $usth = $this->pdo->prepare("UPDATE ttrss_entries SET plugin_data = ? WHERE id = ?"); + + while ($line = $res->fetch()) { if ($line["site_url"]) { $success = $this->cache_article_images($line["content"], $line["site_url"], $line["owner_uid"], $line["id"]); if ($success) { - $plugin_data = db_escape_string("starred_cache_images,${line['owner_uid']}:" . $line["plugin_data"]); + $plugin_data = "starred_cache_images,${line['owner_uid']}:" . $line["plugin_data"]; - db_query("UPDATE ttrss_entries SET plugin_data = '$plugin_data' WHERE id = " . $line["id"]); + $usth->execute([$plugin_data, $line['id']]); } } } diff --git a/plugins/embed_original/init.php b/plugins/embed_original/init.php index a74905f8f..1925d141d 100644 --- a/plugins/embed_original/init.php +++ b/plugins/embed_original/init.php @@ -1,5 +1,7 @@ <?php class Embed_Original extends Plugin { + + /* @var PluginHost $host */ private $host; function init($host) { @@ -34,17 +36,17 @@ class Embed_Original extends Plugin { } function getUrl() { - $id = db_escape_string($_REQUEST['id']); + $id = $_REQUEST['id']; - $result = db_query("SELECT link + $sth = $this->pdo->prepare("SELECT link FROM ttrss_entries, ttrss_user_entries - WHERE id = '$id' AND ref_id = id AND owner_uid = " .$_SESSION['uid']); - - $url = ""; - - if (db_num_rows($result) != 0) { - $url = db_fetch_result($result, 0, "link"); + WHERE id = ? AND ref_id = id AND owner_uid = ?"); + $sth->execute([$id, $_SESSION['uid']]); + if ($row = $sth->fetch()) { + $url = $row['link']; + } else { + $url = ""; } print json_encode(array("url" => $url, "id" => $id)); diff --git a/plugins/mail/init.php b/plugins/mail/init.php index 5fa8c8e1c..045fd7986 100644 --- a/plugins/mail/init.php +++ b/plugins/mail/init.php @@ -1,6 +1,7 @@ <?php class Mail extends Plugin { + /* @var PluginHost $host */ private $host; function about() { @@ -21,7 +22,7 @@ class Mail extends Plugin { } function save() { - $addresslist = db_escape_string($_POST["addresslist"]); + $addresslist = $_POST["addresslist"]; $this->host->set($this, "addresslist", $addresslist); @@ -77,17 +78,21 @@ class Mail extends Plugin { function emailArticle() { - $param = db_escape_string($_REQUEST['param']); + $ids = explode(",", $_REQUEST['param']); + $ids_qmarks = arr_qmarks($ids); print_hidden("op", "pluginhandler"); print_hidden("plugin", "mail"); print_hidden("method", "sendEmail"); - $result = db_query("SELECT email, full_name FROM ttrss_users WHERE + $sth = $this->pdo->prepare("SELECT email, full_name FROM ttrss_users WHERE id = " . $_SESSION["uid"]); + $sth->execute([$_SESSION['uid']]); - $user_email = htmlspecialchars(db_fetch_result($result, 0, "email")); - $user_name = htmlspecialchars(db_fetch_result($result, 0, "full_name")); + if ($row = $sth->fetch()) { + $user_email = htmlspecialchars($row['email']); + $user_name = htmlspecialchars($row['full_name']); + } if (!$user_name) $user_name = $_SESSION['name']; @@ -104,15 +109,16 @@ class Mail extends Plugin { $tpl->setVariable('USER_EMAIL', $user_email, true); $tpl->setVariable('TTRSS_HOST', $_SERVER["HTTP_HOST"], true); - $result = db_query("SELECT DISTINCT link, content, title, note + $sth = $this->pdo->prepare("SELECT DISTINCT link, content, title, note FROM ttrss_user_entries, ttrss_entries WHERE id = ref_id AND - id IN ($param) AND owner_uid = " . $_SESSION["uid"]); + id IN ($ids_qmarks) AND owner_uid = ?"); + $sth->execute(array_merge($ids, [$_SESSION['uid']])); - if (db_num_rows($result) > 1) { + if (count($ids) > 1) { $subject = __("[Forwarded]") . " " . __("Multiple articles"); } - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { if (!$subject) $subject = __("[Forwarded]") . " " . htmlspecialchars($line["title"]); @@ -199,7 +205,7 @@ class Mail extends Plugin { if (!$rc) { $reply['error'] = $mail->ErrorInfo; } else { - //save_email_address(db_escape_string($destination)); + //save_email_address($destination); $reply['message'] = "UPDATE_COUNTERS"; } @@ -207,7 +213,7 @@ class Mail extends Plugin { } /* function completeEmails() { - $search = db_escape_string($_REQUEST["search"]); + $search = $_REQUEST["search"]; print "<ul>"; diff --git a/plugins/mailto/init.php b/plugins/mailto/init.php index 83e643231..60c58b707 100644 --- a/plugins/mailto/init.php +++ b/plugins/mailto/init.php @@ -27,7 +27,8 @@ class MailTo extends Plugin { function emailArticle() { - $param = db_escape_string($_REQUEST['param']); + $ids = explode(",", $_REQUEST['param']); + $ids_qmarks = arr_qmarks($ids); require_once "lib/MiniTemplator.class.php"; @@ -40,15 +41,18 @@ class MailTo extends Plugin { $tpl->setVariable('TTRSS_HOST', $_SERVER["HTTP_HOST"], true); - $result = db_query("SELECT DISTINCT link, content, title + $sth = $this->pdo->prepare("SELECT DISTINCT link, content, title FROM ttrss_user_entries, ttrss_entries WHERE id = ref_id AND - id IN ($param) AND owner_uid = " . $_SESSION["uid"]); + id IN ($ids_qmarks) AND owner_uid = ?"); + $sth->execute(array_merge($ids, [$_SESSION['uid']])); - if (db_num_rows($result) > 1) { + if (count($ids) > 1) { $subject = __("[Forwarded]") . " " . __("Multiple articles"); + } else { + $subject = ""; } - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { if (!$subject) $subject = __("[Forwarded]") . " " . htmlspecialchars($line["title"]); diff --git a/plugins/note/init.php b/plugins/note/init.php index 65cdf30e4..354591b75 100644 --- a/plugins/note/init.php +++ b/plugins/note/init.php @@ -1,5 +1,7 @@ <?php class Note extends Plugin { + + /* @var PluginHost $host */ private $host; function about() { @@ -27,24 +29,29 @@ class Note extends Plugin { } function edit() { - $param = db_escape_string($_REQUEST['param']); + $param = $_REQUEST['param']; + + $sth = $this->pdo->prepare("SELECT note FROM ttrss_user_entries WHERE + ref_id = ? AND owner_uid = ?"); + $sth->execute([$param, $_SESSION['uid']]); + + if ($row = $sth->fetch()) { - $result = db_query("SELECT note FROM ttrss_user_entries WHERE - ref_id = '$param' AND owner_uid = " . $_SESSION['uid']); + $note = $row['note']; - $note = db_fetch_result($result, 0, "note"); + print_hidden("id", "$param"); + print_hidden("op", "pluginhandler"); + print_hidden("method", "setNote"); + print_hidden("plugin", "note"); - print_hidden("id", "$param"); - print_hidden("op", "pluginhandler"); - print_hidden("method", "setNote"); - print_hidden("plugin", "note"); + print "<table width='100%'><tr><td>"; + print "<textarea dojoType=\"dijit.form.SimpleTextarea\" + style='font-size : 12px; width : 98%; height: 100px;' + placeHolder='body#ttrssMain { font-size : 14px; };' + name='note'>$note</textarea>"; + print "</td></tr></table>"; - print "<table width='100%'><tr><td>"; - print "<textarea dojoType=\"dijit.form.SimpleTextarea\" - style='font-size : 12px; width : 98%; height: 100px;' - placeHolder='body#ttrssMain { font-size : 14px; };' - name='note'>$note</textarea>"; - print "</td></tr></table>"; + } print "<div class='dlgButtons'>"; print "<button dojoType=\"dijit.form.Button\" @@ -56,11 +63,12 @@ class Note extends Plugin { } function setNote() { - $id = db_escape_string($_REQUEST["id"]); - $note = trim(strip_tags(db_escape_string($_REQUEST["note"]))); + $id = $_REQUEST["id"]; + $note = trim(strip_tags($_REQUEST["note"])); - db_query("UPDATE ttrss_user_entries SET note = '$note' - WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET note = ? + WHERE ref_id = ? AND owner_uid = ?"); + $sth->execute([$note, $id, $_SESSION['uid']]); $formatted_note = Article::format_article_note($id, $note); diff --git a/plugins/share/init.php b/plugins/share/init.php index 133f09447..84bc78eb4 100644 --- a/plugins/share/init.php +++ b/plugins/share/init.php @@ -8,6 +8,7 @@ class Share extends Plugin { "fox"); } + /* @var PluginHost $host */ function init($host) { $this->host = $host; @@ -25,10 +26,11 @@ class Share extends Plugin { function unshare() { - $id = db_escape_string($_REQUEST['id']); + $id = $_REQUEST['id']; - db_query("UPDATE ttrss_user_entries SET uuid = '' WHERE int_id = '$id' - AND owner_uid = " . $_SESSION['uid']); + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET uuid = '' WHERE int_id = ? + AND owner_uid = ?"); + $sth->execute([$id, $_SESSION['uid']]); print "OK"; } @@ -48,20 +50,21 @@ class Share extends Plugin { // Silent function clearArticleKeys() { - db_query("UPDATE ttrss_user_entries SET uuid = '' WHERE - owner_uid = " . $_SESSION["uid"]); + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET uuid = '' WHERE + owner_uid = ?"); + $sth->execute([$_SESSION['uid']]); return; } function newkey() { - $id = db_escape_string($_REQUEST['id']); + $id = $_REQUEST['id']; + $uuid = uniqid_short(); - $uuid = db_escape_string(uniqid_short()); - - db_query("UPDATE ttrss_user_entries SET uuid = '$uuid' WHERE int_id = '$id' - AND owner_uid = " . $_SESSION['uid']); + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET uuid = ? WHERE int_id = ? + AND owner_uid = ?"); + $sth->execute([$uuid, $id, $_SESSION['uid']]); print json_encode(array("link" => $uuid)); } @@ -76,21 +79,22 @@ class Share extends Plugin { } function shareArticle() { - $param = db_escape_string($_REQUEST['param']); + $param = $_REQUEST['param']; - $result = db_query("SELECT uuid FROM ttrss_user_entries WHERE int_id = '$param' - AND owner_uid = " . $_SESSION['uid']); + $sth = $this->pdo->prepare("SELECT uuid FROM ttrss_user_entries WHERE int_id = ? + AND owner_uid = ?"); + $sth->execute([$param, $_SESSION['uid']]); - if (db_num_rows($result) == 0) { - print "Article not found."; - } else { + if ($row = $sth->fetch()) { - $uuid = db_fetch_result($result, 0, "uuid"); + $uuid = $row['uuid']; if (!$uuid) { - $uuid = db_escape_string(uniqid_short()); - db_query("UPDATE ttrss_user_entries SET uuid = '$uuid' WHERE int_id = '$param' - AND owner_uid = " . $_SESSION['uid']); + $uuid = uniqid_short(); + + $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET uuid = ? WHERE int_id = ? + AND owner_uid = ?"); + $sth->execute([$uuid, $param, $_SESSION['uid']]); } print __("You can share this article by the following unique URL:") . "<br/>"; @@ -106,6 +110,10 @@ class Share extends Plugin { label_create(__('Shared'), $_SESSION["uid"]); label_add_article($ref_id, __('Shared'), $_SESSION['uid']); */ + + + } else { + print "Article not found."; } print "<div align='center'>"; diff --git a/plugins/vf_shared/init.php b/plugins/vf_shared/init.php index ce18f92d5..a3b0daeb6 100644 --- a/plugins/vf_shared/init.php +++ b/plugins/vf_shared/init.php @@ -1,6 +1,7 @@ <?php class VF_Shared extends Plugin { + /* @var PluginHost $host */ private $host; function about() { @@ -24,18 +25,30 @@ class VF_Shared extends Plugin { * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ function get_unread($feed_id) { - $result = db_query("select count(int_id) AS count from ttrss_user_entries where owner_uid = ".$_SESSION["uid"]." and unread = true and uuid != ''"); + $sth = $this->pdo->prepare("select count(int_id) AS count + from ttrss_user_entries where owner_uid = ? and unread = true and uuid != ''"); + $sth->execute([$_SESSION['uid']]); - return db_fetch_result($result, 0, "count"); + if ($row = $sth->fetch()) { + return $row['count']; + } + + return 0; } /** * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ function get_total($feed_id) { - $result = db_query("select count(int_id) AS count from ttrss_user_entries where owner_uid = ".$_SESSION["uid"]." and uuid != ''"); + $sth = $this->pdo->prepare("select count(int_id) AS count + from ttrss_user_entries where owner_uid = ? and uuid != ''"); + $sth->execute([$_SESSION['uid']]); + + if ($row = $sth->fetch()) { + return $row['count']; + } - return db_fetch_result($result, 0, "count"); + return 0; } /** @@ -38,14 +38,13 @@ </script> <?php echo stylesheet_tag("lib/dijit/themes/claro/claro.css"); ?> - <?php echo stylesheet_tag("css/layout.css"); ?> <?php if ($_SESSION["uid"]) { $theme = get_pref( "USER_CSS_THEME", $_SESSION["uid"], false); if ($theme && theme_valid("$theme")) { echo stylesheet_tag(get_theme_path($theme)); } else { - echo stylesheet_tag("themes/default.php"); + echo stylesheet_tag("css/default.css"); } } ?> @@ -93,7 +92,7 @@ } } - print get_minified_js(array("functions", "deprecated", "prefs")); + print get_minified_js(["functions.js", "prefs.js"]); init_js_translations(); ?> @@ -109,7 +108,7 @@ </head> -<body id="ttrssPrefs" class="claro"> +<body class="claro ttrss_main ttrss_prefs"> <div id="notify" class="notify"></div> <div id="cmdline" style="display : none"></div> diff --git a/register.php b/register.php index 11f5d9284..d03218fc1 100644 --- a/register.php +++ b/register.php @@ -95,7 +95,6 @@ <head> <title>Create new account</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> -<?php echo stylesheet_tag("css/utility.css") ?> <?php echo stylesheet_tag("css/default.css") ?> <?php echo javascript_tag("js/functions.js") ?> <?php echo javascript_tag("lib/prototype.js") ?> @@ -180,7 +179,7 @@ </script> -<body> +<body class="claro ttrss_utility"> <div class="floatingLogo"><img src="images/logo_small.png"></div> diff --git a/themes/night.css b/themes/night.css index 9d4cbb392..2bc0a36af 100644 --- a/themes/night.css +++ b/themes/night.css @@ -1,114 +1,114 @@ @import "default.php"; -body#ttrssMain #feeds-holder { +body.claro.ttrss_main #feeds-holder { background : #222; border-color : #666; border-left-width : 1px; } -body#ttrssMain #headlines-frame { +body.claro.ttrss_main #headlines-frame { border-color : #ddd; } -body#ttrssMain div.whiteBox { +body.claro.ttrss_main div.whiteBox { border-color : #666; } -body#ttrssMain #content-insert { +body.claro.ttrss_main #content-insert { background : #333; } -body#ttrssMain #feedTree .dijitTreeRow { +body.claro.ttrss_main #feedTree .dijitTreeRow { color : #ccc ! important; } -body#ttrssMain.claro .dijitTree .dijitTreeRowHover { +body.claro.ttrss_main .dijitTree .dijitTreeRowHover { background : #444; border-color : #666; } -body#ttrssMain.claro .dijitTree .dijitTreeRowSelected { +body.claro.ttrss_main .dijitTree .dijitTreeRowSelected { background : #555; border-color : #666 ! important; } -body#ttrssMain.claro .dijitTreeRowSelected .dijitTreeLabel { +body.claro.ttrss_main .dijitTreeRowSelected .dijitTreeLabel { text-shadow : none; } -body#ttrssMain #feedTree .counterNode.aux { +body.claro.ttrss_main #feedTree .counterNode.aux { background : #555; color : #999; border-color : #333; } -body#ttrssMain #feedTree .counterNode { +body.claro.ttrss_main #feedTree .counterNode { border : 1px solid #6280AD; color : white; background : #6280AD; } -body#ttrssMain #headlines-frame { +body.claro.ttrss_main #headlines-frame { background : #333; color : #ccc; } -body#ttrssMain #main-toolbar { +body.claro.ttrss_main #main-toolbar { background : #333; border-color : #666; } -body#ttrssMain #main-toolbar .dijitButtonText { +body.claro.ttrss_main #main-toolbar .dijitButtonText { color : #ddd; } -body#ttrssMain #main-toolbar .dijitSelect { +body.claro.ttrss_main #main-toolbar .dijitSelect { border-color : #666; } -body#ttrssMain #main, -body#ttrssMain #overlay { +body.claro.ttrss_main #main, +body.claro.ttrss_main #overlay { background : #333; color : #ccc; } -body#ttrssMain .hlFeed a { +body.claro.ttrss_main .hlFeed a { color : white; } -body#ttrssMain div.cdm.expanded div.cdmFooter, -body#ttrssMain div.cdm.expandable div.cdmFooter { +body.claro.ttrss_main div.cdm.expanded div.cdmFooter, +body.claro.ttrss_main div.cdm.expandable div.cdmFooter { border-color : #666; color : #ccc; } -body#ttrssMain div.cdm.expandable.Selected { +body.claro.ttrss_main div.cdm.expandable.Selected { background : #6280AD; } -body#ttrssMain div.cdm.expanded.Unread div.cdmHeader a.title, -body#ttrssMain div.cdm.expandable.Unread a.title { +body.claro.ttrss_main div.cdm.expanded.Unread div.cdmHeader a.title, +body.claro.ttrss_main div.cdm.expandable.Unread a.title { color : #ccc; } -body#ttrssMain div.cdm.expanded div.cdmHeader a.title, -body#ttrssMain div.cdm.expandable a.title { +body.claro.ttrss_main div.cdm.expanded div.cdmHeader a.title, +body.claro.ttrss_main div.cdm.expandable a.title { color : #aaa; } -body#ttrssMain .cdm.expandable.active, -body#ttrssMain .cdm.expandable { +body.claro.ttrss_main .cdm.expandable.active, +body.claro.ttrss_main .cdm.expandable { border-color : #666; background : transparent; } -body#ttrssMain .cdm.active { +body.claro.ttrss_main .cdm.active { background : transparent ! important; } -body#ttrssMain .cdm .cdmContentInner, -body#ttrssMain .cdm .titleWrap { +body.claro.ttrss_main .cdm .cdmContentInner, +body.claro.ttrss_main .cdm .titleWrap { color : #bbb; } @@ -126,64 +126,64 @@ body#ttrssMain .cdm .titleWrap { background : black; } -body#ttrssMain .dijitInputField.dijitButtonText { +body.claro.ttrss_main .dijitInputField.dijitButtonText { background : #333; } -body#ttrssMain .cdm .cdmContentInner img, -body#ttrssMain .cdm img.tinyFeedIcon, -body#ttrssMain .cdm .cdmFooter img, -body#ttrssMain #feedTree img, -body#ttrssMain .postContent img { +body.claro.ttrss_main .cdm .cdmContentInner img, +body.claro.ttrss_main .cdm img.tinyFeedIcon, +body.claro.ttrss_main .cdm .cdmFooter img, +body.claro.ttrss_main #feedTree img, +body.claro.ttrss_main .postContent img { filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#grayscale"); // firefox lol filter: grayscale(1); -webkit-filter: grayscale(1); } -body#ttrssMain .hl img.hlScorePic { +body.claro.ttrss_main .hl img.hlScorePic { opacity : 0.5; } -body#ttrssMain .cdm .cdmFooter img { +body.claro.ttrss_main .cdm .cdmFooter img { opacity : 0.6; } -body#ttrssMain #floatingTitle { +body.claro.ttrss_main #floatingTitle { background : #333; border-color : #666; } -body#ttrssMain #floatingTitle a.title { +body.claro.ttrss_main #floatingTitle a.title { color : #ccc; } -body#ttrssMain .dijitMenu, -body#ttrssMain .dijitMenuTable, -body#ttrssMain .dijitMenu .dijitMenuItem td { +body.claro.ttrss_main .dijitMenu, +body.claro.ttrss_main .dijitMenuTable, +body.claro.ttrss_main .dijitMenu .dijitMenuItem td { border-color : #666; background : #666; } -body#ttrssMain .dijitMenu .dijitMenuItemSelected, -body#ttrssMain .dijitMenu .dijitMenuItemSelected td { +body.claro.ttrss_main .dijitMenu .dijitMenuItemSelected, +body.claro.ttrss_main .dijitMenu .dijitMenuItemSelected td { background : black; color : white; } -body#ttrssMain .hl { +body.claro.ttrss_main .hl { border-color : #666; } -body#ttrssMain .hl .hlTitle a { +body.claro.ttrss_main .hl .hlTitle a { color : #ccc; } -body#ttrssMain .hl.Selected, -body#ttrssMain .hl.active { +body.claro.ttrss_main .hl.Selected, +body.claro.ttrss_main .hl.active { background : #6280AD ! important; } -body#ttrssMain .postHeader { +body.claro.ttrss_main .postHeader { background : #333; border-color : #666; } diff --git a/update.php b/update.php index 9012d717b..99f4a59dc 100755 --- a/update.php +++ b/update.php @@ -17,6 +17,8 @@ if (!defined('PHP_EXECUTABLE')) define('PHP_EXECUTABLE', '/usr/bin/php'); + $pdo = Db::pdo(); + init_plugins(); $longopts = array("feeds", @@ -58,7 +60,6 @@ <head> <title>Tiny Tiny RSS data update script.</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> - <link rel="stylesheet" type="text/css" href="css/utility.css"> </head> <body> @@ -159,8 +160,8 @@ if (isset($options["force-update"])) { _debug("marking all feeds as needing update..."); - db_query( "UPDATE ttrss_feeds SET last_update_started = '1970-01-01', - last_updated = '1970-01-01'"); + $pdo->query( "UPDATE ttrss_feeds SET + last_update_started = '1970-01-01', last_updated = '1970-01-01'"); } if (isset($options["feeds"])) { @@ -218,16 +219,16 @@ _debug("clearing existing indexes..."); if (DB_TYPE == "pgsql") { - $result = db_query( "SELECT relname FROM + $sth = $pdo->query( "SELECT relname FROM pg_catalog.pg_class WHERE relname LIKE 'ttrss_%' AND relname NOT LIKE '%_pkey' AND relkind = 'i'"); } else { - $result = db_query( "SELECT index_name,table_name FROM + $sth = $pdo->query( "SELECT index_name,table_name FROM information_schema.statistics WHERE index_name LIKE 'ttrss_%'"); } - while ($line = db_fetch_assoc($result)) { + while ($line = $sth->fetch()) { if (DB_TYPE == "pgsql") { $statement = "DROP INDEX " . $line["relname"]; _debug($statement); @@ -236,7 +237,7 @@ $line['table_name']." DROP INDEX ".$line['index_name']; _debug($statement); } - db_query( $statement, false); + $pdo->query($statement); } _debug("reading indexes from schema for: " . DB_TYPE); @@ -253,7 +254,7 @@ $statement = "CREATE INDEX $index ON $table"; _debug($statement); - db_query( $statement); + $pdo->query($statement); } } fclose($fp); @@ -272,11 +273,11 @@ _debug("converting filters..."); - db_query( "DELETE FROM ttrss_filters2"); + $pdo->query("DELETE FROM ttrss_filters2"); - $result = db_query( "SELECT * FROM ttrss_filters ORDER BY id"); + $res = $pdo->query("SELECT * FROM ttrss_filters ORDER BY id"); - while ($line = db_fetch_assoc($result)) { + while ($line = $res->fetch()) { $owner_uid = $line["owner_uid"]; // date filters are removed @@ -317,7 +318,7 @@ if (isset($options["update-schema"])) { _debug("checking for updates (" . DB_TYPE . ")..."); - $updater = new DbUpdater(Db::get(), DB_TYPE, SCHEMA_VERSION); + $updater = new DbUpdater(Db::pdo(), DB_TYPE, SCHEMA_VERSION); if ($updater->isUpdateRequired()) { _debug("schema update required, version " . $updater->getSchemaVersion() . " to " . SCHEMA_VERSION); @@ -346,28 +347,37 @@ if (isset($options["gen-search-idx"])) { echo "Generating search index (stemming set to English)...\n"; - $result = db_query("SELECT COUNT(id) AS count FROM ttrss_entries WHERE tsvector_combined IS NULL"); - $count = db_fetch_result($result, 0, "count"); + $res = $pdo->query("SELECT COUNT(id) AS count FROM ttrss_entries WHERE tsvector_combined IS NULL"); + $row = $res->fetch(); + $count = $row['count']; print "Articles to process: $count.\n"; $limit = 500; $processed = 0; + $sth = $pdo->prepare("SELECT id, title, content FROM ttrss_entries WHERE + tsvector_combined IS NULL ORDER BY id LIMIT ?"); + $sth->execute([$limit]); + + $usth = $pdo->prepare("UPDATE ttrss_entries + SET tsvector_combined = to_tsvector('english', ?) WHERE id = ?"); + while (true) { - $result = db_query("SELECT id, title, content FROM ttrss_entries WHERE tsvector_combined IS NULL ORDER BY id LIMIT $limit"); - while ($line = db_fetch_assoc($result)) { - $tsvector_combined = db_escape_string(mb_substr($line['title'] . ' ' . strip_tags(str_replace('<', ' <', $line['content'])), - 0, 1000000)); + while ($line = $sth->fetch()) { + $tsvector_combined = mb_substr($line['title'] . ' ' . + preg_replace('/[<\?\:]/', ' ', strip_tags($line['content'])), + 0, 1000000); - db_query("UPDATE ttrss_entries SET tsvector_combined = to_tsvector('english', '$tsvector_combined') WHERE id = " . $line["id"]); + $usth->execute([$tsvector_combined, $line['id']]); + + $processed++; } - $processed += db_num_rows($result); print "Processed $processed articles...\n"; - if (db_num_rows($result) != $limit) { + if ($processed < $limit) { echo "All done.\n"; break; } @@ -410,31 +420,34 @@ } if (isset($options["decrypt-feeds"])) { - $result = db_query("SELECT id, auth_pass FROM ttrss_feeds WHERE auth_pass_encrypted = true"); if (!function_exists("mcrypt_decrypt")) { _debug("mcrypt functions not available."); return; } + $res = $pdo->query("SELECT id, auth_pass FROM ttrss_feeds WHERE auth_pass_encrypted = true"); + require_once "crypt.php"; $total = 0; - db_query("BEGIN"); + $pdo->beginTransaction(); + + $usth = $pdo->prepare("UPDATE ttrss_feeds SET auth_pass_encrypted = false, auth_pass = ? + WHERE id = ?"); - while ($line = db_fetch_assoc($result)) { + while ($line = $res->fetch()) { _debug("processing feed id " . $line["id"]); - $auth_pass = db_escape_string(decrypt_string($line["auth_pass"])); + $auth_pass = decrypt_string($line["auth_pass"]); - db_query("UPDATE ttrss_feeds SET auth_pass_encrypted = false, auth_pass = '$auth_pass' - WHERE id = " . $line["id"]); + $usth->execute([$auth_pass, $line['id']]); ++$total; } - db_query("COMMIT"); + $pdo->commit(); _debug("$total feeds processed."); } |