Browse Source

konflikti

Schunka Calcifer 1 year ago
parent
commit
8e859215d2
100 changed files with 14439 additions and 15261 deletions
  1. 27 28
      classes/article.php
  2. 2 2
      classes/dbupdater.php
  3. 86 0
      classes/debug.php
  4. 8 8
      classes/digest.php
  5. 39 53
      classes/feeds.php
  6. 0 9
      classes/pluginhost.php
  7. 10 13
      classes/pref/feeds.php
  8. 1 1
      classes/pref/filters.php
  9. 2 2
      classes/pref/prefs.php
  10. 1 1
      classes/pref/users.php
  11. 110 120
      classes/rssutils.php
  12. 176 168
      css/cdm.less
  13. 0 0
      css/default.css
  14. 34 43
      css/tt-rss.less
  15. 80 80
      css/zoom.less
  16. 16 73
      include/functions.php
  17. 1 1
      include/version.php
  18. 2 5
      index.php
  19. 8 8
      js/FeedStoreModel.js
  20. 70 73
      js/FeedTree.js
  21. 1 1
      js/PluginHost.js
  22. 10 9
      js/PrefFeedTree.js
  23. 12 11
      js/PrefFilterTree.js
  24. 8 7
      js/PrefLabelTree.js
  25. 145 201
      js/feedlist.js
  26. 293 334
      js/functions.js
  27. 234 442
      js/prefs.js
  28. 145 313
      js/tt-rss.js
  29. 263 526
      js/viewfeed.js
  30. BIN
      locale/ar_SA/LC_MESSAGES/messages.mo
  31. 469 469
      locale/ar_SA/LC_MESSAGES/messages.po
  32. BIN
      locale/bg_BG/LC_MESSAGES/messages.mo
  33. 469 469
      locale/bg_BG/LC_MESSAGES/messages.po
  34. BIN
      locale/ca_CA/LC_MESSAGES/messages.mo
  35. 475 473
      locale/ca_CA/LC_MESSAGES/messages.po
  36. BIN
      locale/cs_CZ/LC_MESSAGES/messages.mo
  37. 470 470
      locale/cs_CZ/LC_MESSAGES/messages.po
  38. BIN
      locale/da_DK/LC_MESSAGES/messages.mo
  39. 460 464
      locale/da_DK/LC_MESSAGES/messages.po
  40. BIN
      locale/de_DE/LC_MESSAGES/messages.mo
  41. 466 467
      locale/de_DE/LC_MESSAGES/messages.po
  42. BIN
      locale/el_GR/LC_MESSAGES/messages.mo
  43. 460 464
      locale/el_GR/LC_MESSAGES/messages.po
  44. BIN
      locale/es_ES/LC_MESSAGES/messages.mo
  45. 469 469
      locale/es_ES/LC_MESSAGES/messages.po
  46. BIN
      locale/es_LA/LC_MESSAGES/messages.mo
  47. 460 464
      locale/es_LA/LC_MESSAGES/messages.po
  48. BIN
      locale/fi_FI/LC_MESSAGES/messages.mo
  49. 470 471
      locale/fi_FI/LC_MESSAGES/messages.po
  50. BIN
      locale/fr_FR/LC_MESSAGES/messages.mo
  51. 469 469
      locale/fr_FR/LC_MESSAGES/messages.po
  52. BIN
      locale/hu_HU/LC_MESSAGES/messages.mo
  53. 470 470
      locale/hu_HU/LC_MESSAGES/messages.po
  54. BIN
      locale/it_IT/LC_MESSAGES/messages.mo
  55. 467 468
      locale/it_IT/LC_MESSAGES/messages.po
  56. BIN
      locale/ja_JP/LC_MESSAGES/messages.mo
  57. 470 470
      locale/ja_JP/LC_MESSAGES/messages.po
  58. BIN
      locale/ko_KR/LC_MESSAGES/messages.mo
  59. 466 467
      locale/ko_KR/LC_MESSAGES/messages.po
  60. BIN
      locale/lv_LV/LC_MESSAGES/messages.mo
  61. 469 469
      locale/lv_LV/LC_MESSAGES/messages.po
  62. BIN
      locale/nb_NO/LC_MESSAGES/messages.mo
  63. 476 474
      locale/nb_NO/LC_MESSAGES/messages.po
  64. BIN
      locale/nl_NL/LC_MESSAGES/messages.mo
  65. 471 471
      locale/nl_NL/LC_MESSAGES/messages.po
  66. BIN
      locale/pl_PL/LC_MESSAGES/messages.mo
  67. 472 472
      locale/pl_PL/LC_MESSAGES/messages.po
  68. BIN
      locale/pt_BR/LC_MESSAGES/messages.mo
  69. 467 467
      locale/pt_BR/LC_MESSAGES/messages.po
  70. BIN
      locale/pt_PT/LC_MESSAGES/messages.mo
  71. 471 471
      locale/pt_PT/LC_MESSAGES/messages.po
  72. BIN
      locale/ru_RU/LC_MESSAGES/messages.mo
  73. 470 475
      locale/ru_RU/LC_MESSAGES/messages.po
  74. BIN
      locale/sv_SE/LC_MESSAGES/messages.mo
  75. 471 471
      locale/sv_SE/LC_MESSAGES/messages.po
  76. BIN
      locale/tr_TR/LC_MESSAGES/messages.mo
  77. 470 470
      locale/tr_TR/LC_MESSAGES/messages.po
  78. BIN
      locale/zh_CN/LC_MESSAGES/messages.mo
  79. 474 480
      locale/zh_CN/LC_MESSAGES/messages.po
  80. BIN
      locale/zh_TW/LC_MESSAGES/messages.mo
  81. 467 467
      locale/zh_TW/LC_MESSAGES/messages.po
  82. 302 335
      messages.pot
  83. 0 7
      plugins/af_comics/filters/af_comics_pa.php
  84. 1 1
      plugins/af_psql_trgm/init.js
  85. 2 2
      plugins/af_psql_trgm/init.php
  86. 19 19
      plugins/af_redditimgur/init.php
  87. 3 3
      plugins/af_zz_noautoplay/init.js
  88. 30 4
      plugins/cache_starred_images/init.php
  89. 28 36
      plugins/embed_original/init.js
  90. 2 2
      plugins/import_export/init.php
  91. 5 10
      plugins/mail/mail.js
  92. 2 2
      plugins/mailto/init.js
  93. 2 2
      plugins/mark_button/init.php
  94. 14 19
      plugins/note/note.js
  95. 1 1
      plugins/nsfw/init.php
  96. 29 40
      plugins/share/share.js
  97. 5 10
      plugins/share/share_prefs.js
  98. 17 20
      plugins/shorten_expanded/init.js
  99. 1 1
      tests/functional/BasicTest.php
  100. 4 4
      themes/compact.css

+ 27 - 28
classes/article.php

@@ -372,8 +372,7 @@ class Article extends Handler_Protected {
 		$ids = explode(",", clean($_REQUEST["ids"]));
 		$label_id = clean($_REQUEST["lid"]);
 
-		$label = db_escape_string(Labels::find_caption($label_id,
-		$_SESSION["uid"]));
+		$label = Labels::find_caption($label_id, $_SESSION["uid"]);
 
 		$reply["info-for-headlines"] = array();
 
@@ -457,7 +456,7 @@ class Article extends Handler_Protected {
 #				$entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\" rel=\"noopener noreferrer\">" .
 #					$filename . " (" . $ctype . ")" . "</a>";
 
-				$entry = "<div onclick=\"openUrlPopup('".htmlspecialchars($url)."')\"
+				$entry = "<div onclick=\"popupOpenUrl('".htmlspecialchars($url)."')\"
 					dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
 
 				array_push($entries_html, $entry);
@@ -537,7 +536,7 @@ class Article extends Handler_Protected {
 				else
 					$filename = "";
 
-				$rv .= "<div onclick='openUrlPopup(\"".htmlspecialchars($entry["url"])."\")'
+				$rv .= "<div onclick='popupOpenUrl(\"".htmlspecialchars($entry["url"])."\")'
 					dojoType=\"dijit.MenuItem\">".$filename . $title."</div>";
 
 			};
@@ -621,13 +620,13 @@ class Article extends Handler_Protected {
 				} else {
 					$comments_url = htmlspecialchars($line["link"]);
 				}
-				$entry_comments = "<a class=\"postComments\"
+				$entry_comments = "<a class=\"comments\"
 					target='_blank' rel=\"noopener noreferrer\" href=\"$comments_url\">$num_comments ".
 					_ngettext("comment", "comments", $num_comments)."</a>";
 
 			} else {
 				if ($line["comments"] && $line["link"] != $line["comments"]) {
-					$entry_comments = "<a class=\"postComments\" target='_blank' rel=\"noopener noreferrer\" href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
+					$entry_comments = "<a class=\"comments\" target='_blank' rel=\"noopener noreferrer\" href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
 				}
 			}
 
@@ -679,9 +678,9 @@ class Article extends Handler_Protected {
 				$rv['content'] .= "<body class=\"claro ttrss_utility ttrss_zoom\">";
 			}
 
-			$rv['content'] .= "<div class=\"postReply\" id=\"POST-$id\">";
+			$rv['content'] .= "<div class=\"post\" id=\"POST-$id\">";
 
-			$rv['content'] .= "<div class=\"postHeader\" id=\"POSTHDR-$id\">";
+			$rv['content'] .= "<div class=\"header\">";
 
 			$entry_author = $line["author"];
 
@@ -693,25 +692,25 @@ class Article extends Handler_Protected {
 				$owner_uid, true);
 
 			if (!$zoom_mode)
-				$rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
+				$rv['content'] .= "<div class=\"date\">$parsed_updated</div>";
 
 			if ($line["link"]) {
-				$rv['content'] .= "<div class='postTitle'><a target='_blank' rel='noopener noreferrer'
+				$rv['content'] .= "<div class='title'><a target='_blank' rel='noopener noreferrer'
 					title=\"".htmlspecialchars($line['title'])."\"
 					href=\"" .
 					htmlspecialchars($line["link"]) . "\">" .
 					$line["title"] . "</a>" .
 					"<span class='author'>$entry_author</span></div>";
 			} else {
-				$rv['content'] .= "<div class='postTitle'>" . $line["title"] . "$entry_author</div>";
+				$rv['content'] .= "<div class='title'>" . $line["title"] . "$entry_author</div>";
 			}
 
 			if ($zoom_mode) {
 				$feed_title = htmlspecialchars($line["feed_title"]);
 
-				$rv['content'] .= "<div class=\"postFeedTitle\">$feed_title</div>";
+				$rv['content'] .= "<div class=\"feed-title\">$feed_title</div>";
 
-				$rv['content'] .= "<div class=\"postDate\">$parsed_updated</div>";
+				$rv['content'] .= "<div class=\"date\">$parsed_updated</div>";
 			}
 
 			$tags_str = Article::format_tags_string($line["tags"], $id);
@@ -721,7 +720,7 @@ class Article extends Handler_Protected {
 
 			if (!$entry_comments) $entry_comments = "&nbsp;"; # placeholder
 
-			$rv['content'] .= "<div class='postTags' style='float : right'>
+			$rv['content'] .= "<div class='tags' style='float : right'>
 				<img src='images/tag.png'
 				class='tagsPic' alt='Tags' title='Tags'>&nbsp;";
 
@@ -787,7 +786,7 @@ class Article extends Handler_Protected {
 
 			if (!$line['lang']) $line['lang'] = 'en';
 
-			$rv['content'] .= "<div class=\"postContent\" lang=\"".$line['lang']."\">";
+			$rv['content'] .= "<div class=\"content\" lang=\"".$line['lang']."\">";
 
 			$rv['content'] .= $line["content"];
 
@@ -937,24 +936,24 @@ class Article extends Handler_Protected {
 		return $rv;
 	}
 
-	static function purge_orphans($do_output = false) {
+	static function purge_orphans() {
 
-		// purge orphaned posts in main content table
+        // purge orphaned posts in main content table
 
-		if (DB_TYPE == "mysql")
-			$limit_qpart = "LIMIT 5000";
-		else
-			$limit_qpart = "";
+        if (DB_TYPE == "mysql")
+            $limit_qpart = "LIMIT 5000";
+        else
+            $limit_qpart = "";
 
-		$pdo = Db::pdo();
-		$res = $pdo->query("DELETE FROM ttrss_entries WHERE
+        $pdo = Db::pdo();
+        $res = $pdo->query("DELETE FROM ttrss_entries WHERE
 			NOT EXISTS (SELECT ref_id FROM ttrss_user_entries WHERE ref_id = id) $limit_qpart");
 
-		if ($do_output) {
-			$rows = $res->rowCount();
-			_debug("Purged $rows orphaned posts.");
-		}
-	}
+        if (Debug::enabled()) {
+            $rows = $res->rowCount();
+            Debug::log("Purged $rows orphaned posts.");
+        }
+    }
 
 	static function catchupArticlesById($ids, $cmode, $owner_uid = false) {
 

+ 2 - 2
classes/dbupdater.php

@@ -47,8 +47,8 @@ class DbUpdater {
 								print_notice("Query: $line");
 								print_error("Error: " . implode(", ", $this->pdo->errorInfo()));
 							} else {
-								_debug("Query: $line");
-								_debug("Error: " . implode(", ", $this->pdo->errorInfo()));
+								Debug::log("Query: $line");
+								Debug::log("Error: " . implode(", ", $this->pdo->errorInfo()));
 							}
 
 							return false;

+ 86 - 0
classes/debug.php

@@ -0,0 +1,86 @@
+<?php
+class Debug {
+	public static $LOG_DISABLED = -1;
+    public static $LOG_NORMAL = 0;
+    public static $LOG_VERBOSE = 1;
+    public static $LOG_EXTENDED = 2;
+
+    private static $enabled = false;
+    private static $quiet = false;
+    private static $logfile = false;
+    private static $loglevel = 0;
+
+	public static function set_logfile($logfile) {
+        Debug::$logfile = $logfile;
+    }
+
+    public static function enabled() {
+        return Debug::$enabled;
+    }
+
+    public static function set_enabled($enable) {
+        Debug::$enabled = $enable;
+    }
+
+    public static function set_quiet($quiet) {
+        Debug::$quiet = $quiet;
+    }
+
+    public static function set_loglevel($level) {
+        Debug::$loglevel = $level;
+    }
+
+    public static function get_loglevel() {
+        return Debug::$loglevel;
+    }
+
+    public static function log($message, $level = 0) {
+
+        if (!Debug::$enabled || Debug::$loglevel < $level) return false;
+
+        $ts = strftime("%H:%M:%S", time());
+        if (function_exists('posix_getpid')) {
+            $ts = "$ts/" . posix_getpid();
+        }
+
+        if (Debug::$logfile) {
+            $fp = fopen(Debug::$logfile, 'a+');
+
+            if ($fp) {
+                $locked = false;
+
+                if (function_exists("flock")) {
+                    $tries = 0;
+
+                    // try to lock logfile for writing
+                    while ($tries < 5 && !$locked = flock($fp, LOCK_EX | LOCK_NB)) {
+                        sleep(1);
+                        ++$tries;
+                    }
+
+                    if (!$locked) {
+                        fclose($fp);
+                        user_error("Unable to lock debugging log file: " . Debug::$logfile, E_USER_WARNING);
+                        return;
+                    }
+                }
+
+                fputs($fp, "[$ts] $message\n");
+
+                if (function_exists("flock")) {
+                    flock($fp, LOCK_UN);
+                }
+
+                fclose($fp);
+
+                if (Debug::$quiet)
+                    return;
+
+            } else {
+                user_error("Unable to open debugging log file: " . Debug::$logfile, E_USER_WARNING);
+            }
+        }
+
+        print "[$ts] $message\n";
+    }
+}

+ 8 - 8
classes/digest.php

@@ -9,12 +9,12 @@ class Digest
 	 * @param integer $limit The maximum number of articles by digest.
 	 * @return boolean Return false if digests are not enabled.
 	 */
-	static function send_headlines_digests($debug = false) {
+	static function send_headlines_digests() {
 
 		$user_limit = 15; // amount of users to process (e.g. emails to send out)
 		$limit = 1000; // maximum amount of headlines to include
 
-		if ($debug) _debug("Sending digests, batch of max $user_limit users, headline limit = $limit");
+		Debug::log("Sending digests, batch of max $user_limit users, headline limit = $limit");
 
 		if (DB_TYPE == "pgsql") {
 			$interval_qpart = "last_digest_sent < NOW() - INTERVAL '1 days'";
@@ -37,7 +37,7 @@ class Digest
 					time() - $preferred_ts <= 7200
 				) {
 
-					if ($debug) _debug("Sending digest for UID:" . $line['id'] . " - " . $line["email"]);
+					Debug::log("Sending digest for UID:" . $line['id'] . " - " . $line["email"]);
 
 					$do_catchup = get_pref('DIGEST_CATCHUP', $line['id'], false);
 
@@ -64,16 +64,16 @@ class Digest
 							"message" => $digest_text,
 							"message_html" => $digest]);
 
-						//if (!$rc && $debug) _debug("ERROR: " . $mailer->lastError());
+						//if (!$rc && $debug) Debug::log("ERROR: " . $mailer->lastError());
 
-						if ($debug) _debug("RC=$rc");
+						Debug::log("RC=$rc");
 
 						if ($rc && $do_catchup) {
-							if ($debug) _debug("Marking affected articles as read...");
+							Debug::log("Marking affected articles as read...");
 							Article::catchupArticlesById($affected_ids, 0, $line["id"]);
 						}
 					} else {
-						if ($debug) _debug("No headlines");
+						Debug::log("No headlines");
 					}
 
 					$sth = $pdo->prepare("UPDATE ttrss_users SET last_digest_sent = NOW()
@@ -84,7 +84,7 @@ class Digest
 			}
 		}
 
-		if ($debug) _debug("All done.");
+		Debug::log("All done.");
 
 	}
 

+ 39 - 53
classes/feeds.php

@@ -277,7 +277,6 @@ class Feeds extends Handler_Protected {
         $lnum = $offset;
         $num_unread = 0;
         if ($_REQUEST["debug"]) $timing_info = print_checkpoint("PS", $timing_info);
-        $expand_cdm = get_pref('CDM_EXPANDED');
 
         if (is_object($result)) {
 
@@ -332,11 +331,11 @@ class Feeds extends Handler_Protected {
 
 				$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)'>";
+				$marked_pic = "<img src=\"images/$marked_pic_src\" class=\"marked-pic marked-$id\" onclick='toggleMark($id)'>";
 
 				$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)'>";
+                $published_pic = "<img src=\"images/$published_pic_src\" class=\"pub-pic pub-$id\" onclick='togglePub($id)'>";
 
 				$updated_fmt = make_local_datetime($line["updated"], false, false, false, true);
 				$date_entered_fmt = T_sprintf("Imported at %s",
@@ -346,7 +345,7 @@ class Feeds extends Handler_Protected {
 
 				$score_pic = "images/" . get_score_pic($score);
 
-				$score_pic = "<img class='hlScorePic' score='$score' onclick='changeScore($id, this)' src=\"$score_pic\"
+				$score_pic = "<img class='score-pic' score='$score' onclick='changeScore($id, this)' src=\"$score_pic\"
                 title=\"$score\">";
 
 				if ($score > 500) {
@@ -363,9 +362,7 @@ class Feeds extends Handler_Protected {
 					$entry_author = " &mdash; $entry_author";
 				}
 
-				$has_feed_icon = feeds::feedHasIcon($feed_id);
-
-				if ($has_feed_icon) {
+				if (feeds::feedHasIcon($feed_id)) {
 					$feed_icon_img = "<img class=\"tinyFeedIcon\" src=\"".ICONS_URL."/$feed_id.ico\" alt=\"\">";
 				} else {
 					$feed_icon_img = "<img class=\"tinyFeedIcon\" src=\"images/pub_set.png\" alt=\"\">";
@@ -393,7 +390,7 @@ class Feeds extends Handler_Protected {
 
 							$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'>".
+							$reply['content'] .= "<div data-feed-id='$feed_id' class='feed-titl'>".
 								"<div style='float : right'>$feed_icon_img</div>".
 								"<a class='title' href=\"#\" onclick=\"viewfeed({feed:$feed_id})\">".
 								$line["feed_title"]."</a>
@@ -405,7 +402,7 @@ class Feeds extends Handler_Protected {
 
 					$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'] .= "<div class='left'>";
 
 					$reply['content'] .= "<input dojoType=\"dijit.form.CheckBox\"
                         type=\"checkbox\" onclick=\"toggleSelectRow2(this)\"
@@ -417,14 +414,14 @@ class Feeds extends Handler_Protected {
 					$reply['content'] .= "</div>";
 
 					$reply['content'] .= "<div onclick='return hlClicked(event, $id)'
-                    class=\"hlTitle\"><span class='hlContent $hlc_suffix'>";
-					$reply['content'] .= "<a id=\"RTITLE-$id\" class=\"title $hlc_suffix\"
+                    class=\"title\"><span class='hl-content $hlc_suffix'>";
+					$reply['content'] .= "<a class=\"title $hlc_suffix\"
                     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=\"preview\">" . $line["content_preview"] . "</span>";
 					}
 
 					$reply['content'] .= "</a></span>";
@@ -437,18 +434,18 @@ class Feeds extends Handler_Protected {
 						if (@$line["feed_title"]) {
 							$rgba = @$rgba_cache[$feed_id];
 
-							$reply['content'] .= "<span class=\"hlFeed\"><a style=\"background : rgba($rgba, 0.3)\" href=\"#\" onclick=\"viewfeed({feed:$feed_id})\">".
+							$reply['content'] .= "<span class=\"feed\"><a style=\"background : rgba($rgba, 0.3)\" href=\"#\" onclick=\"viewfeed({feed:$feed_id})\">".
 								truncate_string($line["feed_title"],30)."</a></span>";
 						}
 					}
 
 
-					$reply['content'] .= "<span class=\"hlUpdated\">";
+					$reply['content'] .= "<span class=\"updated\">";
 
 					$reply['content'] .= "<div title='$date_entered_fmt'>$updated_fmt</div>
                     </span>";
 
-					$reply['content'] .= "<div class=\"hlRight\">";
+					$reply['content'] .= "<div class=\"right\">";
 
 					$reply['content'] .= $score_pic;
 
@@ -489,7 +486,7 @@ class Feeds extends Handler_Protected {
 							$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'>".
+							$reply['content'] .= "<div data-feed-id='$feed_id' class='feed-title'>".
 								"<div style=\"float : right\">$feed_icon_img</div>".
 								"<a href=\"#\" class='title' onclick=\"viewfeed({feed:$feed_id})\">".
 								$line["feed_title"]."</a> $vf_catchup_link</div>";
@@ -497,12 +494,13 @@ class Feeds extends Handler_Protected {
 						}
 					}
 
-					$expanded_class = $expand_cdm ? "expanded" : "expandable";
+                    $content_encoded = htmlspecialchars($line["content"]);
 
-					$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>";
+					$expanded_class = get_pref("CDM_EXPANDED") ? "expanded" : "expandable";
+                    $tmp_content = "<div class=\"cdm $expanded_class $hlc_suffix $class\"
+                        id=\"RROW-$id\" data-content=\"$content_encoded\" data-article-id='$id' data-orig-feed-id='$feed_id' $mouseover_attrs>";
 
-					$tmp_content .= "<div class=\"cdmHeader\">";
+					$tmp_content .= "<div class=\"header\">";
 					$tmp_content .= "<div style=\"vertical-align : middle\">";
 
 					$tmp_content .= "<input dojoType=\"dijit.form.CheckBox\"
@@ -524,7 +522,7 @@ class Feeds extends Handler_Protected {
 					}
 
 					// data-article-id included for context menu
-					$tmp_content .= "<span id=\"RTITLE-$id\"
+					$tmp_content .= "<span
                     onclick=\"return cdmClicked(event, $id);\"
                     data-article-id=\"$id\"
                     class=\"titleWrap hlMenuAttach $hlc_suffix\">
@@ -537,16 +535,9 @@ 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>";
-
-					if (!$expand_cdm)
-						$content_hidden = "style=\"display : none\"";
-					else
-						$excerpt_hidden = "style=\"display : none\"";
-
-					$tmp_content .= "<span $excerpt_hidden id=\"CEXC-$id\" class=\"cdmExcerpt\">" . $content_preview . "</span>";
+					$tmp_content .= "<span class='collapse'>
+                        <img src=\"images/collapse.png\" onclick=\"return cdmCollapseActive(event)\"
+                        title=\"".__("Collapse article")."\"/></span>";
 
 					$tmp_content .= "</span>";
 
@@ -554,7 +545,7 @@ class Feeds extends Handler_Protected {
 						if (@$line["feed_title"]) {
 							$rgba = @$rgba_cache[$feed_id];
 
-							$tmp_content .= "<div class=\"hlFeed\">
+							$tmp_content .= "<div class=\"feed\">
                             <a href=\"#\" style=\"background-color: rgba($rgba,0.3)\"
                             onclick=\"viewfeed({feed:$feed_id})\">".
 								truncate_string($line["feed_title"],30)."</a>
@@ -564,7 +555,7 @@ class Feeds extends Handler_Protected {
 
 					$tmp_content .= "<span class='updated' title='$date_entered_fmt'>$updated_fmt</span>";
 
-					$tmp_content .= "<div class='scoreWrap' style=\"vertical-align : middle\">";
+					$tmp_content .= "<div style=\"vertical-align : middle\">";
 					$tmp_content .= "$score_pic";
 
 					if (!get_pref("VFEED_GROUP_BY_FEED") && $line["feed_title"]) {
@@ -572,13 +563,11 @@ class Feeds extends Handler_Protected {
                         title=\"".htmlspecialchars($line["feed_title"])."\"
                         onclick=\"viewfeed({feed:$feed_id})\">$feed_icon_img</span>";
 					}
-					$tmp_content .= "</div>"; //scoreWrap
+					$tmp_content .= "</div>"; //score wrapper2
 
-					$tmp_content .= "</div>"; //cdmHeader
+					$tmp_content .= "</div>"; //header
 
-					$tmp_content .= "<div class=\"cdmContent\" $content_hidden
-                    onclick=\"return cdmClicked(event, $id, true);\"
-                    id=\"CICD-$id\">";
+					$tmp_content .= "<div class=\"content\" onclick=\"return cdmClicked(event, $id, true);\">";
 
 					$tmp_content .= "<div id=\"POSTNOTE-$id\">";
 					if ($line['note']) {
@@ -588,7 +577,8 @@ class Feeds extends Handler_Protected {
 
 					if (!$line['lang']) $line['lang'] = 'en';
 
-					$tmp_content .= "<div class=\"cdmContentInner\" lang=\"".$line['lang']."\">";
+					// this is filled from RROW data-content
+					$tmp_content .= "<div class=\"content-inner\" lang=\"".$line['lang']."\">";
 
 					if ($line["orig_feed_id"]) {
 
@@ -616,15 +606,8 @@ class Feeds extends Handler_Protected {
 						}
 					}
 
-					$tmp_content .= "<span id=\"CWRAP-$id\">";
-					$tmp_content .= "<span id=\"CENCW-$id\" class=\"cencw\" style=\"display : none\">";
-					$tmp_content .= htmlspecialchars($line["content"]);
-					$tmp_content .= "</span>";
-					$tmp_content .= "</span>";
-
-					$tmp_content .= "</div>"; //cdmContentInner
-
-					$tmp_content .= "<div class=\"cdmIntermediate\">";
+					$tmp_content .= "</div>"; //content-inner
+					$tmp_content .= "<div class=\"intermediate\">";
 
 					$always_display_enclosures = $line["always_display_enclosures"];
 					$tmp_content .= Article::format_article_enclosures($id, $always_display_enclosures,
@@ -632,7 +615,7 @@ class Feeds extends Handler_Protected {
 
 					$tmp_content .= "</div>"; // cdmIntermediate
 
-					$tmp_content .= "<div class=\"cdmFooter\" onclick=\"cdmFooterClick(event)\">";
+					$tmp_content .= "<div class=\"footer\" onclick=\"event.stopPropagation()\">";
 
 					foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
 						$tmp_content .= $p->hook_article_left_button($line);
@@ -656,13 +639,13 @@ class Feeds extends Handler_Protected {
 						} else {
 							$comments_url = htmlspecialchars($line["link"]);
 						}
-						$entry_comments = "<a class=\"postComments\"
+						$entry_comments = "<a class=\"comments\"
                         target='_blank' rel='noopener noreferrer' href=\"$comments_url\">$num_comments ".
 							_ngettext("comment", "comments", $num_comments)."</a>";
 
 					} else {
 						if ($line["comments"] && $line["link"] != $line["comments"]) {
-							$entry_comments = "<a class=\"postComments\" target='_blank' rel='noopener noreferrer' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
+							$entry_comments = "<a class=\"comments\" target='_blank' rel='noopener noreferrer' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
 						}
 					}
 
@@ -677,7 +660,7 @@ class Feeds extends Handler_Protected {
 
 					$tmp_content .= "</div>"; // buttons
 
-					$tmp_content .= "</div>"; // cdmFooter
+					$tmp_content .= "</div>"; // cdm footer
 					$tmp_content .= "</div>"; // cdmContent
 					$tmp_content .= "</div>"; // RROW.cdm
 
@@ -1023,7 +1006,7 @@ class Feeds extends Handler_Protected {
 
 		print "<div style=\"clear : both\">
 			<input type=\"checkbox\" name=\"need_auth\" dojoType=\"dijit.form.CheckBox\" id=\"feedDlg_loginCheck\"
-					onclick='checkboxToggleElement(this, \"feedDlg_loginContainer\")'>
+					onclick='displayIfChecked(this, \"feedDlg_loginContainer\")'>
 				<label for=\"feedDlg_loginCheck\">".
 				__('This feed requires authentication.')."</div>";
 
@@ -1134,6 +1117,9 @@ class Feeds extends Handler_Protected {
 	function update_debugger() {
 		header("Content-type: text/html");
 
+		Debug::set_enabled(true);
+		Debug::set_loglevel($_REQUEST["xdebug"]);
+
 		$feed_id = (int)$_REQUEST["feed_id"];
 		@$do_update = $_REQUEST["action"] == "do_update";
 		$csrf_token = $_REQUEST["csrf_token"];

+ 0 - 9
classes/pluginhost.php

@@ -10,7 +10,6 @@ class PluginHost {
 	private $api_methods = array();
 	private $plugin_actions = array();
 	private $owner_uid;
-	private $debug;
 	private $last_registered;
 	private static $instance;
 
@@ -400,14 +399,6 @@ class PluginHost {
 		}
 	}
 
-	function set_debug($debug) {
-		$this->debug = $debug;
-	}
-
-	function get_debug() {
-		return $this->debug;
-	}
-
 	// Plugin feed functions are *EXPERIMENTAL*!
 
 	// cat_id: only -1 is supported (Special)

+ 10 - 13
classes/pref/feeds.php

@@ -328,13 +328,12 @@ class Pref_Feeds extends Handler_Protected {
 	}
 
 	private function process_category_order(&$data_map, $item_id, $parent_id = false, $nest_level = 0) {
-		$debug = isset($_REQUEST["debug"]);
 
 		$prefix = "";
 		for ($i = 0; $i < $nest_level; $i++)
 			$prefix .= "   ";
 
-		if ($debug) _debug("$prefix C: $item_id P: $parent_id");
+		Debug::log("$prefix C: $item_id P: $parent_id");
 
 		$bare_item_id = substr($item_id, strpos($item_id, ':')+1);
 
@@ -361,7 +360,7 @@ class Pref_Feeds extends Handler_Protected {
 				$id = $item['_reference'];
 				$bare_id = substr($id, strpos($id, ':')+1);
 
-				if ($debug) _debug("$prefix [$order_id] $id/$bare_id");
+				Debug::log("$prefix [$order_id] $id/$bare_id");
 
 				if ($item['_reference']) {
 
@@ -641,7 +640,7 @@ class Pref_Feeds extends Handler_Protected {
 			$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\")'>
+						onclick='displayIfChecked(this, \"feedEditDlg_loginContainer\")'>
 					<label for=\"feedEditDlg_loginCheck\">".
 				__('This feed requires authentication.')."</div>";
 
@@ -1545,12 +1544,10 @@ class Pref_Feeds extends Handler_Protected {
 	}
 
 	static function remove_feed($id, $owner_uid) {
-		$debug = isset($_REQUEST["debug"]);
-
 		foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_UNSUBSCRIBE_FEED) as $p) {
-			if( ! $p->hook_unsubscribe_feed($id, $owner_uid)){
-					if($debug) _debug("Feed not removed due to Error in Plugin. (HOOK_UNSUBSCRIBE_FEED)");
-					return;
+			if (! $p->hook_unsubscribe_feed($id, $owner_uid)) {
+                user_error("Feed $id (owner: $owner_uid) not removed due to plugin error (HOOK_UNSUBSCRIBE_FEED).", E_USER_WARNING);
+                return;
 			}
 		}
 
@@ -1659,7 +1656,7 @@ class Pref_Feeds extends Handler_Protected {
 
 		print "<div style=\"clear : both\">
 			<input type=\"checkbox\" name=\"need_auth\" dojoType=\"dijit.form.CheckBox\" id=\"feedDlg_loginCheck\"
-					onclick='checkboxToggleElement(this, \"feedDlg_loginContainer\")'>
+					onclick='displayIfChecked(this, \"feedDlg_loginContainer\")'>
 				<label for=\"feedDlg_loginCheck\">".
 				__('Feeds require authentication.')."</div>";
 
@@ -1714,11 +1711,11 @@ class Pref_Feeds extends Handler_Protected {
 
 	function regenFeedKey() {
 		$feed_id = clean($_REQUEST['id']);
-		$is_cat = clean($_REQUEST['is_cat']) == "true";
+		$is_cat = clean($_REQUEST['is_cat']);
 
 		$new_key = $this->update_feed_access_key($feed_id, $is_cat);
 
-		print json_encode(array("link" => $new_key));
+		print json_encode(["link" => $new_key]);
 	}
 
 
@@ -1728,7 +1725,7 @@ class Pref_Feeds extends Handler_Protected {
 		// 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]);
+		$sth->execute([$feed_id, bool_to_sql_bool($is_cat), $owner_uid]);
 
 		return get_feed_access_key($feed_id, $is_cat, $owner_uid);
 	}

+ 1 - 1
classes/pref/filters.php

@@ -153,7 +153,7 @@ class Pref_Filters extends Handler_Protected {
 
 				$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>";
+					src='images/information.png' onclick='popupOpenArticle($id)'></td><td>";
 
 				/*foreach ($filter['rules'] as $rule) {
 					$reg_exp = str_replace('/', '\/', $rule["reg_exp"]);

+ 2 - 2
classes/pref/prefs.php

@@ -162,7 +162,7 @@ class Pref_Prefs extends Handler_Protected {
 
 		$prefs_blacklist = array("ALLOW_DUPLICATE_POSTS", "STRIP_UNSAFE_TAGS", "REVERSE_HEADLINES",
 			"SORT_HEADLINES_BY_FEED_DATE", "DEFAULT_ARTICLE_LIMIT",
-			"FEEDS_SORT_BY_UNREAD");
+			"FEEDS_SORT_BY_UNREAD", "CDM_EXPANDED");
 
 		/* "FEEDS_SORT_BY_UNREAD", "HIDE_READ_FEEDS", "REVERSE_HEADLINES" */
 
@@ -1089,7 +1089,7 @@ class Pref_Prefs extends Handler_Protected {
 
 		print "<div class='dlgButtons'>
 			<div style='float : left'>
-			<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('profileEditDlg').removeSelected()\">".
+			<button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('profileEditDlg').removeSelected()\">".
 			__('Remove selected profiles')."</button>
 			<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('profileEditDlg').activateProfile()\">".
 			__('Activate profile')."</button>

+ 1 - 1
classes/pref/users.php

@@ -425,7 +425,7 @@ class Pref_Users extends Handler_Protected {
 
 				$onclick = "onclick='editUser($uid, event)' title='".__('Click to edit')."'";
 
-				print "<td $onclick><img src='images/user.png' class='markedPic' alt=''> " . $line["login"] . "</td>";
+				print "<td $onclick><img src='images/user.png' class='marked-pic' alt=''> " . $line["login"] . "</td>";
 
 				if (!$line["email"]) $line["email"] = "&nbsp;";
 

+ 110 - 120
classes/rssutils.php

@@ -7,8 +7,6 @@ class RSSUtils {
 			if ($k != "feed" && isset($v)) {
 				$x = strip_tags(is_array($v) ? implode(",", $v) : $v);
 
-				//_debug("$k:" . sha1($x) . ":" . htmlspecialchars($x), true);
-
 				$tmp .= sha1("$k:" . sha1($x));
 			}
 		}
@@ -68,7 +66,7 @@ class RSSUtils {
 
 	}
 
-	static function update_daemon_common($limit = DAEMON_FEED_LIMIT, $debug = true) {
+	static function update_daemon_common($limit = DAEMON_FEED_LIMIT) {
 		$schema_version = get_schema_version();
 
 		if ($schema_version != SCHEMA_VERSION) {
@@ -145,7 +143,7 @@ class RSSUtils {
 			array_push($feeds_to_update, $line['feed_url']);
 		}
 
-		if ($debug) _debug(sprintf("Scheduled %d feeds to update...", count($feeds_to_update)));
+		Debug::log(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
@@ -175,13 +173,13 @@ class RSSUtils {
 			ORDER BY ttrss_feeds.id $query_limit");
 
 		foreach ($feeds_to_update as $feed) {
-			if($debug) _debug("Base feed: $feed");
+			Debug::log("Base feed: $feed");
 
 			$usth->execute([$feed]);
 			//update_rss_feed($line["id"], true);
 
 			if ($tline = $usth->fetch()) {
-				if ($debug) _debug(" => " . $tline["last_updated"] . ", " . $tline["id"] . " " . $tline["owner_uid"]);
+				Debug::log(" => " . $tline["last_updated"] . ", " . $tline["id"] . " " . $tline["owner_uid"]);
 
 				if (array_search($tline["owner_uid"], $batch_owners) === FALSE)
 					array_push($batch_owners, $tline["owner_uid"]);
@@ -200,27 +198,26 @@ class RSSUtils {
 						// thrown outside of an active transaction during feed update
 					}
 				}
-				_debug_suppress(false);
 
-				_debug(sprintf("    %.4f (sec)", microtime(true) - $fstarted));
+				Debug::log(sprintf("    %.4f (sec)", microtime(true) - $fstarted));
 
 				++$nf;
 			}
 		}
 
 		if ($nf > 0) {
-			_debug(sprintf("Processed %d feeds in %.4f (sec), %.4f (sec/feed avg)", $nf,
+			Debug::log(sprintf("Processed %d feeds in %.4f (sec), %.4f (sec/feed avg)", $nf,
 				microtime(true) - $bstarted, (microtime(true) - $bstarted) / $nf));
 		}
 
 		foreach ($batch_owners as $owner_uid) {
-			_debug("Running housekeeping tasks for user $owner_uid...");
+			Debug::log("Running housekeeping tasks for user $owner_uid...");
 
 			RSSUtils::housekeeping_user($owner_uid);
 		}
 
 		// Send feed digests by email if needed.
-		Digest::send_headlines_digests($debug);
+		Digest::send_headlines_digests();
 
 		return $nf;
 	}
@@ -312,10 +309,7 @@ class RSSUtils {
 	 */
 	static function update_rss_feed($feed, $no_cache = false) {
 
-		$debug_enabled = defined('DAEMON_EXTENDED_DEBUG') || clean($_REQUEST['xdebug']);
-
-		_debug_suppress(!$debug_enabled);
-		_debug("start", $debug_enabled);
+		Debug::log("start", Debug::$LOG_VERBOSE);
 
 		$pdo = Db::pdo();
 
@@ -323,7 +317,7 @@ class RSSUtils {
 		$sth->execute([$feed]);
 
 		if (!$row = $sth->fetch()) {
-			_debug("feed $feed NOT FOUND/SKIPPED", $debug_enabled);
+			Debug::log("feed $feed not found, skipping.");
 			user_error("Attempt to update unknown/invalid feed $feed", E_USER_WARNING);
 			return false;
 		}
@@ -333,7 +327,7 @@ class RSSUtils {
 		// feed was batch-subscribed or something, we need to get basic info
 		// this is not optimal currently as it fetches stuff separately TODO: optimize
 		if ($title == "[Unknown]") {
-			_debug("setting basic feed info for $feed...");
+			Debug::log("setting basic feed info for $feed...");
 			RSSUtils::set_basic_feed_info($feed);
 		}
 
@@ -374,7 +368,6 @@ class RSSUtils {
 		$cache_filename = CACHE_DIR . "/feeds/" . sha1($fetch_url) . ".xml";
 
 		$pluginhost = new PluginHost();
-		$pluginhost->set_debug($debug_enabled);
 		$user_plugins = get_pref("_ENABLED_PLUGINS", $owner_uid);
 
 		$pluginhost->load(PLUGINS, PluginHost::KIND_ALL);
@@ -397,7 +390,7 @@ class RSSUtils {
 			!$auth_login && !$auth_pass &&
 			filemtime($cache_filename) > time() - 30) {
 
-			_debug("using local cache [$cache_filename].", $debug_enabled);
+			Debug::log("using local cache [$cache_filename].", Debug::$LOG_VERBOSE);
 
 			@$feed_data = file_get_contents($cache_filename);
 
@@ -406,28 +399,28 @@ class RSSUtils {
 			}
 
 		} else {
-			_debug("local cache will not be used for this feed", $debug_enabled);
+			Debug::log("local cache will not be used for this feed", Debug::$LOG_VERBOSE);
 		}
 
 		global $fetch_last_modified;
 
 		// fetch feed from source
 		if (!$feed_data) {
-			_debug("last unconditional update request: $last_unconditional");
+			Debug::log("last unconditional update request: $last_unconditional", Debug::$LOG_VERBOSE);
 
 			if (ini_get("open_basedir") && function_exists("curl_init")) {
-				_debug("not using CURL due to open_basedir restrictions");
+				Debug::log("not using CURL due to open_basedir restrictions", Debug::$LOG_VERBOSE);
 			}
 
 			if (time() - strtotime($last_unconditional) > MAX_CONDITIONAL_INTERVAL) {
-				_debug("maximum allowed interval for conditional requests exceeded, forcing refetch");
+				Debug::log("maximum allowed interval for conditional requests exceeded, forcing refetch", Debug::$LOG_VERBOSE);
 
 				$force_refetch = true;
 			} else {
-				_debug("stored last modified for conditional request: $stored_last_modified", $debug_enabled);
+				Debug::log("stored last modified for conditional request: $stored_last_modified", Debug::$LOG_VERBOSE);
 			}
 
-			_debug("fetching [$fetch_url] (force_refetch: $force_refetch)...", $debug_enabled);
+			Debug::log("fetching [$fetch_url] (force_refetch: $force_refetch)...", Debug::$LOG_VERBOSE);
 
 			$feed_data = fetch_file_contents([
 				"url" => $fetch_url,
@@ -447,8 +440,8 @@ class RSSUtils {
 
 			$feed_data = trim($feed_data);
 
-			_debug("fetch done.", $debug_enabled);
-			_debug("source last modified: " . $fetch_last_modified, $debug_enabled);
+			Debug::log("fetch done.", Debug::$LOG_VERBOSE);
+			Debug::log("source last modified: " . $fetch_last_modified, Debug::$LOG_VERBOSE);
 
 			if ($feed_data && $fetch_last_modified != $stored_last_modified) {
 				$sth = $pdo->prepare("UPDATE ttrss_feeds SET last_modified = ? WHERE id = ?");
@@ -460,7 +453,7 @@ class RSSUtils {
 				$new_rss_hash = sha1($feed_data);
 
 				if ($new_rss_hash != $rss_hash) {
-					_debug("saving $cache_filename", $debug_enabled);
+					Debug::log("saving $cache_filename", Debug::$LOG_VERBOSE);
 					@file_put_contents($cache_filename, $feed_data);
 				}
 			}
@@ -470,13 +463,13 @@ class RSSUtils {
 			global $fetch_last_error;
 			global $fetch_last_error_code;
 
-			_debug("unable to fetch: $fetch_last_error [$fetch_last_error_code]", $debug_enabled);
+			Debug::log("unable to fetch: $fetch_last_error [$fetch_last_error_code]", Debug::$LOG_VERBOSE);
 
 			// If-Modified-Since
 			if ($fetch_last_error_code != 304) {
 				$error_message = $fetch_last_error;
 			} else {
-				_debug("source claims data not modified, nothing to do.", $debug_enabled);
+				Debug::log("source claims data not modified, nothing to do.", Debug::$LOG_VERBOSE);
 				$error_message = "";
 			}
 
@@ -499,8 +492,8 @@ class RSSUtils {
 			// We use local pluginhost here because we need to load different per-user feed plugins
 			$pluginhost->run_hooks(PluginHost::HOOK_FEED_PARSED, "hook_feed_parsed", $rss);
 
-			_debug("language: $feed_language", $debug_enabled);
-			_debug("processing feed data...", $debug_enabled);
+			Debug::log("language: $feed_language", Debug::$LOG_VERBOSE);
+			Debug::log("processing feed data...", Debug::$LOG_VERBOSE);
 
 			if (DB_TYPE == "pgsql") {
 				$favicon_interval_qpart = "favicon_last_checked < NOW() - INTERVAL '12 hour'";
@@ -524,8 +517,8 @@ class RSSUtils {
 
 			$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);
+			Debug::log("site_url: $site_url", Debug::$LOG_VERBOSE);
+			Debug::log("feed_title: " . $rss->get_title(), Debug::$LOG_VERBOSE);
 
 			if ($favicon_needs_check || $force_refetch) {
 
@@ -535,7 +528,7 @@ class RSSUtils {
 				$favicon_file = ICONS_DIR . "/$feed.ico";
 				$favicon_modified = @filemtime($favicon_file);
 
-				_debug("checking favicon...", $debug_enabled);
+				Debug::log("checking favicon...", Debug::$LOG_VERBOSE);
 
 				RSSUtils::check_feed_favicon($site_url, $feed);
 				$favicon_modified_new = @filemtime($favicon_file);
@@ -556,7 +549,7 @@ class RSSUtils {
 					$favicon_colorstring = ",favicon_avg_color = " . $pdo->quote($favicon_color);
 
 				} else if ($favicon_avg_color == 'fail') {
-					_debug("floicon failed on this file, not trying to recalculate avg color", $debug_enabled);
+					Debug::log("floicon failed on this file, not trying to recalculate avg color", Debug::$LOG_VERBOSE);
 				}
 
 				$sth = $pdo->prepare("UPDATE ttrss_feeds SET favicon_last_checked = NOW()
@@ -564,20 +557,20 @@ class RSSUtils {
 				$sth->execute([$feed]);
 			}
 
-			_debug("loading filters & labels...", $debug_enabled);
+			Debug::log("loading filters & labels...", Debug::$LOG_VERBOSE);
 
 			$filters = load_filters($feed, $owner_uid);
 
-			if ($debug_enabled) {
+			if (Debug::get_loglevel() >= Debug::$LOG_EXTENDED) {
 				print_r($filters);
 			}
 
-			_debug("" . count($filters) . " filters loaded.", $debug_enabled);
+			Debug::log("" . count($filters) . " filters loaded.", Debug::$LOG_VERBOSE);
 
 			$items = $rss->get_items();
 
 			if (!is_array($items)) {
-				_debug("no articles found.", $debug_enabled);
+				Debug::log("no articles found.", Debug::$LOG_VERBOSE);
 
 				$sth = $pdo->prepare("UPDATE ttrss_feeds
 					SET last_updated = NOW(), last_unconditional = NOW(), last_error = '' WHERE id = ?");
@@ -586,19 +579,19 @@ class RSSUtils {
 				return true; // no articles
 			}
 
-			_debug("processing articles...", $debug_enabled);
+			Debug::log("processing articles...", Debug::$LOG_VERBOSE);
 
 			$tstart = time();
 
 			foreach ($items as $item) {
 				$pdo->beginTransaction();
 
-				if (clean($_REQUEST['xdebug']) == 3) {
+				if (Debug::get_loglevel() >= 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);
+					Debug::log("looks like there's too many articles to process at once, breaking out", Debug::$LOG_VERBOSE);
 					$pdo->commit();
 					break;
 				}
@@ -616,11 +609,11 @@ class RSSUtils {
 
 				$entry_guid_hashed = 'SHA1:' . sha1($entry_guid);
 
-				_debug("guid $entry_guid / $entry_guid_hashed", $debug_enabled);
+				Debug::log("guid $entry_guid / $entry_guid_hashed", Debug::$LOG_VERBOSE);
 
 				$entry_timestamp = strip_tags($item->get_date());
 
-				_debug("orig date: " . $item->get_date(), $debug_enabled);
+				Debug::log("orig date: " . $item->get_date(), Debug::$LOG_VERBOSE);
 
 				if ($entry_timestamp == -1 || !$entry_timestamp || $entry_timestamp > time()) {
 					$entry_timestamp = time();
@@ -628,7 +621,7 @@ class RSSUtils {
 
 				$entry_timestamp_fmt = strftime("%Y/%m/%d %H:%M:%S", $entry_timestamp);
 
-				_debug("date $entry_timestamp [$entry_timestamp_fmt]", $debug_enabled);
+				Debug::log("date $entry_timestamp [$entry_timestamp_fmt]", Debug::$LOG_VERBOSE);
 
 				$entry_title = strip_tags($item->get_title());
 
@@ -636,16 +629,16 @@ class RSSUtils {
 
 				$entry_language = mb_substr(trim($item->get_language()), 0, 2);
 
-				_debug("title $entry_title", $debug_enabled);
-				_debug("link $entry_link", $debug_enabled);
-				_debug("language $entry_language", $debug_enabled);
+				Debug::log("title $entry_title", Debug::$LOG_VERBOSE);
+				Debug::log("link $entry_link", Debug::$LOG_VERBOSE);
+				Debug::log("language $entry_language", Debug::$LOG_VERBOSE);
 
 				if (!$entry_title) $entry_title = date("Y-m-d H:i:s", $entry_timestamp);;
 
 				$entry_content = $item->get_content();
 				if (!$entry_content) $entry_content = $item->get_description();
 
-				if (clean($_REQUEST["xdebug"]) == 2) {
+				if (Debug::get_loglevel() >= 3) {
 					print "content: ";
 					print htmlspecialchars($entry_content);
 					print "\n";
@@ -657,9 +650,9 @@ class RSSUtils {
 				$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);
-				_debug("looking for tags...", $debug_enabled);
+				Debug::log("author $entry_author", Debug::$LOG_VERBOSE);
+				Debug::log("num_comments: $num_comments", Debug::$LOG_VERBOSE);
+				Debug::log("looking for tags...", Debug::$LOG_VERBOSE);
 
 				// parse <category> entries into tags
 
@@ -682,9 +675,9 @@ class RSSUtils {
 					if (is_numeric($entry_tags[$i])) $entry_tags[$i] = 't:' . $entry_tags[$i];
 				}
 
-				_debug("tags found: " . join(",", $entry_tags), $debug_enabled);
+				Debug::log("tags found: " . join(",", $entry_tags), Debug::$LOG_VERBOSE);
 
-				_debug("done collecting data.", $debug_enabled);
+				Debug::log("done collecting data.", Debug::$LOG_VERBOSE);
 
 				$sth = $pdo->prepare("SELECT id, content_hash, lang FROM ttrss_entries
 					WHERE guid = ? OR guid = ?");
@@ -725,10 +718,10 @@ class RSSUtils {
 				$entry_plugin_data = "";
 				$entry_current_hash = RSSUtils::calculate_article_hash($article, $pluginhost);
 
-				_debug("article hash: $entry_current_hash [stored=$entry_stored_hash]", $debug_enabled);
+				Debug::log("article hash: $entry_current_hash [stored=$entry_stored_hash]", Debug::$LOG_VERBOSE);
 
 				if ($entry_current_hash == $entry_stored_hash && !isset($_REQUEST["force_rehash"])) {
-					_debug("stored article seems up to date [IID: $base_entry_id], updating timestamp only", $debug_enabled);
+					Debug::log("stored article seems up to date [IID: $base_entry_id], updating timestamp only", Debug::$LOG_VERBOSE);
 
 					// we keep encountering the entry in feeds, so we need to
 					// update date_updated column so that we don't get horrible
@@ -743,26 +736,26 @@ class RSSUtils {
 					continue;
 				}
 
-				_debug("hash differs, applying plugin filters:", $debug_enabled);
+				Debug::log("hash differs, applying plugin filters:", Debug::$LOG_VERBOSE);
 
 				foreach ($pluginhost->get_hooks(PluginHost::HOOK_ARTICLE_FILTER) as $plugin) {
-					_debug("... " . get_class($plugin), $debug_enabled);
+					Debug::log("... " . get_class($plugin), Debug::$LOG_VERBOSE);
 
 					$start = microtime(true);
 					$article = $plugin->hook_article_filter($article);
 
-					_debug("=== " . sprintf("%.4f (sec)", microtime(true) - $start), $debug_enabled);
+					Debug::log(sprintf("=== %.4f (sec)", microtime(true) - $start), Debug::$LOG_VERBOSE);
 
 					$entry_plugin_data .= mb_strtolower(get_class($plugin)) . ",";
 				}
 
-				if (clean($_REQUEST["xdebug"]) == 2) {
+                if (Debug::get_loglevel() >= 3) {
 					print "processed content: ";
 					print htmlspecialchars($article["content"]);
 					print "\n";
 				}
 
-				_debug("plugin data: $entry_plugin_data", $debug_enabled);
+				Debug::log("plugin data: $entry_plugin_data", Debug::$LOG_VERBOSE);
 
 				// Workaround: 4-byte unicode requires utf8mb4 in MySQL. See https://tt-rss.org/forum/viewtopic.php?f=1&t=3377&p=20077#p20077
 				if (DB_TYPE == "mysql" && MYSQL_CHARSET != "UTF8MB4") {
@@ -782,14 +775,14 @@ class RSSUtils {
 					$article["content"], $article["link"], $article["author"],
 					$article["tags"], $matched_rules);
 
-				if ($debug_enabled) {
-					_debug("matched filter rules: ", $debug_enabled);
+				if (Debug::get_loglevel() >= Debug::$LOG_EXTENDED) {
+					Debug::log("matched filter rules: ", Debug::$LOG_VERBOSE);
 
 					if (count($matched_rules) != 0) {
 						print_r($matched_rules);
 					}
 
-					_debug("filter actions: ", $debug_enabled);
+					Debug::log("filter actions: ", Debug::$LOG_VERBOSE);
 
 					if (count($article_filters) != 0) {
 						print_r($article_filters);
@@ -800,7 +793,7 @@ class RSSUtils {
 				$plugin_filter_actions = $pluginhost->get_filter_actions();
 
 				if (count($plugin_filter_names) > 0) {
-					_debug("applying plugin filter actions...", $debug_enabled);
+					Debug::log("applying plugin filter actions...", Debug::$LOG_VERBOSE);
 
 					foreach ($plugin_filter_names as $pfn) {
 						list($pfclass,$pfaction) = explode(":", $pfn["param"]);
@@ -808,18 +801,18 @@ class RSSUtils {
 						if (isset($plugin_filter_actions[$pfclass])) {
 							$plugin = $pluginhost->get_plugin($pfclass);
 
-							_debug("... $pfclass: $pfaction", $debug_enabled);
+							Debug::log("... $pfclass: $pfaction", Debug::$LOG_VERBOSE);
 
 							if ($plugin) {
 								$start = microtime(true);
 								$article = $plugin->hook_article_filter_action($article, $pfaction);
 
-								_debug("=== " . sprintf("%.4f (sec)", microtime(true) - $start), $debug_enabled);
+								Debug::log(sprintf("=== %.4f (sec)"), Debug::$LOG_VERBOSE);
 							} else {
-								_debug("??? $pfclass: plugin object not found.");
+								Debug::log("??? $pfclass: plugin object not found.", Debug::$LOG_VERBOSE);
 							}
 						} else {
-							_debug("??? $pfclass: filter plugin not registered.");
+							Debug::log("??? $pfclass: filter plugin not registered.", Debug::$LOG_VERBOSE);
 						}
 					}
 				}
@@ -834,18 +827,18 @@ class RSSUtils {
 				$entry_score_modifier = (int) $article["score_modifier"];
 				$entry_language = $article["language"];
 
-				if ($debug_enabled) {
-					_debug("article labels:", $debug_enabled);
+				if (Debug::get_loglevel() >= Debug::$LOG_EXTENDED) {
+					Debug::log("article labels:", Debug::$LOG_VERBOSE);
 
 					if (count($article_labels) != 0) {
 						print_r($article_labels);
 					}
 				}
 
-				_debug("force catchup: $entry_force_catchup");
+				Debug::log("force catchup: $entry_force_catchup", Debug::$LOG_VERBOSE);
 
 				if ($cache_images && is_writable(CACHE_DIR . '/images'))
-					RSSUtils::cache_media($entry_content, $site_url, $debug_enabled);
+					RSSUtils::cache_media($entry_content, $site_url);
 
 				$csth = $pdo->prepare("SELECT id FROM ttrss_entries
 					WHERE guid = ? OR guid = ?");
@@ -853,7 +846,7 @@ class RSSUtils {
 
 				if (!$row = $csth->fetch()) {
 
-					_debug("base guid [$entry_guid or $entry_guid_hashed] not found, creating...", $debug_enabled);
+					Debug::log("base guid [$entry_guid or $entry_guid_hashed] not found, creating...", Debug::$LOG_VERBOSE);
 
 					// base post entry does not exist, create it
 
@@ -901,7 +894,7 @@ class RSSUtils {
 
 				if ($row = $csth->fetch()) {
 
-					_debug("base guid found, checking for user record", $debug_enabled);
+					Debug::log("base guid found, checking for user record", Debug::$LOG_VERBOSE);
 
 					$ref_id = $row['id'];
 					$entry_ref_id = $ref_id;
@@ -913,7 +906,7 @@ class RSSUtils {
 
 					$score = RSSUtils::calculate_article_score($article_filters) + $entry_score_modifier;
 
-					_debug("initial score: $score [including plugin modifier: $entry_score_modifier]", $debug_enabled);
+					Debug::log("initial score: $score [including plugin modifier: $entry_score_modifier]", Debug::$LOG_VERBOSE);
 
 					// check for user post link to main table
 
@@ -926,10 +919,10 @@ class RSSUtils {
 						$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);
+						Debug::log("user record FOUND: RID: $entry_ref_id, IID: $entry_int_id", Debug::$LOG_VERBOSE);
 					} else {
 
-						_debug("user record not found, creating...", $debug_enabled);
+						Debug::log("user record not found, creating...", Debug::$LOG_VERBOSE);
 
 						if ($score >= -500 && !RSSUtils::find_article_filter($article_filters, 'catchup') && !$entry_force_catchup) {
 							$unread = 1;
@@ -974,7 +967,7 @@ class RSSUtils {
 							$entry_int_id = $row['int_id'];
 					}
 
-					_debug("resulting RID: $entry_ref_id, IID: $entry_int_id", $debug_enabled);
+					Debug::log("resulting RID: $entry_ref_id, IID: $entry_int_id", Debug::$LOG_VERBOSE);
 
 					if (DB_TYPE == "pgsql")
 						$tsvector_qpart = "tsvector_combined = to_tsvector(:ts_lang, :ts_content),";
@@ -1017,7 +1010,7 @@ class RSSUtils {
 					$sth->execute([$score, $ref_id]);
 
 					if ($mark_unread_on_update) {
-						_debug("article updated, marking unread as requested.", $debug_enabled);
+						Debug::log("article updated, marking unread as requested.", Debug::$LOG_VERBOSE);
 
 						$sth = $pdo->prepare("UPDATE ttrss_user_entries
 							SET last_read = null, unread = true WHERE ref_id = ?");
@@ -1025,18 +1018,18 @@ class RSSUtils {
 					}
 				}
 
-				_debug("assigning labels [other]...", $debug_enabled);
+				Debug::log("assigning labels [other]...", Debug::$LOG_VERBOSE);
 
 				foreach ($article_labels as $label) {
 					Labels::add_article($entry_ref_id, $label[1], $owner_uid);
 				}
 
-				_debug("assigning labels [filters]...", $debug_enabled);
+				Debug::log("assigning labels [filters]...", Debug::$LOG_VERBOSE);
 
 				RSSUtils::assign_article_to_label_filters($entry_ref_id, $article_filters,
 					$owner_uid, $article_labels);
 
-				_debug("looking for enclosures...", $debug_enabled);
+				Debug::log("looking for enclosures...", Debug::$LOG_VERBOSE);
 
 				// enclosures
 
@@ -1064,10 +1057,10 @@ class RSSUtils {
 				}
 
 				if ($cache_images && is_writable(CACHE_DIR . '/images'))
-					RSSUtils::cache_enclosures($enclosures, $site_url, $debug_enabled);
+					RSSUtils::cache_enclosures($enclosures, $site_url);
 
-				if ($debug_enabled) {
-					_debug("article enclosures:", $debug_enabled);
+				if (Debug::get_loglevel() >= Debug::$LOG_EXTENDED) {
+					Debug::log("article enclosures:", Debug::$LOG_VERBOSE);
 					print_r($enclosures);
 				}
 
@@ -1126,8 +1119,8 @@ class RSSUtils {
 
 				$filtered_tags = array_unique($filtered_tags);
 
-				if ($debug_enabled) {
-					_debug("filtered article tags:", $debug_enabled);
+				if (Debug::get_loglevel() >= Debug::$LOG_EXTENDED) {
+					Debug::log("filtered article tags:", Debug::$LOG_VERBOSE);
 					print_r($filtered_tags);
 				}
 
@@ -1170,14 +1163,14 @@ class RSSUtils {
 					$tsth->execute([$tags_str, $entry_ref_id, $owner_uid]);
 				}
 
-				_debug("article processed", $debug_enabled);
+				Debug::log("article processed", Debug::$LOG_VERBOSE);
 
 				$pdo->commit();
 			}
 
-			_debug("purging feed...", $debug_enabled);
+			Debug::log("purging feed...", Debug::$LOG_VERBOSE);
 
-			purge_feed($feed, 0, $debug_enabled);
+			purge_feed($feed, 0);
 
 			$sth = $pdo->prepare("UPDATE ttrss_feeds
 				SET last_updated = NOW(), last_unconditional = NOW(), last_error = '' WHERE id = ?");
@@ -1187,11 +1180,11 @@ class RSSUtils {
 
 			$error_msg = mb_substr($rss->error(), 0, 245);
 
-			_debug("fetch error: $error_msg", $debug_enabled);
+			Debug::log("fetch error: $error_msg", Debug::$LOG_VERBOSE);
 
 			if (count($rss->errors()) > 1) {
 				foreach ($rss->errors() as $error) {
-					_debug("+ $error");
+					Debug::log("+ $error", Debug::$LOG_VERBOSE);
 				}
 			}
 
@@ -1203,12 +1196,12 @@ class RSSUtils {
 			return false;
 		}
 
-		_debug("done", $debug_enabled);
+		Debug::log("done", Debug::$LOG_VERBOSE);
 
 		return true;
 	}
 
-	static function cache_enclosures($enclosures, $site_url, $debug) {
+	static function cache_enclosures($enclosures, $site_url) {
 		foreach ($enclosures as $enc) {
 
 			if (preg_match("/(image|audio|video)/", $enc[1])) {
@@ -1217,7 +1210,7 @@ class RSSUtils {
 
 				$local_filename = CACHE_DIR . "/images/" . sha1($src);
 
-				if ($debug) _debug("cache_enclosures: downloading: $src to $local_filename");
+				Debug::log("cache_enclosures: downloading: $src to $local_filename", Debug::$LOG_VERBOSE);
 
 				if (!file_exists($local_filename)) {
 					$file_content = fetch_file_contents($src);
@@ -1232,7 +1225,7 @@ class RSSUtils {
 		}
 	}
 
-	static function cache_media($html, $site_url, $debug) {
+	static function cache_media($html, $site_url) {
 		libxml_use_internal_errors(true);
 
 		$charset_hack = '<head>
@@ -1251,10 +1244,10 @@ class RSSUtils {
 
 				$local_filename = CACHE_DIR . "/images/" . sha1($src);
 
-				if ($debug) _debug("cache_media: checking $src");
+				Debug::log("cache_media: checking $src", Debug::$LOG_VERBOSE);
 
 				if (!file_exists($local_filename)) {
-					if ($debug) _debug("cache_media: downloading: $src to $local_filename");
+					Debug::log("cache_media: downloading: $src to $local_filename", Debug::$LOG_VERBOSE);
 
 					$file_content = fetch_file_contents($src);
 
@@ -1268,8 +1261,8 @@ class RSSUtils {
 		}
 	}
 
-	static function expire_error_log($debug) {
-		if ($debug) _debug("Removing old error log entries...");
+	static function expire_error_log() {
+		Debug::log("Removing old error log entries...");
 
 		$pdo = Db::pdo();
 
@@ -1282,8 +1275,8 @@ class RSSUtils {
 		}
 	}
 
-	static function expire_lock_files($debug) {
-		//if ($debug) _debug("Removing old lock files...");
+	static function expire_lock_files() {
+		Debug::log("Removing old lock files...", Debug::$LOG_VERBOSE);
 
 		$num_deleted = 0;
 
@@ -1300,14 +1293,14 @@ class RSSUtils {
 			}
 		}
 
-		if ($debug) _debug("Removed $num_deleted old lock files.");
+		Debug::log("Removed $num_deleted old lock files.");
 	}
 
-	static function expire_cached_files($debug) {
+	static function expire_cached_files() {
 		foreach (array("feeds", "images", "export", "upload") as $dir) {
 			$cache_dir = CACHE_DIR . "/$dir";
 
-//			if ($debug) _debug("Expiring $cache_dir");
+			Debug::log("Expiring $cache_dir", Debug::$LOG_VERBOSE);
 
 			$num_deleted = 0;
 
@@ -1325,7 +1318,7 @@ class RSSUtils {
 				}
 			}
 
-			if ($debug) _debug("$cache_dir: removed $num_deleted files.");
+			Debug::log("$cache_dir: removed $num_deleted files.");
 		}
 	}
 
@@ -1485,7 +1478,7 @@ class RSSUtils {
 			mb_strtolower(strip_tags($title), 'utf-8'));
 	}
 
-	static function cleanup_counters_cache($debug) {
+	static function cleanup_counters_cache() {
 		$pdo = Db::pdo();
 
 		$res = $pdo->query("DELETE FROM ttrss_counters_cache
@@ -1504,7 +1497,7 @@ class RSSUtils {
 
 		$crows = $res->rowCount();
 
-		if ($debug) _debug("Removed $frows (feeds) $crows (cats) orphaned counter cache entries.");
+		Debug::log("Removed $frows (feeds) $crows (cats) orphaned counter cache entries.");
 	}
 
 	static function housekeeping_user($owner_uid) {
@@ -1515,19 +1508,16 @@ class RSSUtils {
 		$tmph->run_hooks(PluginHost::HOOK_HOUSE_KEEPING, "hook_house_keeping", "");
 	}
 
-	static function housekeeping_common($debug) {
-		RSSUtils::expire_cached_files($debug);
-		RSSUtils::expire_lock_files($debug);
-		RSSUtils::expire_error_log($debug);
+	static function housekeeping_common() {
+		RSSUtils::expire_cached_files();
+		RSSUtils::expire_lock_files();
+		RSSUtils::expire_error_log();
 
 		$count = RSSUtils::update_feedbrowser_cache();
-		_debug("Feedbrowser updated, $count feeds processed.");
-
-		Article::purge_orphans( true);
-		RSSUtils::cleanup_counters_cache($debug);
+		Debug::log("Feedbrowser updated, $count feeds processed.");
 
-		//$rc = cleanup_tags( 14, 50000);
-		//_debug("Cleaned $rc cached tags.");
+		Article::purge_orphans();
+		RSSUtils::cleanup_counters_cache();
 
 		PluginHost::getInstance()->run_hooks(PluginHost::HOOK_HOUSE_KEEPING, "hook_house_keeping", "");
 	}

+ 176 - 168
css/cdm.less

@@ -1,27 +1,27 @@
 .cdm {
 	margin-right : 4px;
 
-	.cdmHeader, .cdmFooter {
+	.header, .footer {
 		display : table;
 	}
 
-	.cdmHeader img, .cdmHeader input, .cdmFooter img {
+	.header img, .header input, .footer img {
 		vertical-align : middle;
 	}
 
-	.cdmHeader > div, .cdmFooter > div {
+	.header > div, .footer > div {
 		white-space : nowrap;
 	}
 
-	.cdmHeader > span, .cdmFooter > span.left {
+	.header > span, .footer > span.left {
 		width : 100%;
 	}
 
-	.cdmHeader img, .cdmFooter img {
+	.header img, .footer img {
 		margin : 0px 4px;
 	}
 
-	.cdmHeader {
+	.header {
 		> * {
 			display : table-cell;
 			padding : 5px;
@@ -55,7 +55,7 @@
 		}
 	}
 
-	.cdmFooter {
+	.footer {
 		height : 30px;
 		padding-left : 5px;
 		font-weight : normal;
@@ -68,11 +68,11 @@
 		}
 	}
 
-	.cdmIntermediate {
+	.intermediate {
 		margin : 10px;
 	}
 
-	.cdmContentInner {
+	.content-inner {
 		margin : 10px;
 		line-height : 1.5;
 		font-size : 16px;
@@ -89,10 +89,10 @@
 
 	}
 
-	.cdmIntermediate img,
-	.cdmIntermediate video,
-	.cdmContentInner img,
-	.cdmContentInner video {
+	.intermediate img,
+	.intermediate video,
+	.content-inner img,
+	.content-inner video {
 		border-width : 0px;
 		max-width : 98%;
 		height : auto;
@@ -103,7 +103,11 @@
 	margin-top : 4px;
 	margin-bottom : 4px;
 
-	.cdmFooter {
+	.collapse {
+		display : none;
+	}
+
+	.footer {
 		border: 0px solid #ddd;
 		border-bottom-width: 1px;
 	}
@@ -115,71 +119,11 @@
 
 }
 
-.cdm.expandable {
-	background-color : #f0f0f0;
-	border: 0px solid #ddd;
-	border-bottom-width: 1px;
-
-	> hr {
-		display : none;
-	}
-
-	div.cdmHeader span.titleWrap {
-		white-space : nowrap;
-		text-overflow : ellipsis;
-		overflow : hidden;
-		max-width : 500px;
-	}
-}
-
-.cdm.expandable.Unread {
-	background : white;
-}
-
-.cdm.expandable.Selected:not(.active) {
-	background : desaturate(@color-accent, 25%);
-
-	a,
-	.cdmHeader a.title,
-	span {
-		color : white;
-	}
-}
-
-.cdm.expandable.active {
-	background : white ! important;
-}
-
-div.cdm.expandable.active div.cdmHeader span.titleWrap {
-	white-space : normal;
-}
-
-div.cdm.expandable div.cdmHeader a.title {
-	font-weight : 600;
-	color : @default-text;
-	font-size : 14px;
-	transition : color 0.2s, background 0.2s;
-	text-rendering: optimizelegibility;
-	font-family : @fonts-ui-bold;
-}
-
-div.cdm.expandable.Unread div.cdmHeader a.title {
-	color : black;
-}
-
-div.cdm.expandable.active div.cdmHeader a.title {
-	color: @color-link;
-	font-size: 16px;
-	font-weight: 600;
-	text-rendering: optimizelegibility;
-	font-family: @fonts-ui-bold;
-}
-
-div.cdm.expanded div.cdmHeader {
+div.cdm.expanded div.header {
 	background : transparent ! important;
 }
 
-div.cdm.expanded div.cdmHeader a.title {
+div.cdm.expanded div.header a.title {
 	font-size : 16px;
 	color : #999;
 	font-weight : 600;
@@ -192,95 +136,89 @@ div.cdm.expanded.active {
 	background : white;
 }
 
-div.cdm.expanded.active div.cdmHeader a.title {
+div.cdm.expanded.active div.header a.title {
 	color : @color-link;
 }
 
-div.cdm.expanded.Unread div.cdmHeader a.title {
+div.cdm.expanded.Unread div.header a.title {
 	color : black;
 }
 
-div.cdm.expanded div.cdmContent {
+div.cdm.expanded div.content {
 	color : @default-text;
 }
 
-div.cdm.expanded.Unread div.cdmContent {
+div.cdm.expanded.Unread div.content {
 	color : black;
 }
 
-div.cdm.active div.cdmContent {
+div.cdm.active div.content {
 	color : black;
 }
 
-span.cdmExcerpt {
-	white-space : nowrap;
-	font-size : 11px;
-	color : #999;
-	font-weight : normal;
-	cursor : pointer;
-}
-
-div.cdmContent div.postEnclosures {
-	margin-top : 1em;
-	color : @default-text;
-}
+.cdm {
+	div.content div.postEnclosures {
+		margin-top: 1em;
+		color: @default-text;
+	}
 
-div.cdmFeedTitle {
-	border: 0px solid @color-link;
-	border-bottom-width: 1px;
-	padding : 5px 3px 5px 5px;
-}
+	div.feed-title {
+		border: 0px solid @color-link;
+		border-bottom-width: 1px;
+		padding: 5px 3px 5px 5px;
+	}
 
-div.cdmFeedTitle a.title {
-	color : @default-text;
-	font-weight : bold;
-}
+	div.feed-title a.title {
+		color: @default-text;
+		font-weight: bold;
+	}
 
-div.cdmFeedTitle a {
-	color : @default-text;
-}
+	div.feed-title a {
+		color: @default-text;
+	}
 
-div.cdmFeedTitle a:hover {
-	color : @color-link;
-}
+	div.feed-title a:hover {
+		color: @color-link;
+	}
 
-div.cdmHeader span.hlFeed {
-	float : right;
-	font-weight : normal;
-	font-style : italic;
-}
+	div.header span.feed {
+		float: right;
+		font-weight: normal;
+		font-style: italic;
+	}
 
-div.cdmHeader div.hlFeed, div.cdmHeader div.hlFeed a {
-	vertical-align : middle;
-	color : @default-text;
-	font-weight : normal;
-	font-style : italic;
-	font-size : 11px;
-}
+	div.header div.feed, div.header div.feed a {
+		vertical-align: middle;
+		color: @default-text;
+		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.content-inner p {
+		/*max-width : 650px;*/
+		-webkit-hyphens: auto;
+		-moz-hyphens: auto;
+		hyphens: auto;
+	}
 
-div.cdmContentInner p {
-	/*max-width : 650px;*/
-	-webkit-hyphens: auto;
-	-moz-hyphens: auto;
-	hyphens: auto;
-}
+	div.content-inner iframe {
+		min-width : 50%;
+		max-width : 98%;
+	}
 
-div.cdmContentInner iframe {
-	min-width : 50%;
-	max-width : 98%;
-}
+	div.header span.author {
+		white-space : nowrap;
+		color : @default-text;
+		font-size : 11px;
+		font-weight : normal;
+	}
 
-div.cdmHeader span.author {
-	white-space : nowrap;
-	color : @default-text;
-	font-size : 11px;
-	font-weight : normal;
+	.feed a {
+		border-radius : 4px;
+		display : inline-block;
+		padding : 1px 4px 1px 4px;
+	}
 }
 
 div#floatingTitle {
@@ -326,7 +264,7 @@ div#floatingTitle {
 		margin-left : 0px;
 	}
 
-	div.hlFeed {
+	div.feed {
 		padding-right : 10px;
 		color : @default-text;
 		font-weight : normal;
@@ -335,7 +273,7 @@ div#floatingTitle {
 		white-space : nowrap;
 	}
 
-	div.hlFeed a {
+	div.feed a {
 		border-radius : 4px;
 		display : inline-block;
 		padding : 1px 4px 1px 4px;
@@ -348,72 +286,142 @@ div#floatingTitle {
 		font-size : 11px;
 	}
 
-	div.hlFeed a {
+	div.feed a {
 		color : @default-text;
 	}
 
+	.collapse {
+		display : none;
+	}
+
 	span.titleWrap {
 		width : 100%;
 		white-space : normal;
 	}
 
 	.dijit,
-	img.hlScorePic {
+	img.score-pic {
 		display : none;
 	}
 
+	.feed-title {
+		> * {
+			display : table-cell;
+			vertical-align : middle;
+		}
+
+		a.title {
+			width : 100%;
+		}
+
+		a.catchup {
+			text-align : right;
+			color : @default-text;
+			padding-right : 10px;
+			font-size : 11px;
+			white-space : nowrap;
+		}
+
+		a.catchup:hover {
+			color : @color-link;
+		}
+
+	}
 }
 
 div#floatingTitle.Unread a.title {
 	color : black;
 }
 
-.cdm.high .cdmHeader {
+.cdm.high .header {
 	a.title.high,
-	.cdmExcerpt,
+	.excerpt,
 	span.author {
 		color : #00aa00;
 	}
 }
 
-.cdm.Unread.high .cdmHeader {
+.cdm.Unread.high .header {
 	a.title.high,
-	.cdmExcerpt,
+	.excerpt,
 	span.author {
 		color : #00dd00;
 	}
 }
 
-.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 {
+.cdm .header a.title.low,
+.cdm.low .header .excerpt,
+.cdm.Unread .header a.title.low,
+.cdm.Unread.low .header .excerpt,
+.cdm.low .header span.author {
 	color : #909090;
 	text-decoration : line-through;
 }
 
-.cdmFeedTitle {
-
-	> * {
-		display : table-cell;
-		vertical-align : middle;
-	}
+.cdm.expandable {
+	background-color : #f0f0f0;
+	border: 0px solid #ddd;
+	border-bottom-width: 1px;
 
-	a.title {
-		width : 100%;
+	> hr {
+		display : none;
 	}
 
-	a.catchup {
-		text-align : right;
-		color : @default-text;
-		padding-right : 10px;
-		font-size : 11px;
+	div.header span.titleWrap {
 		white-space : nowrap;
+		text-overflow : ellipsis;
+		overflow : hidden;
+		max-width : 500px;
 	}
+}
+
+.cdm.expandable.Unread {
+	background : white;
+}
+
+.cdm.expandable.Selected:not(.active) {
+	background : desaturate(@color-accent, 25%);
 
-	a.catchup:hover {
-		color : @color-link;
+	a,
+	.header a.title,
+	span {
+		color : white;
 	}
+}
+
+.cdm.expandable.active {
+	background : white ! important;
+}
+
+div.cdm.expandable.active div.header span.titleWrap {
+	white-space : normal;
+}
+
+div.cdm.expandable div.header a.title {
+	font-weight : 600;
+	color : @default-text;
+	font-size : 14px;
+	transition : color 0.2s, background 0.2s;
+	text-rendering: optimizelegibility;
+	font-family : @fonts-ui-bold;
+}
+
+div.cdm.expandable.Unread div.header a.title {
+	color : black;
+}
 
+div.cdm.expandable.active div.header a.title {
+	color: @color-link;
+	font-size: 16px;
+	font-weight: 600;
+	text-rendering: optimizelegibility;
+	font-family: @fonts-ui-bold;
 }
+
+div.cdm.expandable:not(.active) {
+	cursor : pointer;
+
+	.content, .collapse {
+		display : none;
+	}
+}

File diff suppressed because it is too large
+ 0 - 0
css/default.css


+ 34 - 43
css/tt-rss.less

@@ -9,17 +9,17 @@ body.ttrss_main {
 		outline: none;
 	}
 
-	div.postReply {
+	div.post {
 		padding : 0px;
 
-		div.postHeader {
+		div.header {
 			padding : 5px;
 			color : #909090;
 			border: 0px solid #ddd;
 			border-bottom-width: 1px;
 			background: #f0f0f0;
 
-			div.postDate {
+			div.date {
 				text-align : right;
 				float : right;
 			}
@@ -35,7 +35,7 @@ body.ttrss_main {
 			}
 		}
 
-		div.postTitle {
+		div.title {
 			overflow : hidden;
 			font-size : 15px;
 			text-overflow: ellipsis;
@@ -45,11 +45,11 @@ body.ttrss_main {
 			font-family : @fonts-ui-bold;
 		}
 
-		div.postDate {
+		div.date {
 			padding-left : 10px;
 		}
 
-		div.postContent {
+		div.content {
 			padding : 10px;
 			font-size : 16px;
 
@@ -221,7 +221,7 @@ body.ttrss_main {
 		padding : 1px;
 		transition : color 0.2s, background 0.2s;
 
-		div.hlTitle {
+		div.title {
 			display : table-cell;
 			cursor : pointer;
 			width : 100%;
@@ -233,31 +233,31 @@ body.ttrss_main {
 			padding: 4px 6px;
 		}
 
-		div.hlLeft {
+		div.left {
 			display : table-cell;
 			vertical-align : middle;
 			white-space: nowrap;
 		}
 
-		div.hlRight {
+		div.right {
 			display : table-cell;
 			white-space: nowrap;
 			text-align : right;
 			vertical-align : middle;
 		}
 
-		div.hlRight img {
+		div.right img {
 			max-width : 16px;
 			max-height : 16px;
 		}
 
-		span.hlFeed {
+		span.feed {
 			display : table-cell;
 			vertical-align : middle;
 			text-align : right;
 		}
 
-		span.hlFeed a {
+		span.feed a {
 			border-radius : 4px;
 			display : inline-block;
 			padding : 1px 4px 1px 4px;
@@ -268,11 +268,11 @@ body.ttrss_main {
 			white-space : nowrap;
 		}
 
-		span.hlFeed a:hover {
+		span.feed a:hover {
 			color : @color-accent;
 		}
 
-		span.hlUpdated {
+		span.updated {
 			color : @default-text;
 			display : table-cell;
 			vertical-align : middle;
@@ -282,55 +282,55 @@ body.ttrss_main {
 			padding-left : 10px;
 		}
 
-		span.hlUpdated div {
+		span.updated div {
 			display : inline-block;
 		}
 
-		div.hlLeft {
+		div.left {
 			padding-left : 8px;
 		}
 
-		div.hlLeft input {
+		div.left input {
 			margin-left : 4px;
 			margin-right : 4px;
 		}
 
-		div.hlLeft img, div.hlRight img {
+		div.left img, div.right img {
 			margin : 0px 4px;
 		}
 
-		div.hlLeft img {
+		div.left img {
 			width : 16px;
 			height : 16px;
 		}
 
-		div.hlTitle a {
+		div.title a {
 			font-weight : 600;
 			text-rendering: optimizelegibility;
 			font-family : @fonts-ui;
 			color : #777;
 		}
 
-		a.title.high, span.hlContent.high .contentPreview {
+		a.title.high, span.hl-content.high .preview {
 			color : #00aa00;
 		}
 	}
 
-	.hl.Unread a.title.high, .hl.Unread span.hlContent.high .contentPreview {
+	.hl.Unread a.title.high, .hl.Unread span.hl-content.high .preview {
 		color : #00dd00;
 	}
 
-	.hl a.title.low, span.hlContent.low .contentPreview,
-	.hl.Unread a.title.low, .hl.Unread span.hlContent.low .contentPreview {
+	.hl a.title.low, span.hl-content.low .preview,
+	.hl.Unread a.title.low, .hl.Unread span.hl-content.low .preview {
 		color : #909090;
 		text-decoration : line-through;
 	}
 
-	.hl.Unread div.hlTitle a {
+	.hl.Unread div.title a {
 		color : black;
 	}
 
-	.hl.active div.hlTitle a {
+	.hl.active div.title a {
 		color : @color-accent;
 		/* text-shadow : 1px 1px 2px #fff; */
 	}
@@ -345,8 +345,8 @@ body.ttrss_main {
 		background : desaturate(@color-accent, 25%);
 
 		a,
-		.hlFeed a,
-		.hlContent a.title,
+		.feed a,
+		.hl-content a.title,
 		span {
 			color : white;
 		}
@@ -380,7 +380,6 @@ body.ttrss_main {
 	.dijitContentPane code {
 		color : #009900;
 		font-family : monospace;
-		font-size : 12px;
 	}
 
 	#content-insert pre,
@@ -541,7 +540,7 @@ body.ttrss_main {
 	}
 
 
-	span.contentPreview {
+	span.preview {
 		color : #999;
 		font-weight : normal;
 		font-size : 12px;
@@ -561,14 +560,14 @@ body.ttrss_main {
 		border-radius : 4px;
 	}
 
-	img.markedPic, img.pubPic {
+	img.marked-pic, img.pub-pic {
 		cursor : pointer;
 		vertical-align : middle;
 		opacity : 0.5;
 		transition : opacity 0.25s;
 	}
 
-	img.markedPic:hover, img.pubPic:hover {
+	img.marked-pic:hover, img.pub-pic:hover {
 		opacity : 1;
 	}
 
@@ -704,17 +703,17 @@ body.ttrss_main {
 	}
 
 
-	div#headlines-frame.wide .hlTitle {
+	div#headlines-frame.wide .title {
 		max-width : none;
 		overflow : visible;
 		white-space : normal;
 	}
 
-	div#headlines-frame.wide .hl .hlFeed {
+	div#headlines-frame.wide .hl .feed {
 		display : none;
 	}
 
-	img.hlScorePic {
+	img.score-pic {
 		vertical-align : middle;
 		width : 16px;
 		height : 16px;
@@ -1057,14 +1056,6 @@ body.ttrss_main {
 		}
 	}
 
-	span.collapseBtn {
-		cursor : pointer;
-
-		img {
-			vertical-align : middle;
-		}
-	}
-
 	select.attachments {
 		display : block;
 		margin-top : 10px;

+ 80 - 80
css/zoom.less

@@ -5,104 +5,104 @@ body.ttrss_zoom {
 	max-width : 800px;
 	background : #f5f5f5;
 
-	div.postHeader {
-		margin : 10px;
-		padding-bottom : 10px;
-		border: 0px solid #eee;
-		border-bottom-width: 1px;
+	div.post {
+		border : 1px solid #ddd;
 		background : white;
-		font-size : 12px;
-		color : #555;
+		box-shadow : 0px 1px 1px -1px rgba(0,0,0,0.1);
 
-		.author {
-			font-size : 11px;
-		}
+		div.header {
+			margin : 10px;
+			padding-bottom : 10px;
+			border: 0px solid #eee;
+			border-bottom-width: 1px;
+			background : white;
+			font-size : 12px;
+			color : #555;
 
-		div.postFeedTitle {
-			float : left;
-			text-align : right;
-		}
+			.author {
+				font-size : 11px;
+			}
 
-		a.postComments {
-			text-align : right;
-		}
+			div.feed-title {
+				float : left;
+				text-align : right;
+			}
 
-		div.postDate {
-			float : none;
-			text-align : right;
-			margin-bottom : 5px;
-		}
+			a.comments {
+				text-align : right;
+			}
 
-		div.postTags {
+			div.date {
+				float : none;
+				text-align : right;
+				margin-bottom : 5px;
+			}
 
-		}
-
-		div.postTags img {
-			vertical-align : middle;
-		}
+			div.tags {
 
-		div.postTitle {
-			white-space : normal;
-			font-size : 16px;
-			margin-bottom : 5px;
-		}
+			}
 
-	}
-	
-	p {
-		-webkit-hyphens: auto;
-		-moz-hyphens: auto;
-		hyphens: auto;
-	}
+			div.tags img {
+				vertical-align : middle;
+			}
 
-	div.postReply {
-		border : 1px solid #ddd;
-		background : white;
-		box-shadow : 0px 1px 1px -1px rgba(0,0,0,0.1);
-	}
-
-	div.footer {
-		margin-top : 1em;
-		text-align : center;
-	}
-
-
-	div.postContent {
-		font-size : 15px;
-		line-height : 1.5;
-		padding : 10px;
-
-		img, video {
-			max-width : 760px;
-			height : auto;
+			div.title {
+				white-space : normal;
+				font-size : 16px;
+				margin-bottom : 5px;
+			}
 		}
 
-		blockquote {
-			margin : 5px 0px 5px 0px;
-			color : @default-text;
-			padding-left : 10px;
-			border: 0px solid #ccc;
-			border-left-width: 4px;
+		p {
+			-webkit-hyphens: auto;
+			-moz-hyphens: auto;
+			hyphens: auto;
 		}
 
-		code {
-			color : #009900;
-			font-family : monospace;
-			font-size : 12px;
+		div.footer {
+			margin-top : 1em;
+			text-align : center;
 		}
 
-		pre {
-			margin : 5px 0px 5px 0px;
+		div.content {
+			font-size : 15px;
+			line-height : 1.5;
 			padding : 10px;
-			color : @default-text;
-			font-family : monospace;
-			font-size : 12px;
-			border: 0px solid #ccc;
-			background : #f5f5f5;
-			display : block;
-			max-width : 98%;
-			overflow : auto;
+			border-width : 0px;
+
+			img, video {
+				max-width : 760px;
+				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;
+			}
 		}
 	}
+
 }
 

+ 16 - 73
include/functions.php

@@ -13,7 +13,6 @@
 	$fetch_last_error_content = false; // curl only for the time being
 	$fetch_effective_url = false;
 	$fetch_curl_used = false;
-	$suppress_debugging = false;
 
 	libxml_disable_entity_loader(true);
 
@@ -156,67 +155,11 @@
 
 	$schema_version = false;
 
-	function _debug_suppress($suppress) {
-		global $suppress_debugging;
-
-		$suppress_debugging = $suppress;
+	// TODO: compat wrapper, remove at some point
+	function _debug($msg) {
+	    Debug::log($msg);
 	}
 
-	/**
-	 * Print a timestamped debug message.
-	 *
-	 * @param string $msg The debug message.
-	 * @return void
-	 */
-	function _debug($msg, $show = true) {
-		global $suppress_debugging;
-
-		//echo "[$suppress_debugging] $msg $show\n";
-
-		if ($suppress_debugging) return false;
-
-		$ts = strftime("%H:%M:%S", time());
-		if (function_exists('posix_getpid')) {
-			$ts = "$ts/" . posix_getpid();
-		}
-
-		if ($show && !(defined('QUIET') && QUIET)) {
-			print "[$ts] $msg\n";
-		}
-
-		if (defined('LOGFILE'))  {
-			$fp = fopen(LOGFILE, 'a+');
-
-			if ($fp) {
-				$locked = false;
-
-				if (function_exists("flock")) {
-					$tries = 0;
-
-					// try to lock logfile for writing
-					while ($tries < 5 && !$locked = flock($fp, LOCK_EX | LOCK_NB)) {
-						sleep(1);
-						++$tries;
-					}
-
-					if (!$locked) {
-						fclose($fp);
-						return;
-					}
-				}
-
-				fputs($fp, "[$ts] $msg\n");
-
-				if (function_exists("flock")) {
-					flock($fp, LOCK_UN);
-				}
-
-				fclose($fp);
-			}
-		}
-
-	} // function _debug
-
 	/**
 	 * Purge a feed old posts.
 	 *
@@ -227,7 +170,7 @@
 	 * @access public
 	 * @return void
 	 */
-	function purge_feed($feed_id, $purge_interval, $debug = false) {
+	function purge_feed($feed_id, $purge_interval) {
 
 		if (!$purge_interval) $purge_interval = feed_purge_interval($feed_id);
 
@@ -292,9 +235,7 @@
 
 		CCache::update($feed_id, $owner_uid);
 
-		if ($debug) {
-			_debug("Purged feed $feed_id ($purge_interval): deleted $rows articles");
-		}
+        Debug::log("Purged feed $feed_id ($purge_interval): deleted $rows articles");
 
 		return $rows;
 	} // function purge_feed
@@ -421,7 +362,7 @@
 				// holy shit closures in php
 				// download & upload are *expected* sizes respectively, could be zero
 				curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function($curl_handle, $download_size, $downloaded, $upload_size, $uploaded) use( &$max_size) {
-					//_debug("[curl progressfunction] $downloaded $max_size");
+					Debug::log("[curl progressfunction] $downloaded $max_size", Debug::$LOG_EXTENDED);
 
 					return ($downloaded > $max_size) ? 1 : 0; // if max size is set, abort when exceeding it
 				});
@@ -1223,8 +1164,8 @@
 				"feed_debug_viewfeed" => __("Debug viewfeed()"),
 				"catchup_all" => __("Mark all feeds as read"),
 				"cat_toggle_collapse" => __("Un/collapse current category"),
-				"toggle_combined_mode" => __("Toggle combined mode"),
-				"toggle_cdm_expanded" => __("Toggle auto expand in combined mode")),
+				"toggle_cdm_expanded" => __("Toggle auto expand in combined mode"),
+				"toggle_combined_mode" => __("Toggle combined mode")),
 			__("Go to") => array(
 				"goto_all" => __("All articles"),
 				"goto_fresh" => __("Fresh"),
@@ -1310,10 +1251,8 @@
 			"^(191)|Ctrl+/" => "help_dialog",
 		);
 
-		if (get_pref('COMBINED_DISPLAY_MODE')) {
-			$hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
-			$hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
-		}
+		$hotkeys["^(38)|Ctrl-up"] = "prev_article_noscroll";
+		$hotkeys["^(40)|Ctrl-down"] = "next_article_noscroll";
 
 		foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HOTKEY_MAP) as $plugin) {
 			$hotkeys = $plugin->hook_hotkey_map($hotkeys);
@@ -1823,6 +1762,10 @@
 						array_push($attrs_to_remove, $attr);
 					}
 
+					if (strpos($attr->nodeName, "data-") === 0) {
+						array_push($attrs_to_remove, $attr);
+					}
+
 					if ($attr->nodeName == 'href' && stripos($attr->value, 'javascript:') === 0) {
 						array_push($attrs_to_remove, $attr);
 					}
@@ -2152,7 +2095,7 @@
 		$sth = $pdo->prepare("SELECT access_key FROM ttrss_access_keys
 				WHERE feed_id = ? AND is_cat = ?