1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/ttrss_ynh.git synced 2024-10-01 13:34:46 +02:00

update 1.14

This commit is contained in:
Beudbeud 2014-11-29 16:42:53 +01:00
parent debbbf6bf5
commit eb354e8db2
185 changed files with 62547 additions and 32735 deletions

View file

@ -4,7 +4,7 @@ Tiny Tiny RSS
Web-based news feed aggregator, designed to allow you to read news from Web-based news feed aggregator, designed to allow you to read news from
any location, while feeling as close to a real desktop application as possible. any location, while feeling as close to a real desktop application as possible.
http://tt-rss.org http://tt-rss.org (http://mirror.tt-rss.org)
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by

0
source/cache/export/.empty vendored Executable file
View file

0
source/cache/images/.empty vendored Executable file
View file

0
source/cache/js/.empty vendored Executable file
View file

0
source/cache/simplepie/.empty vendored Executable file
View file

0
source/cache/upload/.empty vendored Normal file
View file

View file

@ -2,7 +2,7 @@
class API extends Handler { class API extends Handler {
const API_LEVEL = 7; const API_LEVEL = 9;
const STATUS_OK = 0; const STATUS_OK = 0;
const STATUS_ERR = 1; const STATUS_ERR = 1;
@ -77,6 +77,7 @@ class API extends Handler {
$this->wrap(self::STATUS_OK, array("session_id" => session_id(), $this->wrap(self::STATUS_OK, array("session_id" => session_id(),
"api_level" => self::API_LEVEL)); "api_level" => self::API_LEVEL));
} else { // else we are not logged in } else { // else we are not logged in
user_error("Failed login attempt for $login from {$_SERVER['REMOTE_ADDR']}", E_USER_WARNING);
$this->wrap(self::STATUS_ERR, array("error" => "LOGIN_ERROR")); $this->wrap(self::STATUS_ERR, array("error" => "LOGIN_ERROR"));
} }
} else { } else {
@ -199,9 +200,13 @@ class API extends Handler {
$include_nested = sql_bool_to_bool($_REQUEST["include_nested"]); $include_nested = sql_bool_to_bool($_REQUEST["include_nested"]);
$sanitize_content = !isset($_REQUEST["sanitize"]) || $sanitize_content = !isset($_REQUEST["sanitize"]) ||
sql_bool_to_bool($_REQUEST["sanitize"]); sql_bool_to_bool($_REQUEST["sanitize"]);
$force_update = sql_bool_to_bool($_REQUEST["force_update"]);
$override_order = false; $override_order = false;
switch ($_REQUEST["order_by"]) { switch ($_REQUEST["order_by"]) {
case "title":
$override_order = "ttrss_entries.title";
break;
case "date_reverse": case "date_reverse":
$override_order = "score DESC, date_entered, updated"; $override_order = "score DESC, date_entered, updated";
break; break;
@ -218,7 +223,7 @@ class API extends Handler {
$headlines = $this->api_get_headlines($feed_id, $limit, $offset, $headlines = $this->api_get_headlines($feed_id, $limit, $offset,
$filter, $is_cat, $show_excerpt, $show_content, $view_mode, $override_order, $filter, $is_cat, $show_excerpt, $show_content, $view_mode, $override_order,
$include_attachments, $since_id, $search, $search_mode, $include_attachments, $since_id, $search, $search_mode,
$include_nested, $sanitize_content); $include_nested, $sanitize_content, $force_update);
$this->wrap(self::STATUS_OK, $headlines); $this->wrap(self::STATUS_OK, $headlines);
} else { } else {
@ -310,7 +315,7 @@ class API extends Handler {
if ($article_id) { if ($article_id) {
$query = "SELECT id,title,link,content,feed_id,comments,int_id, $query = "SELECT id,title,link,content,feed_id,comments,int_id,
marked,unread,published,score, marked,unread,published,score,note,lang,
".SUBSTRING_FOR_DATE."(updated,1,16) as updated, ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
author,(SELECT title FROM ttrss_feeds WHERE id = feed_id) AS feed_title author,(SELECT title FROM ttrss_feeds WHERE id = feed_id) AS feed_title
FROM ttrss_entries,ttrss_user_entries FROM ttrss_entries,ttrss_user_entries
@ -342,7 +347,9 @@ class API extends Handler {
"feed_id" => $line["feed_id"], "feed_id" => $line["feed_id"],
"attachments" => $attachments, "attachments" => $attachments,
"score" => (int)$line["score"], "score" => (int)$line["score"],
"feed_title" => $line["feed_title"] "feed_title" => $line["feed_title"],
"note" => $line["note"],
"lang" => $line["lang"]
); );
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_API) as $p) { foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_API) as $p) {
@ -423,7 +430,7 @@ class API extends Handler {
$checked = false; $checked = false;
foreach ($article_labels as $al) { foreach ($article_labels as $al) {
if ($al[0] == $line['id']) { if (feed_to_label_id($al[0]) == $line['id']) {
$checked = true; $checked = true;
break; break;
} }
@ -447,7 +454,7 @@ class API extends Handler {
$assign = (bool) $this->dbh->escape_string($_REQUEST['assign']) == "true"; $assign = (bool) $this->dbh->escape_string($_REQUEST['assign']) == "true";
$label = $this->dbh->escape_string(label_find_caption( $label = $this->dbh->escape_string(label_find_caption(
$label_id, $_SESSION["uid"])); feed_to_label_id($label_id), $_SESSION["uid"]));
$num_updated = 0; $num_updated = 0;
@ -511,7 +518,7 @@ class API extends Handler {
if ($unread || !$unread_only) { if ($unread || !$unread_only) {
$row = array( $row = array(
"id" => $cv["id"], "id" => (int) $cv["id"],
"title" => $cv["description"], "title" => $cv["description"],
"unread" => $cv["counter"], "unread" => $cv["counter"],
"cat_id" => -2, "cat_id" => -2,
@ -557,7 +564,7 @@ class API extends Handler {
if ($unread || !$unread_only) { if ($unread || !$unread_only) {
$row = array( $row = array(
"id" => $line["id"], "id" => (int) $line["id"],
"title" => $line["title"], "title" => $line["title"],
"unread" => $unread, "unread" => $unread,
"is_cat" => true, "is_cat" => true,
@ -626,7 +633,28 @@ class API extends Handler {
$filter, $is_cat, $show_excerpt, $show_content, $view_mode, $order, $filter, $is_cat, $show_excerpt, $show_content, $view_mode, $order,
$include_attachments, $since_id, $include_attachments, $since_id,
$search = "", $search_mode = "", $search = "", $search_mode = "",
$include_nested = false, $sanitize_content = true) { $include_nested = false, $sanitize_content = true, $force_update = false) {
if ($force_update && $feed_id > 0 && is_numeric($feed_id)) {
// Update the feed if required with some basic flood control
$result = db_query(
"SELECT cache_images,".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated
FROM ttrss_feeds WHERE id = '$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 (!$cache_images && time() - $last_updated > 120) {
include "rssfuncs.php";
update_rss_feed($feed_id, true, true);
} else {
db_query("UPDATE ttrss_feeds SET last_updated = '1970-01-01', last_update_started = '1970-01-01'
WHERE id = '$feed_id'");
}
}
}
$qfh_ret = queryFeedHeadlines($feed_id, $limit, $qfh_ret = queryFeedHeadlines($feed_id, $limit,
$view_mode, $is_cat, $search, $search_mode, $view_mode, $is_cat, $search, $search_mode,
@ -638,7 +666,7 @@ class API extends Handler {
$headlines = array(); $headlines = array();
while ($line = db_fetch_assoc($result)) { while ($line = db_fetch_assoc($result)) {
$line["content_preview"] = truncate_string(strip_tags($line["content_preview"]), 100); $line["content_preview"] = truncate_string(strip_tags($line["content"]), 100);
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) { foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
$line = $p->hook_query_headlines($line, 100, true); $line = $p->hook_query_headlines($line, 100, true);
} }
@ -700,6 +728,8 @@ class API extends Handler {
$headline_row["author"] = $line["author"]; $headline_row["author"] = $line["author"];
$headline_row["score"] = (int)$line["score"]; $headline_row["score"] = (int)$line["score"];
$headline_row["note"] = $line["note"];
$headline_row["lang"] = $line["lang"];
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_API) as $p) { foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_API) as $p) {
$headline_row = $p->hook_render_article_api(array("headline" => $headline_row)); $headline_row = $p->hook_render_article_api(array("headline" => $headline_row));

View file

@ -30,7 +30,6 @@ class Article extends Handler_Protected {
$id = $this->dbh->escape_string($_REQUEST["id"]); $id = $this->dbh->escape_string($_REQUEST["id"]);
$cids = explode(",", $this->dbh->escape_string($_REQUEST["cids"])); $cids = explode(",", $this->dbh->escape_string($_REQUEST["cids"]));
$mode = $this->dbh->escape_string($_REQUEST["mode"]); $mode = $this->dbh->escape_string($_REQUEST["mode"]);
$omode = $this->dbh->escape_string($_REQUEST["omode"]);
// in prefetch mode we only output requested cids, main article // in prefetch mode we only output requested cids, main article
// just gets marked as read (it already exists in client cache) // just gets marked as read (it already exists in client cache)
@ -108,7 +107,7 @@ class Article extends Handler_Protected {
// only check for our user data here, others might have shared this with different content etc // 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 $result = db_query("SELECT id FROM ttrss_entries, ttrss_user_entries WHERE
link = '$url' AND ref_id = id AND owner_uid = '$owner_uid' LIMIT 1"); guid = '$guid' AND ref_id = id AND owner_uid = '$owner_uid' LIMIT 1");
if (db_num_rows($result) != 0) { if (db_num_rows($result) != 0) {
$ref_id = db_fetch_result($result, 0, "id"); $ref_id = db_fetch_result($result, 0, "id");

View file

@ -16,7 +16,6 @@ class Dlg extends Handler_Protected {
print __("If you have imported labels and/or filters, you might need to reload preferences to see your new data.") . "</p>"; print __("If you have imported labels and/or filters, you might need to reload preferences to see your new data.") . "</p>";
print "<div class=\"prefFeedOPMLHolder\">"; print "<div class=\"prefFeedOPMLHolder\">";
$owner_uid = $_SESSION["uid"];
$this->dbh->query("BEGIN"); $this->dbh->query("BEGIN");
@ -176,7 +175,7 @@ class Dlg extends Handler_Protected {
while ($row = $this->dbh->fetch_assoc($result)) { while ($row = $this->dbh->fetch_assoc($result)) {
$tmp = htmlspecialchars($row["tag_name"]); $tmp = htmlspecialchars($row["tag_name"]);
print "<option value=\"" . str_replace(" ", "%20", $tmp) . "\">$tmp</option>"; print "<option value=\"$tmp\">$tmp</option>";
} }
print "</select>"; print "</select>";

View file

@ -4,5 +4,7 @@ class FeedEnclosure {
public $type; public $type;
public $length; public $length;
public $title; public $title;
public $height;
public $width;
} }
?> ?>

View file

@ -43,9 +43,9 @@ class FeedItem_Atom extends FeedItem_Common {
$base = $this->xpath->evaluate("string(ancestor-or-self::*[@xml:base][1]/@xml:base)", $link); $base = $this->xpath->evaluate("string(ancestor-or-self::*[@xml:base][1]/@xml:base)", $link);
if ($base) if ($base)
return rewrite_relative_url($base, $link->getAttribute("href")); return rewrite_relative_url($base, trim($link->getAttribute("href")));
else else
return $link->getAttribute("href"); return trim($link->getAttribute("href"));
} }
} }
@ -55,7 +55,7 @@ class FeedItem_Atom extends FeedItem_Common {
$title = $this->elem->getElementsByTagName("title")->item(0); $title = $this->elem->getElementsByTagName("title")->item(0);
if ($title) { if ($title) {
return $title->nodeValue; return trim($title->nodeValue);
} }
} }
@ -106,13 +106,13 @@ class FeedItem_Atom extends FeedItem_Common {
foreach ($categories as $cat) { foreach ($categories as $cat) {
if ($cat->hasAttribute("term")) if ($cat->hasAttribute("term"))
array_push($cats, $cat->getAttribute("term")); array_push($cats, trim($cat->getAttribute("term")));
} }
$categories = $this->xpath->query("dc:subject", $this->elem); $categories = $this->xpath->query("dc:subject", $this->elem);
foreach ($categories as $cat) { foreach ($categories as $cat) {
array_push($cats, $cat->nodeValue); array_push($cats, trim($cat->nodeValue));
} }
return $cats; return $cats;
@ -137,7 +137,7 @@ class FeedItem_Atom extends FeedItem_Common {
} }
} }
$enclosures = $this->xpath->query("media:content | media:group/media:content", $this->elem); $enclosures = $this->xpath->query("media:content", $this->elem);
foreach ($enclosures as $enclosure) { foreach ($enclosures as $enclosure) {
$enc = new FeedEnclosure(); $enc = new FeedEnclosure();
@ -145,6 +145,8 @@ class FeedItem_Atom extends FeedItem_Common {
$enc->type = $enclosure->getAttribute("type"); $enc->type = $enclosure->getAttribute("type");
$enc->link = $enclosure->getAttribute("url"); $enc->link = $enclosure->getAttribute("url");
$enc->length = $enclosure->getAttribute("length"); $enc->length = $enclosure->getAttribute("length");
$enc->height = $enclosure->getAttribute("height");
$enc->width = $enclosure->getAttribute("width");
$desc = $this->xpath->query("media:description", $enclosure)->item(0); $desc = $this->xpath->query("media:description", $enclosure)->item(0);
if ($desc) $enc->title = strip_tags($desc->nodeValue); if ($desc) $enc->title = strip_tags($desc->nodeValue);
@ -152,6 +154,46 @@ class FeedItem_Atom extends FeedItem_Common {
array_push($encs, $enc); array_push($encs, $enc);
} }
$enclosures = $this->xpath->query("media:group", $this->elem);
foreach ($enclosures as $enclosure) {
$enc = new FeedEnclosure();
$content = $this->xpath->query("media:content", $enclosure)->item(0);
if ($content) {
$enc->type = $content->getAttribute("type");
$enc->link = $content->getAttribute("url");
$enc->length = $content->getAttribute("length");
$enc->height = $content->getAttribute("height");
$enc->width = $content->getAttribute("width");
$desc = $this->xpath->query("media:description", $content)->item(0);
if ($desc) {
$enc->title = strip_tags($desc->nodeValue);
} else {
$desc = $this->xpath->query("media:description", $enclosure)->item(0);
if ($desc) $enc->title = strip_tags($desc->nodeValue);
}
array_push($encs, $enc);
}
}
$enclosures = $this->xpath->query("media:thumbnail", $this->elem);
foreach ($enclosures as $enclosure) {
$enc = new FeedEnclosure();
$enc->type = "image/generic";
$enc->link = $enclosure->getAttribute("url");
$enc->height = $enclosure->getAttribute("height");
$enc->width = $enclosure->getAttribute("width");
array_push($encs, $enc);
}
return $encs; return $encs;
} }

View file

@ -44,13 +44,26 @@ abstract class FeedItem_Common extends FeedItem {
} }
} }
// todo
function get_comments_url() { function get_comments_url() {
//RSS only. Use a query here to avoid namespace clashes (e.g. with slash).
//might give a wrong result if a default namespace was declared (possible with XPath 2.0)
$com_url = $this->xpath->query("comments", $this->elem)->item(0);
if($com_url)
return $com_url->nodeValue;
//Atom Threading Extension (RFC 4685) stuff. Could be used in RSS feeds, so it's in common.
//'text/html' for type is too restrictive?
$com_url = $this->xpath->query("atom:link[@rel='replies' and contains(@type,'text/html')]/@href", $this->elem)->item(0);
if($com_url)
return $com_url->nodeValue;
} }
function get_comments_count() { function get_comments_count() {
$comments = $this->xpath->query("slash:comments", $this->elem)->item(0); //also query for ATE stuff here
$query = "slash:comments|thread:total|atom:link[@rel='replies']/@thread:count";
$comments = $this->xpath->query($query, $this->elem)->item(0);
if ($comments) { if ($comments) {
return $comments->nodeValue; return $comments->nodeValue;

View file

@ -33,20 +33,20 @@ class FeedItem_RSS extends FeedItem_Common {
|| $link->getAttribute("rel") == "alternate" || $link->getAttribute("rel") == "alternate"
|| $link->getAttribute("rel") == "standout")) { || $link->getAttribute("rel") == "standout")) {
return $link->getAttribute("href"); return trim($link->getAttribute("href"));
} }
} }
$link = $this->elem->getElementsByTagName("guid")->item(0); $link = $this->elem->getElementsByTagName("guid")->item(0);
if ($link && $link->hasAttributes() && $link->getAttribute("isPermaLink") == "true") { if ($link && $link->hasAttributes() && $link->getAttribute("isPermaLink") == "true") {
return $link->nodeValue; return trim($link->nodeValue);
} }
$link = $this->elem->getElementsByTagName("link")->item(0); $link = $this->elem->getElementsByTagName("link")->item(0);
if ($link) { if ($link) {
return $link->nodeValue; return trim($link->nodeValue);
} }
} }
@ -54,21 +54,26 @@ class FeedItem_RSS extends FeedItem_Common {
$title = $this->elem->getElementsByTagName("title")->item(0); $title = $this->elem->getElementsByTagName("title")->item(0);
if ($title) { if ($title) {
return $title->nodeValue; return trim($title->nodeValue);
} }
} }
function get_content() { function get_content() {
$content = $this->xpath->query("content:encoded", $this->elem)->item(0); $contentA = $this->xpath->query("content:encoded", $this->elem)->item(0);
$contentB = $this->elem->getElementsByTagName("description")->item(0);
if ($content) { if ($contentA && !$contentB) {
return $content->nodeValue; return $contentA->nodeValue;
} }
$content = $this->elem->getElementsByTagName("description")->item(0);
if ($content) { if ($contentB && !$contentA) {
return $content->nodeValue; return $contentB->nodeValue;
}
if ($contentA && $contentB) {
return mb_strlen($contentA->nodeValue) > mb_strlen($contentB->nodeValue) ?
$contentA->nodeValue : $contentB->nodeValue;
} }
} }
@ -85,13 +90,13 @@ class FeedItem_RSS extends FeedItem_Common {
$cats = array(); $cats = array();
foreach ($categories as $cat) { foreach ($categories as $cat) {
array_push($cats, $cat->nodeValue); array_push($cats, trim($cat->nodeValue));
} }
$categories = $this->xpath->query("dc:subject", $this->elem); $categories = $this->xpath->query("dc:subject", $this->elem);
foreach ($categories as $cat) { foreach ($categories as $cat) {
array_push($cats, $cat->nodeValue); array_push($cats, trim($cat->nodeValue));
} }
return $cats; return $cats;
@ -108,11 +113,13 @@ class FeedItem_RSS extends FeedItem_Common {
$enc->type = $enclosure->getAttribute("type"); $enc->type = $enclosure->getAttribute("type");
$enc->link = $enclosure->getAttribute("url"); $enc->link = $enclosure->getAttribute("url");
$enc->length = $enclosure->getAttribute("length"); $enc->length = $enclosure->getAttribute("length");
$enc->height = $enclosure->getAttribute("height");
$enc->width = $enclosure->getAttribute("width");
array_push($encs, $enc); array_push($encs, $enc);
} }
$enclosures = $this->xpath->query("media:content | media:group/media:content", $this->elem); $enclosures = $this->xpath->query("media:content", $this->elem);
foreach ($enclosures as $enclosure) { foreach ($enclosures as $enclosure) {
$enc = new FeedEnclosure(); $enc = new FeedEnclosure();
@ -120,6 +127,8 @@ class FeedItem_RSS extends FeedItem_Common {
$enc->type = $enclosure->getAttribute("type"); $enc->type = $enclosure->getAttribute("type");
$enc->link = $enclosure->getAttribute("url"); $enc->link = $enclosure->getAttribute("url");
$enc->length = $enclosure->getAttribute("length"); $enc->length = $enclosure->getAttribute("length");
$enc->height = $enclosure->getAttribute("height");
$enc->width = $enclosure->getAttribute("width");
$desc = $this->xpath->query("media:description", $enclosure)->item(0); $desc = $this->xpath->query("media:description", $enclosure)->item(0);
if ($desc) $enc->title = strip_tags($desc->nodeValue); if ($desc) $enc->title = strip_tags($desc->nodeValue);
@ -127,6 +136,46 @@ class FeedItem_RSS extends FeedItem_Common {
array_push($encs, $enc); array_push($encs, $enc);
} }
$enclosures = $this->xpath->query("media:group", $this->elem);
foreach ($enclosures as $enclosure) {
$enc = new FeedEnclosure();
$content = $this->xpath->query("media:content", $enclosure)->item(0);
if ($content) {
$enc->type = $content->getAttribute("type");
$enc->link = $content->getAttribute("url");
$enc->length = $content->getAttribute("length");
$enc->height = $content->getAttribute("height");
$enc->width = $content->getAttribute("width");
$desc = $this->xpath->query("media:description", $content)->item(0);
if ($desc) {
$enc->title = strip_tags($desc->nodeValue);
} else {
$desc = $this->xpath->query("media:description", $enclosure)->item(0);
if ($desc) $enc->title = strip_tags($desc->nodeValue);
}
array_push($encs, $enc);
}
}
$enclosures = $this->xpath->query("media:thumbnail", $this->elem);
foreach ($enclosures as $enclosure) {
$enc = new FeedEnclosure();
$enc->type = "image/generic";
$enc->link = $enclosure->getAttribute("url");
$enc->height = $enclosure->getAttribute("height");
$enc->width = $enclosure->getAttribute("width");
array_push($encs, $enc);
}
return $encs; return $encs;
} }

View file

@ -2,6 +2,7 @@
class FeedParser { class FeedParser {
private $doc; private $doc;
private $error; private $error;
private $libxml_errors = array();
private $items; private $items;
private $link; private $link;
private $title; private $title;
@ -12,6 +13,16 @@ class FeedParser {
const FEED_RSS = 1; const FEED_RSS = 1;
const FEED_ATOM = 2; const FEED_ATOM = 2;
function normalize_encoding($data) {
if (preg_match('/^(<\?xml[\t\n\r ].*?encoding[\t\n\r ]*=[\t\n\r ]*["\'])(.+?)(["\'].*?\?>)/s', $data, $matches) === 1) {
$data = mb_convert_encoding($data, 'UTF-8', $matches[2]);
$data = preg_replace('/^<\?xml[\t\n\r ].*?\?>/s', $matches[1] . "UTF-8" . $matches[3] , $data);
}
return $data;
}
function __construct($data) { function __construct($data) {
libxml_use_internal_errors(true); libxml_use_internal_errors(true);
libxml_clear_errors(); libxml_clear_errors();
@ -23,32 +34,8 @@ class FeedParser {
$error = libxml_get_last_error(); $error = libxml_get_last_error();
// libxml compiled without iconv? // libxml compiled without iconv?
if ($error && ($error->code == 32 || $error->code == 9)) { if ($error && $error->code == 32) {
if (preg_match('/^(<\?xml[\t\n\r ].*?encoding=["\'])(.+?)(["\'].*?\?>)/s', $data, $matches) === 1) { $data = $this->normalize_encoding($data);
$enc = $matches[2];
$data = mb_convert_encoding($data, 'UTF-8', $enc);
$data = preg_replace('/^<\?xml[\t\n\r ].*?\?>/s', $matches[1] . "UTF-8" . $matches[3] , $data);
// apparently not all UTF-8 characters are valid for XML
$data = preg_replace('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $data);
if ($data) {
libxml_clear_errors();
$this->doc = new DOMDocument();
$this->doc->loadXML($data);
$error = libxml_get_last_error();
}
}
}
// some terrible invalid unicode entity?
if ($error && $error->code == 9) {
$data = mb_convert_encoding($data, 'UTF-8', 'UTF-8');
if ($data) { if ($data) {
libxml_clear_errors(); libxml_clear_errors();
@ -60,7 +47,41 @@ class FeedParser {
} }
} }
$this->error = $this->format_error($error); // some terrible invalid unicode entity?
if ($error) {
foreach (libxml_get_errors() as $err) {
if ($err->code == 9) {
// if the source feed is not in utf8, next conversion will fail
$data = $this->normalize_encoding($data);
// remove dangling bytes
$data = mb_convert_encoding($data, 'UTF-8', 'UTF-8');
// apparently not all UTF-8 characters are valid for XML
$data = preg_replace('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $data);
if ($data) {
libxml_clear_errors();
$this->doc = new DOMDocument();
$this->doc->loadXML($data);
$error = libxml_get_last_error();
}
break;
}
}
}
if ($error) {
foreach (libxml_get_errors() as $error) {
if ($error->level == LIBXML_ERR_FATAL) {
if(!isset($this->error)) //currently only the first error is reported
$this->error = $this->format_error($error);
$this->libxml_errors [] = $this->format_error($error);
}
}
}
libxml_clear_errors(); libxml_clear_errors();
$this->items = array(); $this->items = array();
@ -76,12 +97,13 @@ class FeedParser {
$xpath->registerNamespace('slash', 'http://purl.org/rss/1.0/modules/slash/'); $xpath->registerNamespace('slash', 'http://purl.org/rss/1.0/modules/slash/');
$xpath->registerNamespace('dc', 'http://purl.org/dc/elements/1.1/'); $xpath->registerNamespace('dc', 'http://purl.org/dc/elements/1.1/');
$xpath->registerNamespace('content', 'http://purl.org/rss/1.0/modules/content/'); $xpath->registerNamespace('content', 'http://purl.org/rss/1.0/modules/content/');
$xpath->registerNamespace('thread', 'http://purl.org/syndication/thread/1.0');
$this->xpath = $xpath; $this->xpath = $xpath;
$root = $xpath->query("(//atom03:feed|//atom:feed|//channel|//rdf:rdf|//rdf:RDF)"); $root = $xpath->query("(//atom03:feed|//atom:feed|//channel|//rdf:rdf|//rdf:RDF)");
if ($root) { if ($root && $root->length > 0) {
$root = $root->item(0); $root = $root->item(0);
if ($root) { if ($root) {
@ -183,6 +205,10 @@ class FeedParser {
break; break;
} }
if ($this->title) $this->title = trim($this->title);
if ($this->link) $this->link = trim($this->link);
} else { } else {
if( !isset($this->error) ){ if( !isset($this->error) ){
$this->error = "Unknown/unsupported feed type"; $this->error = "Unknown/unsupported feed type";
@ -205,6 +231,10 @@ class FeedParser {
return $this->error; return $this->error;
} }
function errors() {
return $this->libxml_errors;
}
function get_link() { function get_link() {
return $this->link; return $this->link;
} }
@ -226,7 +256,7 @@ class FeedParser {
foreach ($links as $link) { foreach ($links as $link) {
if (!$rel || $link->hasAttribute('rel') && $link->getAttribute('rel') == $rel) { if (!$rel || $link->hasAttribute('rel') && $link->getAttribute('rel') == $rel) {
array_push($rv, $link->getAttribute('href')); array_push($rv, trim($link->getAttribute('href')));
} }
} }
break; break;
@ -235,7 +265,7 @@ class FeedParser {
foreach ($links as $link) { foreach ($links as $link) {
if (!$rel || $link->hasAttribute('rel') && $link->getAttribute('rel') == $rel) { if (!$rel || $link->hasAttribute('rel') && $link->getAttribute('rel') == $rel) {
array_push($rv, $link->getAttribute('href')); array_push($rv, trim($link->getAttribute('href')));
} }
} }
break; break;

View file

@ -13,12 +13,6 @@ class Feeds extends Handler_Protected {
$feed_id, $is_cat, $search, $feed_id, $is_cat, $search,
$search_mode, $view_mode, $error, $feed_last_updated) { $search_mode, $view_mode, $error, $feed_last_updated) {
$page_prev_link = "viewFeedGoPage(-1)";
$page_next_link = "viewFeedGoPage(1)";
$page_first_link = "viewFeedGoPage(0)";
$catchup_page_link = "catchupPage()";
$catchup_feed_link = "catchupCurrentFeed()";
$catchup_sel_link = "catchupSelection()"; $catchup_sel_link = "catchupSelection()";
$archive_sel_link = "archiveSelection()"; $archive_sel_link = "archiveSelection()";
@ -43,6 +37,8 @@ class Feeds extends Handler_Protected {
$search_q = ""; $search_q = "";
} }
$reply .= "<span class=\"holder\">";
$rss_link = htmlspecialchars(get_self_url_prefix() . $rss_link = htmlspecialchars(get_self_url_prefix() .
"/public.php?op=rss&id=$feed_id$cat_q$search_q"); "/public.php?op=rss&id=$feed_id$cat_q$search_q");
@ -50,8 +46,14 @@ class Feeds extends Handler_Protected {
$error_class = $error ? "error" : ""; $error_class = $error ? "error" : "";
$reply .= "<span class='r'>"; $reply .= "<span class='r'>
$reply .= "<span id='selected_prompt'></span>"; <a href=\"#\"
title=\"".__("View as RSS feed")."\"
onclick=\"displayDlg('".__("View as RSS")."','generatedFeed', '$feed_id:$is_cat:$rss_link')\">
<img class=\"noborder\" src=\"images/pub_set.png\"></a>";
# $reply .= "<span>";
$reply .= "<span id='feed_title' class='$error_class'>"; $reply .= "<span id='feed_title' class='$error_class'>";
if ($feed_site_url) { if ($feed_site_url) {
@ -60,11 +62,11 @@ class Feeds extends Handler_Protected {
$target = "target=\"_blank\""; $target = "target=\"_blank\"";
$reply .= "<a title=\"$last_updated\" $target href=\"$feed_site_url\">". $reply .= "<a title=\"$last_updated\" $target href=\"$feed_site_url\">".
truncate_string($feed_title,30)."</a>"; truncate_string($feed_title, 30)."</a>";
if ($error) { if ($error) {
$error = htmlspecialchars($error); $error = htmlspecialchars($error);
$reply .= "&nbsp;<img title=\"$error\" src='images/error.png' alt='error' class=\"noborder\" style=\"vertical-align : middle\">"; $reply .= "&nbsp;<img title=\"$error\" src='images/error.png' alt='error' class=\"noborder\">";
} }
} else { } else {
@ -73,17 +75,16 @@ class Feeds extends Handler_Protected {
$reply .= "</span>"; $reply .= "</span>";
$reply .= "
<a href=\"#\"
title=\"".__("View as RSS feed")."\"
onclick=\"displayDlg('".__("View as RSS")."','generatedFeed', '$feed_id:$is_cat:$rss_link')\">
<img class=\"noborder\" style=\"vertical-align : middle\" src=\"images/pub_set.png\"></a>";
$reply .= "</span>"; $reply .= "</span>";
# $reply .= "</span>";
// left part // left part
$reply .= __('Select:')." $reply .= "<span class=\"main\">";
$reply .= "<span id='selected_prompt'></span>";
$reply .= "
<a href=\"#\" onclick=\"$sel_all_link\">".__('All')."</a>, <a href=\"#\" onclick=\"$sel_all_link\">".__('All')."</a>,
<a href=\"#\" onclick=\"$sel_unread_link\">".__('Unread')."</a>, <a href=\"#\" onclick=\"$sel_unread_link\">".__('Unread')."</a>,
<a href=\"#\" onclick=\"$sel_inv_link\">".__('Invert')."</a>, <a href=\"#\" onclick=\"$sel_inv_link\">".__('Invert')."</a>,
@ -132,14 +133,14 @@ class Feeds extends Handler_Protected {
$reply .= "</select>"; $reply .= "</select>";
//$reply .= "</div>";
//$reply .= "</h2"; //$reply .= "</h2";
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HEADLINE_TOOLBAR_BUTTON) as $p) { foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HEADLINE_TOOLBAR_BUTTON) as $p) {
echo $p->hook_headline_toolbar_button($feed_id, $is_cat); $reply .= $p->hook_headline_toolbar_button($feed_id, $is_cat);
} }
$reply .= "</span></span>";
return $reply; return $reply;
} }
@ -148,7 +149,7 @@ class Feeds extends Handler_Protected {
$override_order = false, $include_children = false) { $override_order = false, $include_children = false) {
if (isset($_REQUEST["DevForceUpdate"])) if (isset($_REQUEST["DevForceUpdate"]))
header("Content-Type: text/plain"); header("Content-Type: text/plain; charset=utf-8");
$disable_cache = false; $disable_cache = false;
@ -247,6 +248,8 @@ class Feeds extends Handler_Protected {
false, 0, $include_children); false, 0, $include_children);
} }
$vfeed_group_enabled = get_pref("VFEED_GROUP_BY_FEED") && $feed != -6;
if ($_REQUEST["debug"]) $timing_info = print_checkpoint("H1", $timing_info); if ($_REQUEST["debug"]) $timing_info = print_checkpoint("H1", $timing_info);
$result = $qfh_ret[0]; $result = $qfh_ret[0];
@ -278,6 +281,12 @@ class Feeds extends Handler_Protected {
} }
} */ } */
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);
}
}
if ($this->dbh->num_rows($result) > 0) { if ($this->dbh->num_rows($result) > 0) {
$lnum = $offset; $lnum = $offset;
@ -285,14 +294,12 @@ class Feeds extends Handler_Protected {
$num_unread = 0; $num_unread = 0;
$cur_feed_title = ''; $cur_feed_title = '';
$fresh_intl = get_pref("FRESH_ARTICLE_MAX_AGE") * 60 * 60;
if ($_REQUEST["debug"]) $timing_info = print_checkpoint("PS", $timing_info); if ($_REQUEST["debug"]) $timing_info = print_checkpoint("PS", $timing_info);
$expand_cdm = get_pref('CDM_EXPANDED'); $expand_cdm = get_pref('CDM_EXPANDED');
while ($line = $this->dbh->fetch_assoc($result)) { while ($line = $this->dbh->fetch_assoc($result)) {
$line["content_preview"] = "&mdash; " . truncate_string(strip_tags($line["content_preview"]), 250); $line["content_preview"] = "&mdash; " . truncate_string(strip_tags($line["content"]), 250);
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) { foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
$line = $p->hook_query_headlines($line, 250, false); $line = $p->hook_query_headlines($line, 250, false);
@ -422,7 +429,7 @@ class Feeds extends Handler_Protected {
if (!get_pref('COMBINED_DISPLAY_MODE')) { if (!get_pref('COMBINED_DISPLAY_MODE')) {
if (get_pref('VFEED_GROUP_BY_FEED')) { if ($vfeed_group_enabled) {
if ($feed_id != $vgroup_last_feed && $line["feed_title"]) { if ($feed_id != $vgroup_last_feed && $line["feed_title"]) {
$cur_feed_title = $line["feed_title"]; $cur_feed_title = $line["feed_title"];
@ -430,12 +437,12 @@ class Feeds extends Handler_Protected {
$cur_feed_title = htmlspecialchars($cur_feed_title); $cur_feed_title = htmlspecialchars($cur_feed_title);
$vf_catchup_link = "(<a class='catchup' onclick='catchupFeedInGroup($feed_id);' href='#'>".__('Mark as read')."</a>)"; $vf_catchup_link = "<a class='catchup' onclick='catchupFeedInGroup($feed_id);' href='#'>".__('mark feed as read')."</a>";
$reply['content'] .= "<div class='cdmFeedTitle'>". $reply['content'] .= "<div id='FTITLE-$feed_id' class='cdmFeedTitle'>".
"<div style=\"float : right\">$feed_icon_img</div>". "<div style='float : right'>$feed_icon_img</div>".
"<a class='title' href=\"#\" onclick=\"viewfeed($feed_id)\">". "<a class='title' href=\"#\" onclick=\"viewfeed($feed_id)\">". $line["feed_title"]."</a>
$line["feed_title"]."</a> $vf_catchup_link</div>"; $vf_catchup_link</div>";
} }
} }
@ -443,7 +450,7 @@ class Feeds extends Handler_Protected {
$mouseover_attrs = "onmouseover='postMouseIn(event, $id)' $mouseover_attrs = "onmouseover='postMouseIn(event, $id)'
onmouseout='postMouseOut($id)'"; onmouseout='postMouseOut($id)'";
$reply['content'] .= "<div class='hl $class' id='RROW-$id' $mouseover_attrs>"; $reply['content'] .= "<div class='hl $class' orig-feed-id='$feed_id' id='RROW-$id' $mouseover_attrs>";
$reply['content'] .= "<div class='hlLeft'>"; $reply['content'] .= "<div class='hlLeft'>";
@ -473,17 +480,18 @@ class Feeds extends Handler_Protected {
$reply['content'] .= "</div>"; $reply['content'] .= "</div>";
$reply['content'] .= "<span class=\"hlUpdated\">"; if (!$vfeed_group_enabled) {
if (!get_pref('VFEED_GROUP_BY_FEED')) {
if (@$line["feed_title"]) { if (@$line["feed_title"]) {
$rgba = @$rgba_cache[$feed_id]; $rgba = @$rgba_cache[$feed_id];
$reply['content'] .= "<a class=\"hlFeed\" style=\"background : rgba($rgba, 0.3)\" href=\"#\" onclick=\"viewfeed($feed_id)\">". $reply['content'] .= "<span class=\"hlFeed\"><a style=\"background : rgba($rgba, 0.3)\" href=\"#\" onclick=\"viewfeed($feed_id)\">".
truncate_string($line["feed_title"],30)."</a>"; truncate_string($line["feed_title"],30)."</a></span>";
} }
} }
$reply['content'] .= "<span class=\"hlUpdated\">";
$reply['content'] .= "<div title='$date_entered_fmt'>$updated_fmt</div> $reply['content'] .= "<div title='$date_entered_fmt'>$updated_fmt</div>
</span>"; </span>";
@ -491,12 +499,12 @@ class Feeds extends Handler_Protected {
$reply['content'] .= $score_pic; $reply['content'] .= $score_pic;
if ($line["feed_title"] && !get_pref('VFEED_GROUP_BY_FEED')) { if ($line["feed_title"] && !$vfeed_group_enabled) {
$reply['content'] .= "<span onclick=\"viewfeed($feed_id)\" $reply['content'] .= "<span onclick=\"viewfeed($feed_id)\"
style=\"cursor : pointer\" style=\"cursor : pointer\"
title=\"".htmlspecialchars($line['feed_title'])."\"> title=\"".htmlspecialchars($line['feed_title'])."\">
$feed_icon_img<span>"; $feed_icon_img</span>";
} }
$reply['content'] .= "</div>"; $reply['content'] .= "</div>";
@ -516,7 +524,7 @@ class Feeds extends Handler_Protected {
$line = $p->hook_render_article_cdm($line); $line = $p->hook_render_article_cdm($line);
} }
if (get_pref('VFEED_GROUP_BY_FEED') && $line["feed_title"]) { if ($vfeed_group_enabled && $line["feed_title"]) {
if ($feed_id != $vgroup_last_feed) { if ($feed_id != $vgroup_last_feed) {
$cur_feed_title = $line["feed_title"]; $cur_feed_title = $line["feed_title"];
@ -524,7 +532,7 @@ class Feeds extends Handler_Protected {
$cur_feed_title = htmlspecialchars($cur_feed_title); $cur_feed_title = htmlspecialchars($cur_feed_title);
$vf_catchup_link = "(<a class='catchup' onclick='javascript:catchupFeedInGroup($feed_id);' href='#'>".__('mark as read')."</a>)"; $vf_catchup_link = "<a class='catchup' onclick='catchupFeedInGroup($feed_id);' href='#'>".__('mark feed as read')."</a>";
$has_feed_icon = feed_has_icon($feed_id); $has_feed_icon = feed_has_icon($feed_id);
@ -534,7 +542,7 @@ class Feeds extends Handler_Protected {
//$feed_icon_img = "<img class=\"tinyFeedIcon\" src=\"images/blank_icon.gif\" alt=\"\">"; //$feed_icon_img = "<img class=\"tinyFeedIcon\" src=\"images/blank_icon.gif\" alt=\"\">";
} }
$reply['content'] .= "<div class='cdmFeedTitle'>". $reply['content'] .= "<div id='FTITLE-$feed_id' class='cdmFeedTitle'>".
"<div style=\"float : right\">$feed_icon_img</div>". "<div style=\"float : right\">$feed_icon_img</div>".
"<a href=\"#\" class='title' onclick=\"viewfeed($feed_id)\">". "<a href=\"#\" class='title' onclick=\"viewfeed($feed_id)\">".
$line["feed_title"]."</a> $vf_catchup_link</div>"; $line["feed_title"]."</a> $vf_catchup_link</div>";
@ -547,9 +555,9 @@ class Feeds extends Handler_Protected {
$expanded_class = $expand_cdm ? "expanded" : "expandable"; $expanded_class = $expand_cdm ? "expanded" : "expandable";
$reply['content'] .= "<div class=\"cdm $hlc_suffix $expanded_class $class\" $reply['content'] .= "<div class=\"cdm $hlc_suffix $expanded_class $class\"
id=\"RROW-$id\" $mouseover_attrs>"; id=\"RROW-$id\" orig-feed-id='$feed_id' $mouseover_attrs>";
$reply['content'] .= "<div class=\"cdmHeader\" style=\"$row_background\">"; $reply['content'] .= "<div class=\"cdmHeader\">";
$reply['content'] .= "<div style=\"vertical-align : middle\">"; $reply['content'] .= "<div style=\"vertical-align : middle\">";
$reply['content'] .= "<input dojoType=\"dijit.form.CheckBox\" $reply['content'] .= "<input dojoType=\"dijit.form.CheckBox\"
@ -592,7 +600,7 @@ class Feeds extends Handler_Protected {
$reply['content'] .= "</span>"; $reply['content'] .= "</span>";
if (!get_pref('VFEED_GROUP_BY_FEED')) { if (!$vfeed_group_enabled) {
if (@$line["feed_title"]) { if (@$line["feed_title"]) {
$rgba = @$rgba_cache[$feed_id]; $rgba = @$rgba_cache[$feed_id];
@ -725,7 +733,7 @@ class Feeds extends Handler_Protected {
$reply['content'] .= "</div>"; $reply['content'] .= "</div>";
$reply['content'] .= "</div>"; $reply['content'] .= "</div>";
$reply['content'] .= "</div><hr/>"; $reply['content'] .= "</div>";
$reply['content'] .= "</div>"; $reply['content'] .= "</div>";
@ -803,8 +811,6 @@ class Feeds extends Handler_Protected {
if ($_REQUEST["debug"]) $timing_info = print_checkpoint("0", $timing_info); if ($_REQUEST["debug"]) $timing_info = print_checkpoint("0", $timing_info);
$omode = $this->dbh->escape_string($_REQUEST["omode"]);
$feed = $this->dbh->escape_string($_REQUEST["feed"]); $feed = $this->dbh->escape_string($_REQUEST["feed"]);
$method = $this->dbh->escape_string($_REQUEST["m"]); $method = $this->dbh->escape_string($_REQUEST["m"]);
$view_mode = $this->dbh->escape_string($_REQUEST["view_mode"]); $view_mode = $this->dbh->escape_string($_REQUEST["view_mode"]);
@ -897,7 +903,7 @@ class Feeds extends Handler_Protected {
//$topmost_article_ids = $ret[0]; //$topmost_article_ids = $ret[0];
$headlines_count = $ret[1]; $headlines_count = $ret[1];
$returned_feed = $ret[2]; /* $returned_feed = $ret[2]; */
$disable_cache = $ret[3]; $disable_cache = $ret[3];
$vgroup_last_feed = $ret[4]; $vgroup_last_feed = $ret[4];
@ -978,6 +984,10 @@ class Feeds extends Handler_Protected {
print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"op\" value=\"rpc\">"; print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"op\" value=\"rpc\">";
print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"method\" value=\"addfeed\">"; print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"method\" value=\"addfeed\">";
print "<div id='fadd_multiple_notify' style='display : none'>";
print_notice("Provided URL is a HTML page referencing multiple feeds, please select required feed from the dropdown menu below.");
print "<p></div>";
print "<div class=\"dlgSec\">".__("Feed or site URL")."</div>"; print "<div class=\"dlgSec\">".__("Feed or site URL")."</div>";
print "<div class=\"dlgSecCont\">"; print "<div class=\"dlgSecCont\">";
@ -1073,20 +1083,18 @@ class Feeds extends Handler_Protected {
print " <select dojoType=\"dijit.form.Select\" name=\"limit\" onchange=\"dijit.byId('feedBrowserDlg').update()\">"; print " <select dojoType=\"dijit.form.Select\" name=\"limit\" onchange=\"dijit.byId('feedBrowserDlg').update()\">";
foreach (array(25, 50, 100, 200) as $l) { foreach (array(25, 50, 100, 200) as $l) {
$issel = ($l == $limit) ? "selected=\"1\"" : ""; //$issel = ($l == $limit) ? "selected=\"1\"" : "";
print "<option $issel value=\"$l\">$l</option>"; print "<option value=\"$l\">$l</option>";
} }
print "</select> "; print "</select> ";
print "</div>"; print "</div>";
$owner_uid = $_SESSION["uid"];
require_once "feedbrowser.php"; require_once "feedbrowser.php";
print "<ul class='browseFeedList' id='browseFeedList'>"; print "<ul class='browseFeedList' id='browseFeedList'>";
print make_feed_browser($search, 25); print make_feed_browser("", 25);
print "</ul>"; print "</ul>";
print "<div align='center'> print "<div align='center'>
@ -1145,7 +1153,7 @@ class Feeds extends Handler_Protected {
print "<div class=\"dlgButtons\">"; print "<div class=\"dlgButtons\">";
if (!SPHINX_ENABLED) { if (count(PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH)) == 0) {
print "<div style=\"float : left\"> print "<div style=\"float : left\">
<a class=\"visibleLink\" target=\"_blank\" href=\"http://tt-rss.org/wiki/SearchSyntax\">".__("Search syntax")."</a> <a class=\"visibleLink\" target=\"_blank\" href=\"http://tt-rss.org/wiki/SearchSyntax\">".__("Search syntax")."</a>
</div>"; </div>";

View file

@ -3,7 +3,7 @@ class Handler_Public extends Handler {
private function generate_syndicated_feed($owner_uid, $feed, $is_cat, private function generate_syndicated_feed($owner_uid, $feed, $is_cat,
$limit, $offset, $search, $search_mode, $limit, $offset, $search, $search_mode,
$view_mode = false, $format = 'atom', $order = false, $orig_guid = false) { $view_mode = false, $format = 'atom', $order = false, $orig_guid = false, $start_ts = false) {
require_once "lib/MiniTemplator.class.php"; require_once "lib/MiniTemplator.class.php";
@ -15,11 +15,15 @@ class Handler_Public extends Handler {
if (!$limit) $limit = 60; if (!$limit) $limit = 60;
$date_sort_field = "date_entered DESC, updated DESC"; $date_sort_field = "date_entered DESC, updated DESC";
$date_check_field = "date_entered";
if ($feed == -2) if ($feed == -2 && !$is_cat) {
$date_sort_field = "last_published DESC"; $date_sort_field = "last_published DESC";
else if ($feed == -1) $date_check_field = "last_published";
} else if ($feed == -1 && !$is_cat) {
$date_sort_field = "last_marked DESC"; $date_sort_field = "last_marked DESC";
$date_check_field = "last_marked";
}
switch ($order) { switch ($order) {
case "title": case "title":
@ -33,15 +37,18 @@ class Handler_Public extends Handler {
break; break;
} }
//function queryFeedHeadlines($feed, $limit, $view_mode, $cat_view, $search, $search_mode, $override_order = false, $offset = 0, $owner_uid = 0, $filter = false, $since_id = 0, $include_children = false, $ignore_vfeed_group = false, $override_strategy = false, $override_vfeed = false, $start_ts = false) {
$qfh_ret = queryFeedHeadlines($feed, $qfh_ret = queryFeedHeadlines($feed,
1, $view_mode, $is_cat, $search, $search_mode, 1, $view_mode, $is_cat, $search, $search_mode,
$date_sort_field, $offset, $owner_uid, $date_sort_field, $offset, $owner_uid,
false, 0, false, true); false, 0, true, true, false, false, $start_ts);
$result = $qfh_ret[0]; $result = $qfh_ret[0];
if ($this->dbh->num_rows($result) != 0) { if ($this->dbh->num_rows($result) != 0) {
$ts = strtotime($this->dbh->fetch_result($result, 0, "date_entered"));
$ts = strtotime($this->dbh->fetch_result($result, 0, $date_check_field));
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $ts) { strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $ts) {
@ -56,13 +63,13 @@ class Handler_Public extends Handler {
$qfh_ret = queryFeedHeadlines($feed, $qfh_ret = queryFeedHeadlines($feed,
$limit, $view_mode, $is_cat, $search, $search_mode, $limit, $view_mode, $is_cat, $search, $search_mode,
$date_sort_field, $offset, $owner_uid, $date_sort_field, $offset, $owner_uid,
false, 0, false, true); false, 0, true, true, false, false, $start_ts);
$result = $qfh_ret[0]; $result = $qfh_ret[0];
$feed_title = htmlspecialchars($qfh_ret[1]); $feed_title = htmlspecialchars($qfh_ret[1]);
$feed_site_url = $qfh_ret[2]; $feed_site_url = $qfh_ret[2];
$last_error = $qfh_ret[3]; /* $last_error = $qfh_ret[3]; */
$feed_self_url = get_self_url_prefix() . $feed_self_url = get_self_url_prefix() .
"/public.php?op=rss&id=$feed&key=" . "/public.php?op=rss&id=$feed&key=" .
@ -86,7 +93,7 @@ class Handler_Public extends Handler {
$tpl->setVariable('SELF_URL', htmlspecialchars(get_self_url_prefix()), true); $tpl->setVariable('SELF_URL', htmlspecialchars(get_self_url_prefix()), true);
while ($line = $this->dbh->fetch_assoc($result)) { while ($line = $this->dbh->fetch_assoc($result)) {
$line["content_preview"] = truncate_string(strip_tags($line["content_preview"]), 100, '...'); $line["content_preview"] = truncate_string(strip_tags($line["content"]), 100, '...');
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) { foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
$line = $p->hook_query_headlines($line); $line = $p->hook_query_headlines($line);
@ -100,7 +107,8 @@ class Handler_Public extends Handler {
$tpl->setVariable('ARTICLE_TITLE', htmlspecialchars($line['title']), true); $tpl->setVariable('ARTICLE_TITLE', htmlspecialchars($line['title']), true);
$tpl->setVariable('ARTICLE_EXCERPT', $line["content_preview"], true); $tpl->setVariable('ARTICLE_EXCERPT', $line["content_preview"], true);
$content = sanitize($line["content"], false, $owner_uid); $content = sanitize($line["content"], false, $owner_uid,
$feed_site_url);
if ($line['note']) { if ($line['note']) {
$content = "<div style=\"$note_style\">Article note: " . $line['note'] . "</div>" . $content = "<div style=\"$note_style\">Article note: " . $line['note'] . "</div>" .
@ -118,7 +126,7 @@ class Handler_Public extends Handler {
$tpl->setVariable('ARTICLE_AUTHOR', htmlspecialchars($line['author']), true); $tpl->setVariable('ARTICLE_AUTHOR', htmlspecialchars($line['author']), true);
$tpl->setVariable('ARTICLE_SOURCE_LINK', htmlspecialchars($line['site_url']), true); $tpl->setVariable('ARTICLE_SOURCE_LINK', htmlspecialchars($line['site_url']), true);
$tpl->setVariable('ARTICLE_SOURCE_TITLE', htmlspecialchars($line['feed_title']), true); $tpl->setVariable('ARTICLE_SOURCE_TITLE', htmlspecialchars($line['feed_title'] ? $line['feed_title'] : $feed_title), true);
$tags = get_article_tags($line["id"], $owner_uid); $tags = get_article_tags($line["id"], $owner_uid);
@ -269,16 +277,22 @@ class Handler_Public extends Handler {
function pubsub() { function pubsub() {
$mode = $this->dbh->escape_string($_REQUEST['hub_mode']); $mode = $this->dbh->escape_string($_REQUEST['hub_mode']);
if (!$mode) $mode = $this->dbh->escape_string($_REQUEST['hub.mode']);
$feed_id = (int) $this->dbh->escape_string($_REQUEST['id']); $feed_id = (int) $this->dbh->escape_string($_REQUEST['id']);
$feed_url = $this->dbh->escape_string($_REQUEST['hub_topic']); $feed_url = $this->dbh->escape_string($_REQUEST['hub_topic']);
if (!$feed_url) $feed_url = $this->dbh->escape_string($_REQUEST['hub.topic']);
if (!PUBSUBHUBBUB_ENABLED) { if (!PUBSUBHUBBUB_ENABLED) {
header('HTTP/1.0 404 Not Found'); header('HTTP/1.0 404 Not Found');
echo "404 Not found"; echo "404 Not found (Disabled by server)";
return; return;
} }
// TODO: implement hub_verifytoken checking // TODO: implement hub_verifytoken checking
// TODO: store requested rel=self or whatever for verification
// (may be different from stored feed url) e.g. http://url/ or http://url
$result = $this->dbh->query("SELECT feed_url FROM ttrss_feeds $result = $this->dbh->query("SELECT feed_url FROM ttrss_feeds
WHERE id = '$feed_id'"); WHERE id = '$feed_id'");
@ -287,7 +301,8 @@ class Handler_Public extends Handler {
$check_feed_url = $this->dbh->fetch_result($result, 0, "feed_url"); $check_feed_url = $this->dbh->fetch_result($result, 0, "feed_url");
if ($check_feed_url && ($check_feed_url == $feed_url || !$feed_url)) { // ignore url checking for the time being
if ($check_feed_url && (true || $check_feed_url == $feed_url || !$feed_url)) {
if ($mode == "subscribe") { if ($mode == "subscribe") {
$this->dbh->query("UPDATE ttrss_feeds SET pubsub_state = 2 $this->dbh->query("UPDATE ttrss_feeds SET pubsub_state = 2
@ -316,11 +331,11 @@ class Handler_Public extends Handler {
} }
} else { } else {
header('HTTP/1.0 404 Not Found'); header('HTTP/1.0 404 Not Found');
echo "404 Not found"; echo "404 Not found (URL check failed)";
} }
} else { } else {
header('HTTP/1.0 404 Not Found'); header('HTTP/1.0 404 Not Found');
echo "404 Not found"; echo "404 Not found (Feed not found)";
} }
} }
@ -363,9 +378,10 @@ class Handler_Public extends Handler {
$search_mode = $this->dbh->escape_string($_REQUEST["smode"]); $search_mode = $this->dbh->escape_string($_REQUEST["smode"]);
$view_mode = $this->dbh->escape_string($_REQUEST["view-mode"]); $view_mode = $this->dbh->escape_string($_REQUEST["view-mode"]);
$order = $this->dbh->escape_string($_REQUEST["order"]); $order = $this->dbh->escape_string($_REQUEST["order"]);
$start_ts = $this->dbh->escape_string($_REQUEST["ts"]);
$format = $this->dbh->escape_string($_REQUEST['format']); $format = $this->dbh->escape_string($_REQUEST['format']);
$orig_guid = !sql_bool_to_bool($_REQUEST["no_orig_guid"]); $orig_guid = sql_bool_to_bool($_REQUEST["orig_guid"]);
if (!$format) $format = 'atom'; if (!$format) $format = 'atom';
@ -385,24 +401,24 @@ class Handler_Public extends Handler {
if ($owner_id) { if ($owner_id) {
$this->generate_syndicated_feed($owner_id, $feed, $is_cat, $limit, $this->generate_syndicated_feed($owner_id, $feed, $is_cat, $limit,
$offset, $search, $search_mode, $view_mode, $format, $order, $orig_guid); $offset, $search, $search_mode, $view_mode, $format, $order, $orig_guid, $start_ts);
} else { } else {
header('HTTP/1.1 403 Forbidden'); header('HTTP/1.1 403 Forbidden');
} }
} }
function updateTask() { function updateTask() {
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_UPDATE_TASK, "hook_update_task", $op); PluginHost::getInstance()->run_hooks(PluginHost::HOOK_UPDATE_TASK, "hook_update_task", false);
} }
function housekeepingTask() { function housekeepingTask() {
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_HOUSE_KEEPING, "hook_house_keeping", $op); PluginHost::getInstance()->run_hooks(PluginHost::HOOK_HOUSE_KEEPING, "hook_house_keeping", false);
} }
function globalUpdateFeeds() { function globalUpdateFeeds() {
RPC::updaterandomfeed_real($this->dbh); RPC::updaterandomfeed_real($this->dbh);
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_UPDATE_TASK, "hook_update_task", $op); PluginHost::getInstance()->run_hooks(PluginHost::HOOK_UPDATE_TASK, "hook_update_task", false);
} }
function sharepopup() { function sharepopup() {
@ -411,11 +427,14 @@ class Handler_Public extends Handler {
} }
header('Content-Type: text/html; charset=utf-8'); header('Content-Type: text/html; charset=utf-8');
print "<html><head><title>Tiny Tiny RSS</title>"; 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\">";
stylesheet_tag("css/utility.css"); echo stylesheet_tag("css/utility.css");
javascript_tag("lib/prototype.js"); echo stylesheet_tag("css/dijit.css");
javascript_tag("lib/scriptaculous/scriptaculous.js?load=effects,dragdrop,controls"); 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\"/> print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
</head><body id='sharepopup'>"; </head><body id='sharepopup'>";
@ -561,7 +580,7 @@ class Handler_Public extends Handler {
} }
} else { } else {
$_SESSION["login_error_msg"] = __("Incorrect username or password"); $_SESSION["login_error_msg"] = __("Incorrect username or password");
user_error("Failed login attempt from {$_SERVER['REMOTE_ADDR']}", E_USER_WARNING); user_error("Failed login attempt for $login from {$_SERVER['REMOTE_ADDR']}", E_USER_WARNING);
} }
if ($_REQUEST['return']) { if ($_REQUEST['return']) {
@ -572,6 +591,18 @@ class Handler_Public extends Handler {
} }
} }
/* function subtest() {
header("Content-type: text/plain; charset=utf-8");
$url = $_REQUEST["url"];
print "$url\n\n";
print_r(get_feeds_from_html($url, fetch_file_contents($url)));
} */
function subscribe() { function subscribe() {
if (SINGLE_USER_MODE) { if (SINGLE_USER_MODE) {
login_sequence(); login_sequence();
@ -587,6 +618,9 @@ class Handler_Public extends Handler {
<title>Tiny Tiny RSS</title> <title>Tiny Tiny RSS</title>
<link rel=\"stylesheet\" type=\"text/css\" href=\"css/utility.css\"> <link rel=\"stylesheet\" type=\"text/css\" href=\"css/utility.css\">
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/> <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> </head>
<body> <body>
<img class=\"floatingLogo\" src=\"images/logo_small.png\" <img class=\"floatingLogo\" src=\"images/logo_small.png\"
@ -671,93 +705,6 @@ class Handler_Public extends Handler {
} }
} }
function subscribe2() {
$feed_url = $this->dbh->escape_string(trim($_REQUEST["feed_url"]));
$cat_id = $this->dbh->escape_string($_REQUEST["cat_id"]);
$from = $this->dbh->escape_string($_REQUEST["from"]);
$feed_urls = array();
/* only read authentication information from POST */
$auth_login = $this->dbh->escape_string(trim($_POST["auth_login"]));
$auth_pass = $this->dbh->escape_string(trim($_POST["auth_pass"]));
$rc = subscribe_to_feed($feed_url, $cat_id, $auth_login, $auth_pass);
switch ($rc) {
case 1:
print_notice(T_sprintf("Subscribed to <b>%s</b>.", $feed_url));
break;
case 2:
print_error(T_sprintf("Could not subscribe to <b>%s</b>.", $feed_url));
break;
case 3:
print_error(T_sprintf("No feeds found in <b>%s</b>.", $feed_url));
break;
case 0:
print_warning(T_sprintf("Already subscribed to <b>%s</b>.", $feed_url));
break;
case 4:
print_notice(__("Multiple feed URLs found."));
$contents = @fetch_file_contents($url, false, $auth_login, $auth_pass);
if (is_html($contents)) {
$feed_urls = get_feeds_from_html($url, $contents);
}
break;
case 5:
print_error(T_sprintf("Could not subscribe to <b>%s</b>.<br>Can't download the Feed URL.", $feed_url));
break;
}
if ($feed_urls) {
print "<form action=\"backend.php\">";
print "<input type=\"hidden\" name=\"op\" value=\"pref-feeds\">";
print "<input type=\"hidden\" name=\"quiet\" value=\"1\">";
print "<input type=\"hidden\" name=\"method\" value=\"add\">";
print "<select name=\"feed_url\">";
foreach ($feed_urls as $url => $name) {
$url = htmlspecialchars($url);
$name = htmlspecialchars($name);
print "<option value=\"$url\">$name</option>";
}
print "<input type=\"submit\" value=\"".__("Subscribe to selected feed")."\">";
print "</form>";
}
$tp_uri = get_self_url_prefix() . "/prefs.php";
$tt_uri = get_self_url_prefix();
if ($rc <= 2){
$result = $this->dbh->query("SELECT id FROM ttrss_feeds WHERE
feed_url = '$feed_url' AND owner_uid = " . $_SESSION["uid"]);
$feed_id = $this->dbh->fetch_result($result, 0, "id");
} else {
$feed_id = 0;
}
print "<p>";
if ($feed_id) {
print "<form method=\"GET\" style='display: inline'
action=\"$tp_uri\">
<input type=\"hidden\" name=\"tab\" value=\"feedConfig\">
<input type=\"hidden\" name=\"method\" value=\"editFeed\">
<input type=\"hidden\" name=\"methodparam\" value=\"$feed_id\">
<input type=\"submit\" value=\"".__("Edit subscription options")."\">
</form>";
}
print "<form style='display: inline' method=\"GET\" action=\"$tt_uri\">
<input type=\"submit\" value=\"".__("Return to Tiny Tiny RSS")."\">
</form></p>";
print "</body></html>";
}
function index() { function index() {
header("Content-Type: text/plain"); header("Content-Type: text/plain");
print json_encode(array("error" => array("code" => 7))); print json_encode(array("error" => array("code" => 7)));
@ -766,11 +713,15 @@ class Handler_Public extends Handler {
function forgotpass() { function forgotpass() {
startup_gettext(); startup_gettext();
header('Content-Type: text/html; charset=utf-8'); @$hash = $_REQUEST["hash"];
print "<html><head><title>Tiny Tiny RSS</title>";
stylesheet_tag("css/utility.css"); header('Content-Type: text/html; charset=utf-8');
javascript_tag("lib/prototype.js"); 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 javascript_tag("lib/prototype.js");
print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/> print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
</head><body id='forgotpass'>"; </head><body id='forgotpass'>";
@ -781,8 +732,45 @@ class Handler_Public extends Handler {
@$method = $_POST['method']; @$method = $_POST['method'];
if (!$method) { if ($hash) {
print_notice(__("You will need to provide valid account name and email. New password will be sent on your email address.")); $login = $_REQUEST["login"];
if ($login) {
$result = $this->dbh->query("SELECT id, resetpass_token FROM ttrss_users
WHERE login = '$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");
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");
Pref_Users::resetUserPassword($id, true);
print "<p>"."Completed."."</p>";
} else {
print_error("Some of the information provided is missing or incorrect.");
}
} else {
print_error("Some of the information provided is missing or incorrect.");
}
} else {
print_error("Some of the information provided is missing or incorrect.");
}
print "<form method=\"GET\" action=\"index.php\">
<input type=\"submit\" value=\"".__("Return to Tiny Tiny RSS")."\">
</form>";
} else if (!$method) {
print_notice(__("You will need to provide valid account name and email. A password reset link will be sent to your email address."));
print "<form method='POST' action='public.php'>"; print "<form method='POST' action='public.php'>";
print "<input type='hidden' name='method' value='do'>"; print "<input type='hidden' name='method' value='do'>";
@ -823,17 +811,57 @@ class Handler_Public extends Handler {
} else { } else {
print_notice("Password reset instructions are being sent to your email address.");
$result = $this->dbh->query("SELECT id FROM ttrss_users $result = $this->dbh->query("SELECT id FROM ttrss_users
WHERE login = '$login' AND email = '$email'"); WHERE login = '$login' AND email = '$email'");
if ($this->dbh->num_rows($result) != 0) { if ($this->dbh->num_rows($result) != 0) {
$id = $this->dbh->fetch_result($result, 0, "id"); $id = $this->dbh->fetch_result($result, 0, "id");
Pref_Users::resetUserPassword($id, false); if ($id) {
$resetpass_token = sha1(get_random_bytes(128));
$resetpass_link = get_self_url_prefix() . "/public.php?op=forgotpass&hash=" . $resetpass_token .
"&login=" . urlencode($login);
print "<p>"; require_once 'classes/ttrssmailer.php';
require_once "lib/MiniTemplator.class.php";
print "<p>"."Completed."."</p>"; $tpl = new MiniTemplator;
$tpl->readTemplateFromFile("templates/resetpass_link_template.txt");
$tpl->setVariable('LOGIN', $login);
$tpl->setVariable('RESETPASS_LINK', $resetpass_link);
$tpl->addBlock('message');
$message = "";
$tpl->generateOutputToString($message);
$mail = new ttrssMailer();
$rc = $mail->quickMail($email, $login,
__("[tt-rss] Password reset request"),
$message, false);
if (!$rc) print_error($mail->ErrorInfo);
$resetpass_token_full = $this->dbh->escape_string(time() . ":" . $resetpass_token);
$result = $this->dbh->query("UPDATE ttrss_users
SET resetpass_token = '$resetpass_token_full'
WHERE login = '$login' AND email = '$email'");
//Pref_Users::resetUserPassword($id, false);
print "<p>";
print "<p>"."Completed."."</p>";
} else {
print_error("User ID not found.");
}
print "<form method=\"GET\" action=\"index.php\"> print "<form method=\"GET\" action=\"index.php\">
<input type=\"submit\" value=\"".__("Return to Tiny Tiny RSS")."\"> <input type=\"submit\" value=\"".__("Return to Tiny Tiny RSS")."\">
@ -872,6 +900,8 @@ class Handler_Public extends Handler {
<title>Database Updater</title> <title>Database Updater</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <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/utility.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> </head>
<style type="text/css"> <style type="text/css">
span.ok { color : #009000; font-weight : bold; } span.ok { color : #009000; font-weight : bold; }

View file

@ -257,8 +257,8 @@ class Opml extends Handler_Protected {
$feed_title = $this->dbh->escape_string(mb_substr($attrs->getNamedItem('text')->nodeValue, 0, 250)); $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)); if (!$feed_title) $feed_title = $this->dbh->escape_string(mb_substr($attrs->getNamedItem('title')->nodeValue, 0, 250));
$feed_url = $this->dbh->escape_string(mb_substr($attrs->getNamedItem('xmlUrl')->nodeValue, 0, 250)); $feed_url = $this->dbh->escape_string($attrs->getNamedItem('xmlUrl')->nodeValue);
if (!$feed_url) $feed_url = $this->dbh->escape_string(mb_substr($attrs->getNamedItem('xmlURL')->nodeValue, 0, 250)); if (!$feed_url) $feed_url = $this->dbh->escape_string($attrs->getNamedItem('xmlURL')->nodeValue);
$site_url = $this->dbh->escape_string(mb_substr($attrs->getNamedItem('htmlUrl')->nodeValue, 0, 250)); $site_url = $this->dbh->escape_string(mb_substr($attrs->getNamedItem('htmlUrl')->nodeValue, 0, 250));

View file

@ -39,6 +39,10 @@ class PluginHost {
const HOOK_FETCH_FEED = 22; const HOOK_FETCH_FEED = 22;
const HOOK_QUERY_HEADLINES = 23; const HOOK_QUERY_HEADLINES = 23;
const HOOK_HOUSE_KEEPING = 24; const HOOK_HOUSE_KEEPING = 24;
const HOOK_SEARCH = 25;
const HOOK_FORMAT_ENCLOSURES = 26;
const HOOK_SUBSCRIBE_FEED = 27;
const HOOK_HEADLINES_BEFORE = 28;
const KIND_ALL = 1; const KIND_ALL = 1;
const KIND_SYSTEM = 2; const KIND_SYSTEM = 2;
@ -75,6 +79,16 @@ class PluginHost {
return $this->dbh; return $this->dbh;
} }
function get_plugin_names() {
$names = array();
foreach ($this->plugins as $p) {
array_push($names, get_class($p));
}
return $names;
}
function get_plugins() { function get_plugins() {
return $this->plugins; return $this->plugins;
} }
@ -99,7 +113,7 @@ class PluginHost {
function del_hook($type, $sender) { function del_hook($type, $sender) {
if (is_array($this->hooks[$type])) { if (is_array($this->hooks[$type])) {
$key = array_Search($this->hooks[$type], $sender); $key = array_Search($sender, $this->hooks[$type]);
if ($key !== FALSE) { if ($key !== FALSE) {
unset($this->hooks[$type][$key]); unset($this->hooks[$type][$key]);
} }

View file

@ -55,6 +55,7 @@ class Pref_Feeds extends Handler_Protected {
$cat['unread'] = 0; $cat['unread'] = 0;
$cat['child_unread'] = 0; $cat['child_unread'] = 0;
$cat['auxcounter'] = 0; $cat['auxcounter'] = 0;
$cat['parent_id'] = $cat_id;
$cat['items'] = $this->get_category_items($line['id']); $cat['items'] = $this->get_category_items($line['id']);
@ -395,7 +396,7 @@ class Pref_Feeds extends Handler_Protected {
# print_r($data['items']); # print_r($data['items']);
if (is_array($data) && is_array($data['items'])) { if (is_array($data) && is_array($data['items'])) {
$cat_order_id = 0; # $cat_order_id = 0;
$data_map = array(); $data_map = array();
$root_item = false; $root_item = false;
@ -494,7 +495,7 @@ class Pref_Feeds extends Handler_Protected {
$feed_id = $this->dbh->escape_string($_REQUEST["feed_id"]); $feed_id = $this->dbh->escape_string($_REQUEST["feed_id"]);
if (is_file($icon_file) && $feed_id) { if (is_file($icon_file) && $feed_id) {
if (filesize($icon_file) < 20000) { if (filesize($icon_file) < 65535) {
$result = $this->dbh->query("SELECT id FROM ttrss_feeds $result = $this->dbh->query("SELECT id FROM ttrss_feeds
WHERE id = '$feed_id' AND owner_uid = ". $_SESSION["uid"]); WHERE id = '$feed_id' AND owner_uid = ". $_SESSION["uid"]);
@ -737,9 +738,9 @@ class Pref_Feeds extends Handler_Protected {
<input type=\"hidden\" name=\"op\" value=\"pref-feeds\"> <input type=\"hidden\" name=\"op\" value=\"pref-feeds\">
<input type=\"hidden\" name=\"feed_id\" value=\"$feed_id\"> <input type=\"hidden\" name=\"feed_id\" value=\"$feed_id\">
<input type=\"hidden\" name=\"method\" value=\"uploadicon\"> <input type=\"hidden\" name=\"method\" value=\"uploadicon\">
<button dojoType=\"dijit.form.Button\" onclick=\"return uploadFeedIcon();\" <button class=\"small\" dojoType=\"dijit.form.Button\" onclick=\"return uploadFeedIcon();\"
type=\"submit\">".__('Replace')."</button> type=\"submit\">".__('Replace')."</button>
<button dojoType=\"dijit.form.Button\" onclick=\"return removeFeedIcon($feed_id);\" <button class=\"small\" dojoType=\"dijit.form.Button\" onclick=\"return removeFeedIcon($feed_id);\"
type=\"submit\">".__('Remove')."</button> type=\"submit\">".__('Remove')."</button>
</form>"; </form>";
@ -962,7 +963,7 @@ class Pref_Feeds extends Handler_Protected {
if (!$batch) { if (!$batch) {
$result = $this->dbh->query("UPDATE ttrss_feeds SET $this->dbh->query("UPDATE ttrss_feeds SET
$category_qpart $category_qpart
title = '$feed_title', feed_url = '$feed_link', title = '$feed_title', feed_url = '$feed_link',
update_interval = '$upd_intl', update_interval = '$upd_intl',
@ -1259,13 +1260,18 @@ class Pref_Feeds extends Handler_Protected {
$interval_qpart = "DATE_SUB(NOW(), INTERVAL 3 MONTH)"; $interval_qpart = "DATE_SUB(NOW(), INTERVAL 3 MONTH)";
} }
$result = $this->dbh->query("SELECT COUNT(*) AS num_inactive FROM ttrss_feeds WHERE // could be performance-intensive and prevent feeds pref-panel from showing
if (!defined('_DISABLE_INACTIVE_FEEDS') || !_DISABLE_INACTIVE_FEEDS) {
$result = $this->dbh->query("SELECT COUNT(*) AS num_inactive FROM ttrss_feeds WHERE
(SELECT MAX(updated) FROM ttrss_entries, ttrss_user_entries WHERE (SELECT MAX(updated) FROM ttrss_entries, ttrss_user_entries WHERE
ttrss_entries.id = ref_id AND ttrss_entries.id = ref_id AND
ttrss_user_entries.feed_id = ttrss_feeds.id) < $interval_qpart AND ttrss_user_entries.feed_id = ttrss_feeds.id) < $interval_qpart AND
ttrss_feeds.owner_uid = ".$_SESSION["uid"]); ttrss_feeds.owner_uid = ".$_SESSION["uid"]);
$num_inactive = $this->dbh->fetch_result($result, 0, "num_inactive"); $num_inactive = $this->dbh->fetch_result($result, 0, "num_inactive");
} else {
$num_inactive = 0;
}
if ($num_inactive > 0) { if ($num_inactive > 0) {
$inactive_button = "<button dojoType=\"dijit.form.Button\" $inactive_button = "<button dojoType=\"dijit.form.Button\"
@ -1573,8 +1579,6 @@ class Pref_Feeds extends Handler_Protected {
# class needed for selectTableRows() # class needed for selectTableRows()
print "<tr class=\"placeholder\" $this_row_id>"; print "<tr class=\"placeholder\" $this_row_id>";
$edit_title = htmlspecialchars($line["title"]);
# id needed for selectTableRows() # id needed for selectTableRows()
print "<td width='5%' align='center'><input print "<td width='5%' align='center'><input
onclick='toggleSelectRow2(this);' dojoType=\"dijit.form.CheckBox\" onclick='toggleSelectRow2(this);' dojoType=\"dijit.form.CheckBox\"
@ -1639,8 +1643,6 @@ class Pref_Feeds extends Handler_Protected {
# class needed for selectTableRows() # class needed for selectTableRows()
print "<tr class=\"placeholder\" $this_row_id>"; print "<tr class=\"placeholder\" $this_row_id>";
$edit_title = htmlspecialchars($line["title"]);
# id needed for selectTableRows() # id needed for selectTableRows()
print "<td width='5%' align='center'><input print "<td width='5%' align='center'><input
onclick='toggleSelectRow2(this);' dojoType=\"dijit.form.CheckBox\" onclick='toggleSelectRow2(this);' dojoType=\"dijit.form.CheckBox\"
@ -1891,7 +1893,7 @@ class Pref_Feeds extends Handler_Protected {
AND owner_uid = " . $owner_uid); AND owner_uid = " . $owner_uid);
if ($this->dbh->num_rows($result) == 1) { if ($this->dbh->num_rows($result) == 1) {
$key = $this->dbh->escape_string(sha1(uniqid(rand(), true))); $key = $this->dbh->escape_string(uniqid(base_convert(rand(), 10, 36)));
$this->dbh->query("UPDATE ttrss_access_keys SET access_key = '$key' $this->dbh->query("UPDATE ttrss_access_keys SET access_key = '$key'
WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat WHERE feed_id = '$feed_id' AND is_cat = $sql_is_cat

View file

@ -88,7 +88,6 @@ class Pref_Filters extends Handler_Protected {
$result = $qfh_ret[0]; $result = $qfh_ret[0];
$articles = array();
$found = 0; $found = 0;
print __("Articles matching this filter:"); print __("Articles matching this filter:");
@ -97,15 +96,12 @@ class Pref_Filters extends Handler_Protected {
print "<table width=\"100%\" cellspacing=\"0\" id=\"prefErrorFeedList\">"; print "<table width=\"100%\" cellspacing=\"0\" id=\"prefErrorFeedList\">";
while ($line = $this->dbh->fetch_assoc($result)) { while ($line = $this->dbh->fetch_assoc($result)) {
$line["content_preview"] = truncate_string(strip_tags($line["content_preview"]), 100, '...'); $line["content_preview"] = truncate_string(strip_tags($line["content"]), 100, '...');
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) { foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
$line = $p->hook_query_headlines($line, 100); $line = $p->hook_query_headlines($line, 100);
} }
$entry_timestamp = strtotime($line["updated"]);
$entry_tags = get_article_tags($line["id"], $_SESSION["uid"]);
$content_preview = $line["content_preview"]; $content_preview = $line["content_preview"];
if ($line["feed_title"]) if ($line["feed_title"])
@ -151,6 +147,40 @@ class Pref_Filters extends Handler_Protected {
} }
private function getfilterrules_concise($filter_id) {
$result = $this->dbh->query("SELECT reg_exp,
inverse,
feed_id,
cat_id,
cat_filter,
ttrss_filter_types.description AS field
FROM
ttrss_filters2_rules, ttrss_filter_types
WHERE
filter_id = '$filter_id' AND filter_type = ttrss_filter_types.id");
$rv = "";
while ($line = $this->dbh->fetch_assoc($result)) {
$where = sql_bool_to_bool($line["cat_filter"]) ?
getCategoryTitle($line["cat_id"]) :
($line["feed_id"] ?
getFeedTitle($line["feed_id"]) : __("All feeds"));
# $where = $line["cat_id"] . "/" . $line["feed_id"];
$inverse = sql_bool_to_bool($line["inverse"]) ? "inverse" : "";
$rv .= "<span class='$inverse'>" . T_sprintf("%s on %s in %s %s",
strip_tags($line["reg_exp"]),
$line["field"],
$where,
sql_bool_to_bool($line["inverse"]) ? __("(inverse)") : "") . "</span>";
}
return $rv;
}
function getfiltertree() { function getfiltertree() {
$root = array(); $root = array();
@ -174,24 +204,11 @@ class Pref_Filters extends Handler_Protected {
owner_uid = ".$_SESSION["uid"]." ORDER BY order_id, title"); owner_uid = ".$_SESSION["uid"]." ORDER BY order_id, title");
$action_id = -1;
$folder = array(); $folder = array();
$folder['items'] = array(); $folder['items'] = array();
while ($line = $this->dbh->fetch_assoc($result)) { while ($line = $this->dbh->fetch_assoc($result)) {
/* if ($action_id != $line["action_id"]) {
if (count($folder['items']) > 0) {
array_push($root['items'], $folder);
}
$folder = array();
$folder['id'] = $line["action_id"];
$folder['name'] = __($line["action_name"]);
$folder['items'] = array();
$action_id = $line["action_id"];
} */
$name = $this->getFilterName($line["id"]); $name = $this->getFilterName($line["id"]);
$match_ok = false; $match_ok = false;
@ -227,6 +244,7 @@ class Pref_Filters extends Handler_Protected {
$filter['param'] = $name[1]; $filter['param'] = $name[1];
$filter['checkbox'] = false; $filter['checkbox'] = false;
$filter['enabled'] = sql_bool_to_bool($line["enabled"]); $filter['enabled'] = sql_bool_to_bool($line["enabled"]);
$filter['rules'] = $this->getfilterrules_concise($line['id']);
if (!$filter_search || $match_ok) { if (!$filter_search || $match_ok) {
array_push($folder['items'], $filter); array_push($folder['items'], $filter);
@ -433,8 +451,11 @@ class Pref_Filters extends Handler_Protected {
WHERE id = ".(int)$rule["filter_type"]); WHERE id = ".(int)$rule["filter_type"]);
$filter_type = $this->dbh->fetch_result($result, 0, "description"); $filter_type = $this->dbh->fetch_result($result, 0, "description");
return T_sprintf("%s on %s in %s %s", strip_tags($rule["reg_exp"]), $inverse = isset($rule["inverse"]) ? "inverse" : "";
$filter_type, $feed, isset($rule["inverse"]) ? __("(inverse)") : "");
return "<span class='filterRule $inverse'>" .
T_sprintf("%s on %s in %s %s", strip_tags($rule["reg_exp"]),
$filter_type, $feed, isset($rule["inverse"]) ? __("(inverse)") : "") . "</span>";
} }
function printRuleName() { function printRuleName() {
@ -471,7 +492,7 @@ class Pref_Filters extends Handler_Protected {
$inverse = checkbox_to_sql_bool($this->dbh->escape_string($_REQUEST["inverse"])); $inverse = checkbox_to_sql_bool($this->dbh->escape_string($_REQUEST["inverse"]));
$title = $this->dbh->escape_string($_REQUEST["title"]); $title = $this->dbh->escape_string($_REQUEST["title"]);
$result = $this->dbh->query("UPDATE ttrss_filters2 SET enabled = $enabled, $this->dbh->query("UPDATE ttrss_filters2 SET enabled = $enabled,
match_any_rule = $match_any_rule, match_any_rule = $match_any_rule,
inverse = $inverse, inverse = $inverse,
title = '$title' title = '$title'

View file

@ -181,7 +181,8 @@ class Pref_Prefs extends Handler_Protected {
global $access_level_names; global $access_level_names;
$prefs_blacklist = array("STRIP_UNSAFE_TAGS", "REVERSE_HEADLINES", $prefs_blacklist = array("STRIP_UNSAFE_TAGS", "REVERSE_HEADLINES",
"SORT_HEADLINES_BY_FEED_DATE", "DEFAULT_ARTICLE_LIMIT"); "SORT_HEADLINES_BY_FEED_DATE", "DEFAULT_ARTICLE_LIMIT",
"FEEDS_SORT_BY_UNREAD");
/* "FEEDS_SORT_BY_UNREAD", "HIDE_READ_FEEDS", "REVERSE_HEADLINES" */ /* "FEEDS_SORT_BY_UNREAD", "HIDE_READ_FEEDS", "REVERSE_HEADLINES" */
@ -887,8 +888,9 @@ class Pref_Prefs extends Handler_Protected {
if (!$otp_enabled) { if (!$otp_enabled) {
$secret = $base32->encode(sha1($this->dbh->fetch_result($result, 0, "salt"))); $secret = $base32->encode(sha1($this->dbh->fetch_result($result, 0, "salt")));
$topt = new \OTPHP\TOTP($secret); print QRcode::png("otpauth://totp/".urlencode($login).
print QRcode::png($topt->provisioning_uri($login)); "?secret=$secret&issuer=".urlencode("Tiny Tiny RSS"));
} }
} }

View file

@ -258,7 +258,7 @@ class Pref_Users extends Handler_Protected {
$pwd_hash = encrypt_password($tmp_user_pwd, $new_salt, true); $pwd_hash = encrypt_password($tmp_user_pwd, $new_salt, true);
db_query("UPDATE ttrss_users SET pwd_hash = '$pwd_hash', salt = '$new_salt' db_query("UPDATE ttrss_users SET pwd_hash = '$pwd_hash', salt = '$new_salt', otp_enabled = false
WHERE id = '$uid'"); WHERE id = '$uid'");
if ($show_password) { if ($show_password) {

View file

@ -95,7 +95,7 @@ class RPC extends Handler_Protected {
WHERE orig_feed_id = '$id') = 0 AND WHERE orig_feed_id = '$id') = 0 AND
id = '$id' AND owner_uid = ".$_SESSION["uid"]); id = '$id' AND owner_uid = ".$_SESSION["uid"]);
$rc = $this->dbh->affected_rows($result); $this->dbh->affected_rows($result);
} }
} }
@ -138,7 +138,7 @@ class RPC extends Handler_Protected {
$mark = "false"; $mark = "false";
} }
$result = $this->dbh->query("UPDATE ttrss_user_entries SET marked = $mark, $this->dbh->query("UPDATE ttrss_user_entries SET marked = $mark,
last_marked = NOW() last_marked = NOW()
WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]); WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
@ -148,8 +148,8 @@ class RPC extends Handler_Protected {
function delete() { function delete() {
$ids = $this->dbh->escape_string($_REQUEST["ids"]); $ids = $this->dbh->escape_string($_REQUEST["ids"]);
$result = $this->dbh->query("DELETE FROM ttrss_user_entries $this->dbh->query("DELETE FROM ttrss_user_entries
WHERE ref_id IN ($ids) AND owner_uid = " . $_SESSION["uid"]); WHERE ref_id IN ($ids) AND owner_uid = " . $_SESSION["uid"]);
purge_orphans(); purge_orphans();
@ -258,7 +258,6 @@ class RPC extends Handler_Protected {
function publ() { function publ() {
$pub = $_REQUEST["pub"]; $pub = $_REQUEST["pub"];
$id = $this->dbh->escape_string($_REQUEST["id"]); $id = $this->dbh->escape_string($_REQUEST["id"]);
$note = trim(strip_tags($this->dbh->escape_string($_REQUEST["note"])));
if ($pub == "1") { if ($pub == "1") {
$pub = "true"; $pub = "true";
@ -266,7 +265,7 @@ class RPC extends Handler_Protected {
$pub = "false"; $pub = "false";
} }
$result = $this->dbh->query("UPDATE ttrss_user_entries SET $this->dbh->query("UPDATE ttrss_user_entries SET
published = $pub, last_published = NOW() published = $pub, last_published = NOW()
WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]); WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
@ -620,7 +619,7 @@ class RPC extends Handler_Protected {
$p = new Publisher(PUBSUBHUBBUB_HUB); $p = new Publisher(PUBSUBHUBBUB_HUB);
$pubsub_result = $p->publish_update($rss_link); /* $pubsub_result = */ $p->publish_update($rss_link);
} }
} }

View file

@ -104,13 +104,9 @@
// Enable client PubSubHubbub support in tt-rss. When disabled, tt-rss // Enable client PubSubHubbub support in tt-rss. When disabled, tt-rss
// won't try to subscribe to PUSH feed updates. // won't try to subscribe to PUSH feed updates.
// ********************* // ****************************
// *** Sphinx search *** // *** Sphinx search plugin ***
// ********************* // ****************************
define('SPHINX_ENABLED', false);
// Enable fulltext search using Sphinx (http://www.sphinxsearch.com)
// Please see http://tt-rss.org/wiki/SphinxSearch for more information.
define('SPHINX_SERVER', 'localhost:9312'); define('SPHINX_SERVER', 'localhost:9312');
// Hostname:port combination for the Sphinx server. // Hostname:port combination for the Sphinx server.
@ -184,6 +180,12 @@
define('CHECK_FOR_NEW_VERSION', true); define('CHECK_FOR_NEW_VERSION', true);
// Check for new versions of tt-rss automatically. // Check for new versions of tt-rss automatically.
define('DETECT_ARTICLE_LANGUAGE', false);
// Detect article language when updating feeds, presently this is only
// used for hyphenation. This may increase amount of CPU time used by
// update processes, disable if necessary (i.e. you are being billed
// for CPU time).
define('ENABLE_GZIP_OUTPUT', false); define('ENABLE_GZIP_OUTPUT', false);
// Selectively gzip output to improve wire performance. This requires // Selectively gzip output to improve wire performance. This requires
// PHP Zlib extension on the server. // PHP Zlib extension on the server.
@ -211,4 +213,3 @@
// if necessary (after migrating all new options from this file). // if necessary (after migrating all new options from this file).
// vim:ft=php // vim:ft=php
?>

View file

@ -52,7 +52,8 @@ div.cdmHeader input {
div.cdmContentInner { div.cdmContentInner {
margin : 10px; margin : 10px;
line-height : 20px; line-height : 1.5;
font-size : 15px;
} }
div.cdmContentInner img { div.cdmContentInner img {
@ -61,6 +62,16 @@ div.cdmContentInner img {
height : auto; height : auto;
} }
div.cdmContentInner h1 {
font-size : 16px;
}
div.cdmContentInner h2,
div.cdmContentInner h3,
div.cdmContentInner h4 {
font-size : 15px;
}
div.cdmFooter { div.cdmFooter {
padding : 5px; padding : 5px;
font-weight : normal; font-weight : normal;
@ -68,15 +79,25 @@ div.cdmFooter {
clear : both; clear : both;
} }
div.cdm {
margin-right : 4px;
}
div.cdm.expanded { div.cdm.expanded {
margin-top : 4px; margin-top : 4px;
margin-bottom : 4px; margin-bottom : 4px;
} }
div.cdm.expanded div.cdmFooter {
border-style : solid;
border-width : 0px 0px 1px 0px;
border-color : #ddd;
}
div.cdm.expandable { div.cdm.expandable {
background-color : #f0f0f0; background-color : #f0f0f0;
border-width : 0px 0px 1px 0px; border-width : 0px 0px 1px 0px;
border-color : #c0c0c0; border-color : #ddd;
border-style : solid; border-style : solid;
} }
@ -98,8 +119,6 @@ div.cdm.expandable.Selected {
} }
div.cdm.expandable.active { div.cdm.expandable.active {
box-shadow : inset 0px 0px 3px 0px rgba(0,0,0,0.1);
border-color : #88b0f0;
background : white ! important; background : white ! important;
} }
@ -115,10 +134,15 @@ div.cdm.expandable.active div.cdmHeader span.titleWrap {
} }
div.cdm.expandable div.cdmHeader a.title { div.cdm.expandable div.cdmHeader a.title {
font-weight : bold; font-weight : 600;
color : #555; color : #555;
font-size : 14px;
-webkit-transition : color 0.2s; -webkit-transition : color 0.2s;
transition : color 0.2s; transition : color 0.2s;
text-rendering: optimizelegibility;
font-family : "Segoe WP Semibold", "Segoe UI Semibold",
"Segoe UI Web Semibold", "Segoe UI", Ubuntu, "DejaVu Sans", "Helvetica Neue",
Helvetica, Arial, sans-serif;
} }
div.cdm.expandable.Unread div.cdmHeader a.title { div.cdm.expandable.Unread div.cdmHeader a.title {
@ -127,6 +151,12 @@ div.cdm.expandable.Unread div.cdmHeader a.title {
div.cdm.expandable.active div.cdmHeader a.title { div.cdm.expandable.active div.cdmHeader a.title {
color : #4684ff; color : #4684ff;
font-size : 16px;
font-weight : 600;
text-rendering: optimizelegibility;
font-family : "Segoe WP Semibold", "Segoe UI Semibold",
"Segoe UI Web Semibold", "Segoe UI", Ubuntu, "DejaVu Sans", "Helvetica Neue",
Helvetica, Arial, sans-serif;
} }
div.cdm.expanded div.cdmHeader { div.cdm.expanded div.cdmHeader {
@ -134,11 +164,15 @@ div.cdm.expanded div.cdmHeader {
} }
div.cdm.expanded div.cdmHeader a.title { div.cdm.expanded div.cdmHeader a.title {
font-size : 14px; font-size : 16px;
color : #999; color : #999;
font-weight : bold; font-weight : 600;
-webkit-transition : color 0.2s; -webkit-transition : color 0.2s;
transition : color 0.2s; transition : color 0.2s;
text-rendering: optimizelegibility;
font-family : "Segoe WP Semibold", "Segoe UI Semibold",
"Segoe UI Web Semibold", "Segoe UI", Ubuntu, "DejaVu Sans", "Helvetica Neue",
Helvetica, Arial, sans-serif;
} }
div.cdm.expanded.active { div.cdm.expanded.active {
@ -167,7 +201,7 @@ div.cdm.active div.cdmContent {
span.cdmExcerpt { span.cdmExcerpt {
font-size : 11px; font-size : 11px;
color : #555; color : #999;
font-weight : normal; font-weight : normal;
cursor : pointer; cursor : pointer;
} }
@ -222,7 +256,6 @@ div.cdm .hlFeed a {
div.cdmContentInner p { div.cdmContentInner p {
max-width : 650px; max-width : 650px;
text-align : justify;
-webkit-hyphens: auto; -webkit-hyphens: auto;
-moz-hyphens: auto; -moz-hyphens: auto;
hyphens: auto; hyphens: auto;
@ -242,15 +275,15 @@ div.cdmHeader span.author {
div#floatingTitle { div#floatingTitle {
position : absolute; position : absolute;
z-index : 5; z-index : 5;
top : 25px; top : 0px;
right : 0px; right : 0px;
left : 0px; left : 0px;
border-color : #ccc; border-color : #ddd;
border-width : 1px 0px 1px 0px; border-width : 0px 0px 1px 0px;
border-style : solid; border-style : solid;
background : #fcfcfc; background : white;
color : #555; color : #555;
box-shadow : 0px 1px 1px 0px rgba(0,0,0,0.1); box-shadow : 0px 1px 1px -1px rgba(0,0,0,0.1);
} }
div#floatingTitle > * { div#floatingTitle > * {
@ -272,11 +305,15 @@ div#floatingTitle span.author {
} }
div#floatingTitle a.title { div#floatingTitle a.title {
font-size : 14px; font-size : 16px;
color : #999; color : #999;
font-weight : bold;
-webkit-transition : color 0.2s; -webkit-transition : color 0.2s;
transition : color 0.2s; transition : color 0.2s;
font-weight : 600;
text-rendering: optimizelegibility;
font-family : "Segoe WP Semibold", "Segoe UI Semibold",
"Segoe UI Web Semibold", "Segoe UI", Ubuntu, "DejaVu Sans", "Helvetica Neue",
Helvetica, Arial, sans-serif;
} }
div#floatingTitle.Unread a.title { div#floatingTitle.Unread a.title {
@ -284,7 +321,6 @@ div#floatingTitle.Unread a.title {
} }
div#floatingTitle img.anchor { div#floatingTitle img.anchor {
margin-right : 1px;
margin-left : 0px; margin-left : 0px;
} }
@ -341,4 +377,24 @@ div#floatingTitle img.hlScorePic {
text-decoration : line-through; text-decoration : line-through;
} }
div.cdmFeedTitle > * {
display : table-cell;
vertical-align : middle;
}
div.cdmFeedTitle a.title {
width : 100%;
}
div.cdmFeedTitle a.catchup {
text-align : right;
color : #555;
padding-right : 10px;
font-size : 11px;
white-space : nowrap;
}
div.cdmFeedTitle a.catchup:hover {
color : #4684ff;
}

409
source/css/dijit.css Normal file
View file

@ -0,0 +1,409 @@
/* Tree */
.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 : green;
}
#filterDlg_Matches span.filterRule {
color : green;
}
.claro .dijitTree .filterRules span.inverse,
#filterDlg_Matches span.filterRule.inverse {
color : red;
}
.claro .dijitTree .labelParam {
float : right;
margin-right : 1em;
}
.claro .dijitTree .dijitTreeLabel.Disabled,
.claro .dijitTree .labelParam.Disabled {
color : #555;
}
.claro .dijitTreeRow.Error {
color : red;
}
.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 : 0px;
opacity : 0.6;
}
.claro .dijitTree .dijitTreeNode {
padding : 0px;
border-width : 0px;
}
.claro .dijitTree .dijitTreeRow {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
.claro .dijitTree .dijitTreeRowSelected {
background : white;
}
.claro .dijitTree .dijitTreeRowHover {
background : #f0f0f0;
border-color : #ddd;
}
.claro .dijitTree .dijitTreeRowSelected {
background : white;
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;
}
/* Toolbar */
.claro .dijitToolbar {
background : #f5f5f5;
border-color : #ddd;
/* text-rendering: optimizelegibility;
font-family : "Segoe WP Semibold", "Segoe UI Semibold",
"Segoe UI Web Semibold", "Segoe UI", "Helvetica Neue",
Helvetica, Arial, sans-serif; */
}
/* .claro .dijitToolbar {
text-shadow : 1px 1px 2px #fff;
} */
.claro .dijitDialog .dijitToolbar {
border : 1px solid #ddd;
}
/* Dialog */
.claro .dijitDialog h2 {
margin-top : 0px;
margin-bottom : 4px;
border-width : 0px;
}
.claro .dijitMenuItemLabel {
font-size : 13px;
}
/* Checkbox */
.claro .dijitCheckBox {
background-image : url("../images/untick.png");
background-color : transparent;
width : 15px;
height : 15px;
margin : 1px;
opacity : 0.7;
background-position : center center;
transition : opacity 0.25s;
-webkit-transition : opacity 0.25s;
/* border : 1px solid #b5bcc7; */
padding : 1px;
}
.claro .dijitCheckBox:hover {
opacity : 1;
}
.claro .dijitCheckBox.dijitCheckBoxDisabled:hover {
opacity : 0.7;
}
.claro .dijitCheckBox.dijitCheckBoxChecked {
border-color : #69C671;
background-image : url("../images/tick.png");
opacity : 1;
}
/* Various buttons */
.claro .dijitButton .dijitButtonNode,
.claro .dijitComboButton .dijitButtonNode,
.claro .dijitToolbar .dijitDropDownButton .dijitButtonNode,
.claro .dijitToolbar .dijitComboButton,
.claro .dijitToolbar .dijitComboButton .dijitButtonNode {
background : none;
border-color : transparent;
box-shadow : none;
}
button,
input[type="submit"] {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size : 14px;
}
button,
input[type="submit"],
.claro .dijitButton,
.claro .dijitComboButton {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
color: #333333;
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, #ffffff, #e6e6e6);
background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
background-repeat: repeat-x;
border: 1px solid #cccccc;
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);
}
button:hover,
button:focus,
button:active,
input[type="submit"]:hover,
input[type="submit"]:focus,
input[type="submit"]:active,
.claro .dijitButton:hover,
.claro .dijitButton:focus,
.claro .dijitButton:active,
.claro .dijitComboButton:hover,
.claro .dijitComboButton:focus,
.claro .dijitComboButton:active,
.claro .dijitButton.dijitButtonDisabled {
color: #333333;
background-color: #e6e6e6;
}
button:active,
input[type="submit"]:active,
.claro .dijitButton:active,
.claro .dijitComboButton:active {
background-color: #cccccc \9;
}
.claro .dijitToolbar .dijitButton,
.claro .dijitToolbar .dijitButton.dijitHover,
.claro .dijitToolbar .dijitComboButton,
.claro .dijitToolbar .dijitComboButton.dijitHover {
background : none;
border-color : transparent;
box-shadow : none;
padding : 0px;
margin : 0px;
line-height : auto;
text-shadow : none;
}
.claro .dijitToolbar .dijitDropDownButton .dijitButtonText,
.claro .dijitToolbar .dijitComboButton .dijitButtonText {
padding : 0px;
}
.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;
}
button:hover,
button:focus,
input[type="submit"]:hover,
input[type="submit"]:focus,
.claro .dijitButton:hover,
.claro .dijitToolbar .dijitButton:hover .dijitButtonNode,
.claro .dijitToolbar .dijitButton.dijitHover .dijitButtonNode,
.claro .dijitButton:focus,
.claro .dijitComboButton:hover,
.claro .dijitComboButton:focus {
color: #333333;
text-decoration: none;
background-position: 0 -15px;
-webkit-transition: background-position 0.1s linear;
transition: background-position 0.1s linear;
}
button:focus,
input[type="submit"]:focus,
.claro .dijitButton:focus,
.claro .dijitComboButton:focus {
outline: thin dotted #333;
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
button:active,
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);
}
input[type="submit"][disabled],
button[disabled],
.claro .dijitButton[disabled],
.claro .dijitButton.dijitButtonDisabled,
.claro .dijitComboButton.dijitButtonDisabled {
cursor: default;
background-image: none;
opacity: 0.65;
filter: alpha(opacity=65);
-webkit-box-shadow: none;
box-shadow: none;
}
.claro .dijitButton .dijitButtonContents,
.claro .dijitComboButton .dijitButtonContents {
font-size : 14px;
font-weight : normal;
line-height : 20px;
}
.claro .dijitButton.small .dijitButtonText {
font-size : 11px;
}
.claro .dijitMenu {
border-color : #ccc;
}
.claro .dijitMenu .dijitMenuItem.dijitHover,
.claro .dijitMenu .dijitMenuItem.dijitFocused,
.claro .dijitMenuTable .dijitMenuItem.dijitHover .dijitMenuItemLabel,
.claro .dijitMenuTable .dijitMenuItem.dijitFocused .dijitMenuItemLabel {
background : #eee;
border-color : transparent;
}
.claro .dijitButton .dijitButtonNode,
.claro .dijitComboButton .dijitButtonNode {
padding : 0px;
}
/* Other stuff */
/* .claro .dijitAccordionTitleFocus {
text-shadow : 1px 1px 2px #fff;
}
.claro .dijitAccordionTitle {
text-rendering: optimizelegibility;
font-family : "Segoe WP Semibold", "Segoe UI Semibold",
"Segoe UI Web Semibold", "Segoe UI", "Helvetica Neue",
Helvetica, Arial, sans-serif;
} */
.claro .dijitAccordionInnerContainer.dijitAccordionInnerContainerSelected {
border-color : #ccc;
}
.claro .dijitAccordionContainer .dijitAccordionChildWrapper {
border-color : #ddd;
}
/* Tabs */
.claro .dijitTabContent {
background : #eee;
}
.claro .dijitTabContent.dijitTabChecked,
.claro .dijitTabContent.dijitTabHover,
.claro .dijitTabContent.dijitFocused {
background : white;
}
.claro .dijitTabPaneWrapper,
.claro .dijitTabContainerTop-tabs,
.claro .dijitTab,
.claro .dijitAccordionInnerContainer {
border-color : #ddd;
}

View file

@ -1,3 +1,14 @@
body#ttrssPrefs {
background-color : #f5f5f5;
}
body#ttrssPrefs #footer, body#ttrssPrefs #header {
background-color : #f5f5f5;
padding-left : 8px;
padding-right : 8px;
}
#header a:hover { #header a:hover {
color : black; color : black;
} }
@ -13,12 +24,12 @@ div#pref-tabs .dijitContentPane {
} }
div#pref-tabs { div#pref-tabs {
box-shadow : 0px 1px 1px -1px rgba(0,0,0,0.1);
margin : 0px 5px 0px 5px; margin : 0px 5px 0px 5px;
} }
div#pref-tabs .dijitContentPane h3 { div#pref-tabs .dijitContentPane h3 {
font-size : 14px; font-size : 14px;
font-weight : bold;
} }
#pref-filter-wrap, #pref-filter-header, #pref-filter-content, #pref-filter-wrap, #pref-filter-header, #pref-filter-content,
@ -52,11 +63,10 @@ div.prefProfileHolder, div.prefFeedOPMLHolder, div.inactiveFeedHolder {
height : 300px; height : 300px;
overflow : auto; overflow : auto;
border-width : 0px 1px 1px 1px; border-width : 0px 1px 1px 1px;
border-color : #c0c0c0; border-color : #ddd;
border-style : solid; border-style : solid;
margin : 0px 0px 5px 0px; margin : 0px 0px 5px 0px;
background-color : #ecf4ff; background-color : white;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
} }
div.filterTestHolder, div.prefFeedOPMLHolder { div.filterTestHolder, div.prefFeedOPMLHolder {
border-width : 1px; border-width : 1px;
@ -66,12 +76,10 @@ ul.selfUpdateList, ul.userFeedList {
height : 200px; height : 200px;
overflow : auto; overflow : auto;
list-style-type : none; list-style-type : none;
border : 1px solid #c0c0c0; border : 1px solid #ddd;
background-color : #ecf4ff; background-color : #f5f5f5;
margin : 0px 0px 5px 0px; margin : 0px 0px 5px 0px;
padding : 5px; padding : 5px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
border-radius : 4px;
} }
div#feedlistLoading, div#filterlistLoading, div#labellistLoading { div#feedlistLoading, div#filterlistLoading, div#labellistLoading {
@ -90,7 +98,8 @@ div#feedlistLoading img, div#filterlistLoading img, div#labellistLoading {
a.bookmarklet { a.bookmarklet {
color : #4684ff; color : #4684ff;
border : 1px solid #ecf4ff; border : 1px solid #ddd;
background : #f5f5f5;
padding : 2px; padding : 2px;
} }
@ -120,7 +129,9 @@ table.prefErrorLog td.filename, table.prefErrorLog td.login, table.prefErrorLog
color : #555; color : #555;
} }
.dijitAccordionContainer-child { body#ttrssPrefs hr {
box-shadow : inset 0px 0px 3px rgba(0,0,0,0.2); border-color : #ecf4ff;
max-width : 100%;
} }

View file

@ -7,34 +7,28 @@ body#ttrssMain, body#ttrssPrefs, body#ttrssLogin, body {
font-size: 14px; font-size: 14px;
} }
body#ttrssPrefs {
background-color : #ecf4ff;
}
body#ttrssPrefs #footer, body#ttrssPrefs #header {
background-color : #ecf4ff;
padding-left : 8px;
padding-right : 8px;
}
div.postReply { div.postReply {
padding : 0px; padding : 0px;
} }
div.postReply div.postHeader { div.postReply div.postHeader {
border-width : 0px 0px 1px 0px;
border-style : solid;
border-color : #c0c0c0;
background : #fafafa;
box-shadow : 0px 0px 3px 0px rgba(0,0,0,0.1);
padding : 5px; padding : 5px;
margin-right : 4px;
color : #909090; color : #909090;
border-width : 0px 0px 1px 0px;
border-color : #ddd;
border-style : solid;
} }
div.postReply div.postTitle { div.postReply div.postTitle {
overflow : hidden; overflow : hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space : nowrap; white-space : nowrap;
font-weight : 600;
text-rendering: optimizelegibility;
font-family : "Segoe WP Semibold", "Segoe UI Semibold",
"Segoe UI Web Semibold", "Segoe UI", Ubuntu, "DejaVu Sans", "Helvetica Neue",
Helvetica, Arial, sans-serif;
} }
div.postReply div.postDate { div.postReply div.postDate {
@ -66,13 +60,10 @@ div.postReply img.tagsPic {
div.articleNote { div.articleNote {
background-color : #fff7d5; background-color : #fff7d5;
padding : 5px; padding : 5px;
border-radius : 4px;
margin : 5px; margin : 5px;
border-style : solid; border-style : solid;
border-color : #e7d796; border-color : #e7d796;
border-width : 1px; border-width : 1px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
background-color : #fff7d5;
color : #9a8c59; color : #9a8c59;
} }
@ -87,29 +78,50 @@ div.postReply span.author {
h1 { h1 {
font-size : 18px; font-size : 18px;
font-weight : 600;
text-rendering: optimizelegibility;
font-family : "Segoe WP Semibold", "Segoe UI Semibold",
"Segoe UI Web Semibold", "Segoe UI", Ubuntu, "DejaVu Sans", "Helvetica Neue",
Helvetica, Arial, sans-serif;
} }
h2 { h2 {
font-size : 16px; font-size : 16px;
font-weight : bold; font-weight : 600;
border-width : 0px 0px 1px 0px; border-width : 0px 0px 1px 0px;
border-style : solid; border-style : solid;
border-color : #ecf4ff; border-color : #ecf4ff;
text-rendering: optimizelegibility;
font-family : "Segoe WP Semibold", "Segoe UI Semibold",
"Segoe UI Web Semibold", "Segoe UI", Ubuntu, "DejaVu Sans", "Helvetica Neue",
Helvetica, Arial, sans-serif;
} }
h3 { h3 {
font-size : 12px; font-size : 13px;
font-weight : bold;
border-width : 0px 0px 1px 0px; border-width : 0px 0px 1px 0px;
border-style : solid; border-style : solid;
border-color : #ecf4ff; border-color : #ecf4ff;
font-weight : 600;
text-rendering: optimizelegibility;
font-family : "Segoe WP Semibold", "Segoe UI Semibold",
"Segoe UI Web Semibold", "Segoe UI", Ubuntu, "DejaVu Sans", "Helvetica Neue",
Helvetica, Arial, sans-serif;
}
h4 {
font-size : 14px;
font-weight : 600;
text-rendering: optimizelegibility;
font-family : "Segoe WP Semibold", "Segoe UI Semibold",
"Segoe UI Web Semibold", "Segoe UI", Ubuntu, "DejaVu Sans", "Helvetica Neue",
Helvetica, Arial, sans-serif;
} }
hr { hr {
border-width : 0px 0px 1px 0px; border-width : 0px 0px 1px 0px;
border-style : solid; border-style : solid;
border-color : #c0c0c0; border-color : #ccc;
max-width : 90%;
} }
a { a {
@ -138,7 +150,6 @@ a:hover {
min-width : 100px; min-width : 100px;
padding : 5px; padding : 5px;
-width : 200px; -width : 200px;
box-shadow : 0px 0px 2px rgba(0,0,0,0.2);
} }
#notify img { #notify img {
@ -181,7 +192,11 @@ a:hover {
} }
.hl div.hlTitle a { .hl div.hlTitle a {
font-weight : bold; font-weight : 600;
text-rendering: optimizelegibility;
font-family : "Segoe WP Semibold", "Segoe UI Semibold",
"Segoe UI Web Semibold", "Segoe UI", Ubuntu, "DejaVu Sans", "Helvetica Neue",
Helvetica, Arial, sans-serif;
color : #777; color : #777;
} }
@ -189,13 +204,9 @@ a:hover {
color : black; color : black;
} }
.hl.active {
box-shadow : inset 0px 0px 3px 0px rgba(0,0,0,0.1);
}
.hl.active div.hlTitle a { .hl.active div.hlTitle a {
color : #4684ff; color : #4684ff;
text-shadow : 1px 1px 2px #fff; /* text-shadow : 1px 1px 2px #fff; */
} }
.hl.Selected { .hl.Selected {
@ -206,18 +217,11 @@ a:hover {
color : #909090; color : #909090;
} }
/* #headlines-frame div.hl:nth-child(even) {
background : #fafafa;
} */
#headlines-frame.normal {
}
.hl { .hl {
border-width : 0px 0px 1px 0px; border-width : 0px 0px 1px 0px;
border-style : solid; border-style : solid;
border-color : #c0c0c0; border-color : #ddd;
padding : 1px;
} }
.hl.active { .hl.active {
@ -227,13 +231,11 @@ a:hover {
div.filterTestHolder { div.filterTestHolder {
height : 300px; height : 300px;
overflow : auto; overflow : auto;
border-color : #c0c0c0; border-color : #ddd;
border-style : solid; border-style : solid;
margin : 0px 0px 5px 0px; margin : 0px 0px 5px 0px;
background-color : #ecf4ff; background-color : #f5f5f5;
border-width : 1px; border-width : 1px;
border-radius : 4px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
} }
@ -242,7 +244,7 @@ div.filterTestHolder {
color : #555; color : #555;
padding-left : 10px; padding-left : 10px;
border-width : 0px 0px 0px 4px; border-width : 0px 0px 0px 4px;
border-color : #c0c0c0; border-color : #ccc;
border-style : solid; border-style : solid;
} }
@ -259,9 +261,9 @@ div.filterTestHolder {
font-family : monospace; font-family : monospace;
font-size : 12px; font-size : 12px;
border-width : 0px; border-width : 0px;
border-color : #c0c0c0; border-color : #ccc;
border-style : solid; border-style : solid;
background : #fafafa; background : #f5f5f5;
display : block; display : block;
max-width : 98%; max-width : 98%;
overflow : auto; overflow : auto;
@ -274,9 +276,7 @@ div.notice, div.warning, div.error {
font-size : 12px; font-size : 12px;
border-style : solid; border-style : solid;
border-color : #ccc; border-color : #ccc;
border-radius : 4px;
border-width : 1px; border-width : 1px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
} }
div.notice div.inner, div.warning div.inner, div.error div.inner { div.notice div.inner, div.warning div.inner, div.error div.inner {
@ -324,47 +324,66 @@ div.prefHelp {
color : #555; color : #555;
} }
div#headlines-toolbar { #main-toolbar > * {
border-width : 0px 0px 1px 0px; white-space : nowrap;
background-color : #fcfcfc; display : table-cell;
border-color : #c0c0c0; color : #999;
}
#main-toolbar > *,
#main-toolbar table *,
#main-toolbar .actionChooser * {
text-rendering: optimizelegibility;
font-family : "Segoe WP", "Segoe UI Web", "Segoe UI", Ubuntu, "DejaVu Sans", "Helvetica Neue",
Helvetica, Arial, sans-serif;
font-size : 12px; font-size : 12px;
font-family : "Segoe UI", Tahoma, sans-serif;
color : #555;
padding : 0px;
margin : 0px;
overflow : hidden;
height : 25px;
line-height : 25px;
padding-left : 4px;
} }
div#headlines-toolbar .dijitSelect { #main-toolbar #headlines-toolbar {
font-size : 11px; padding-right : 4px;
position : relative; width : 100%;
top : -2px;
} }
div#headlines-toolbar span.r { #main-toolbar #headlines-toolbar span.holder {
float: right; display : table;
position: relative; width : 100%;
padding : 0 4px 0px 4px; }
#main-toolbar #headlines-toolbar span.holder > * {
display : table-cell;
}
#main-toolbar #headlines-toolbar .main {
text-align : right; text-align : right;
} }
#main-toolbar #headlines-toolbar .main,
#main-toolbar #headlines-toolbar .r {
line-height : 24px;
}
#headlines-toolbar span.r img {
margin-right : 4px;
position : relative;
top : 3px;
}
#headlines-toolbar span.r .error a { #headlines-toolbar span.r .error a {
color : red; color : red;
} }
div#headlines-toolbar span.r a { #main-toolbar #selected_prompt {
color : #555; font-style : italic;
text-align : right;
margin-right : 4px;
} }
span.contentPreview { span.contentPreview {
color : #999; color : #999;
font-weight : normal; font-weight : normal;
text-shadow : 1px 1px 2px #fff; font-size : 12px;
font-size : 11px; padding-left : 4px;
} }
span.hlLabelRef { span.hlLabelRef {
@ -397,63 +416,50 @@ div.postHeader div {
#allEntryTags { #allEntryTags {
border-width : 0px 0px 1px 0px; border-width : 0px 0px 1px 0px;
border-style : solid; border-style : solid;
border-color : #c0c0c0; border-color : #ddd;
padding-bottom : 5px; padding-bottom : 5px;
display : none; display : none;
} }
a.hlFeed {
display : block;
white-space : nowrap;
font-size : 9px;
font-style : italic;
font-weight : normal;
border-radius : 4px;
display : inline-block;
padding : 1px 2px 1px 2px;
margin-bottom : 2px;
margin-top : 2px;
color : #555;
}
a.hlFeed:hover {
color : #4684ff;
}
img.markedPic, img.pubPic { img.markedPic, img.pubPic {
cursor : pointer; cursor : pointer;
vertical-align : middle; vertical-align : middle;
opacity : 0.5;
-webkit-transition : opacity 0.25s;
transition : opacity 0.25s;
}
img.markedPic:hover, img.pubPic:hover {
opacity : 1;
}
img[src*='pub_set.png'], img[src*='mark_set.png'] {
opacity : 1;
} }
div.tagCloudContainer { div.tagCloudContainer {
border : 1px solid #c0c0c0; border : 1px solid #ddd;
background-color : #ecf4ff; background-color : #f5f5f5;
margin : 5px 0px 5px 0px; margin : 5px 0px 5px 0px;
padding : 5px; padding : 5px;
text-align : center; text-align : center;
border-radius : 4px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
} }
div.errorExplained { div.errorExplained {
border : 1px solid #c0c0c0; border : 1px solid #ddd;
background-color : #ecf4ff; background-color : #f5f5f5;
margin : 5px 0px 5px 0px; margin : 5px 0px 5px 0px;
padding : 5px; padding : 5px;
border-radius : 4px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
} }
ul.feedErrorsList { ul.feedErrorsList {
max-height : 300px; max-height : 300px;
overflow : auto; overflow : auto;
list-style-type : none; list-style-type : none;
border : 1px solid #c0c0c0; border : 1px solid #ddd;
background-color : #ecf4ff; background-color : #f5f5f5;
margin : 0px 0px 5px 0px; margin : 0px 0px 5px 0px;
padding : 5px; padding : 5px;
border-radius : 4px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
} }
ul.feedErrorsList em { ul.feedErrorsList em {
@ -464,7 +470,7 @@ ul.browseFeedList {
height : 300px; height : 300px;
overflow : auto; overflow : auto;
border-width : 0px 1px 1px 1px; border-width : 0px 1px 1px 1px;
border-color : #c0c0c0; border-color : #ddd;
border-style : solid; border-style : solid;
margin : 0px 0px 5px 0px; margin : 0px 0px 5px 0px;
background-color : white; background-color : white;
@ -528,14 +534,6 @@ form {
padding : 0px; padding : 0px;
} }
#main_toolbar_form {
margin : 0px;
padding : 0px;
display : table-cell;
white-space : nowrap;
width : 100%;
}
div.loadingPrompt { div.loadingPrompt {
padding : 1em; padding : 1em;
text-align : center; text-align : center;
@ -545,20 +543,11 @@ div.loadingPrompt {
div.whiteBox { div.whiteBox {
margin-left : 1px; margin-left : 1px;
text-align : center; text-align : center;
padding : 1em; padding : 1em 1em 0px 1em;
} font-size : 11px;
border-width : 0px 0px 1px 0px;
/* html, body#ttrssMain, #main { border-style : solid;
width: 100%; border-color : #ddd;
height: 100%;
padding: 0;
margin: 0;
} */
#toolbar div.actionChooser {
display : table-cell;
text-align : right;
padding-right : 3px;
} }
div.autocomplete { div.autocomplete {
@ -589,11 +578,13 @@ div.autocomplete ul li {
cursor : pointer; cursor : pointer;
} }
.hlTitle { .hl .hlTitle {
overflow : hidden; overflow : hidden;
white-space : nowrap; white-space : nowrap;
max-width : 500px; max-width : 500px;
text-overflow : ellipsis; text-overflow : ellipsis;
padding-left : 6px;
padding-right : 6px;
} }
div#headlines-frame.wide .hlTitle { div#headlines-frame.wide .hlTitle {
@ -602,6 +593,10 @@ div#headlines-frame.wide .hlTitle {
white-space : normal; white-space : normal;
} }
div#headlines-frame.wide .hl .hlFeed {
display : none;
}
.hl a.title.high, span.hlContent.high .contentPreview { .hl a.title.high, span.hlContent.high .contentPreview {
color : #00aa00; color : #00aa00;
} }
@ -667,7 +662,6 @@ span.labelColorIndicator {
background-color : #fff7d5; background-color : #fff7d5;
color : #063064; color : #063064;
text-align : center; text-align : center;
box-shadow : 0px 0px 1px 0px rgba(0,0,0,0.1);
} }
div#cmdline { div#cmdline {
@ -716,14 +710,39 @@ div.hlRight img {
max-height : 16px; max-height : 16px;
} }
span.hlUpdated { .hl span.hlFeed {
color : #555;
min-width : 100px;
display : table-cell; display : table-cell;
width : 100%;
vertical-align : middle; vertical-align : middle;
text-align : right; text-align : right;
font-size : 10px; }
.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 : #4684ff;
}
.hl span.hlUpdated {
color : #555;
display : table-cell;
vertical-align : middle;
text-align : right;
font-size : 11px;
white-space : nowrap;
padding-left : 10px;
}
span.hlUpdated div {
display : inline-block;
} }
div.hlLeft { div.hlLeft {
@ -771,21 +790,19 @@ div.fatalError textarea {
#content-wrap { #content-wrap {
padding : 0px; padding : 0px;
border-width : 0px 0px 0px 1px; border-width : 0px;
border-style : solid;
border-color : #c0c0c0;
margin : 0px; margin : 0px;
} }
#feeds-holder { #feeds-holder {
padding : 0px; padding : 0px;
border-color : #c0c0c0; border-width : 0px 1px 0px 0px;
border-left-width : 0px; border-style : solid;
border-bottom-width : 0px; border-color : #ddd;
border-top-width : 0px;
overflow : hidden; overflow : hidden;
box-shadow : inset 0px 0px 3px rgba(0,0,0,0.1); background : #f5f5f5;
background : #f9fbff; box-shadow : inset -1px 0px 2px -1px rgba(0,0,0,0.1);
-webkit-overflow-scrolling : touch;
} }
#headlines-wrap-inner { #headlines-wrap-inner {
@ -796,11 +813,10 @@ div.fatalError textarea {
#headlines-frame { #headlines-frame {
padding : 0px; padding : 0px;
border-color : #c0c0c0;
border-style : solid;
border-width : 0px; border-width : 0px;
border-color : #ddd;
margin-top : 0px; margin-top : 0px;
box-shadow : inset 0px 0px 3px rgba(0,0,0,0.1); -webkit-overflow-scrolling : touch;
} }
#headlines-toolbar_splitter, #toolbar_splitter { #headlines-toolbar_splitter, #toolbar_splitter {
@ -813,7 +829,16 @@ div.fatalError textarea {
border-width : 0px; border-width : 0px;
white-space: nowrap; white-space: nowrap;
font-size : 12px; font-size : 12px;
box-shadow : 0px 0px 2px rgba(0,0,0,0.1); }
#main-toolbar {
background : white;
border-width : 0px 0px 1px 0px;
border-color : #ddd;
border-style : solid;
padding-left : 4px;
height : 26px;
} }
#header { #header {
@ -837,12 +862,12 @@ div.fatalError textarea {
#content-insert { #content-insert {
padding : 0px; padding : 0px;
border-color : #c0c0c0; border-color : #ddd;
border-bottom-width : 0px; border-width : 0px;
border-right-width : 0px; line-height: 1.5;
border-left-width : 0px; font-size : 15px;
line-height: 20px;
overflow : auto; overflow : auto;
-webkit-overflow-scrolling : touch;
} }
#feedTree .dijitTreeRow .dijitTreeLabel.Unread { #feedTree .dijitTreeRow .dijitTreeLabel.Unread {
@ -853,33 +878,6 @@ div.fatalError textarea {
color : red; color : red;
} }
.dijitTreeLabel {
outline : 0;
}
.feedParam {
color : #555;
float : right;
margin-right : 1em;
}
.labelParam {
float : right;
margin-right : 1em;
}
.dijitTreeLabel.Disabled, .labelParam.Disabled {
color : #555;
}
.dijitTreeRow.Error {
color : red;
}
.dijitTreeRow.Hidden {
display : none;
}
img.feedIcon, img.tinyFeedIcon { img.feedIcon, img.tinyFeedIcon {
width : 16px; width : 16px;
height : 16px; height : 16px;
@ -888,24 +886,6 @@ img.feedIcon, img.tinyFeedIcon {
display : inline-block; display : inline-block;
} }
.dijitToolbar {
text-shadow : 1px 1px 2px #fff;
}
.dijitAccordionTitleFocus {
text-shadow : 1px 1px 2px #fff;
}
.dijitDialog .dijitToolbar {
border : 1px solid #c0c0c0;
}
.dijitDialog h2 {
margin-top : 0px;
margin-bottom : 4px;
border-width : 0px;
}
.player { .player {
display : inline-block; display : inline-block;
color : #555; color : #555;
@ -933,8 +913,19 @@ img.feedIcon, img.tinyFeedIcon {
height : 100%; height : 100%;
margin-left : 1px; margin-left : 1px;
text-align : center; text-align : center;
padding : 1em;
color : #555; 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 : #88b0f0;
} }
ul#filterDlg_Matches, ul#filterDlg_Actions { ul#filterDlg_Matches, ul#filterDlg_Actions {
@ -942,12 +933,11 @@ ul#filterDlg_Matches, ul#filterDlg_Actions {
overflow : auto; overflow : auto;
list-style-type : none; list-style-type : none;
border-style : solid; border-style : solid;
border-color : #c0c0c0; border-color : #ddd;
border-width : 0px 1px 1px 1px; border-width : 0px 1px 1px 1px;
background-color : #ecf4ff; background-color : white;
margin : 0px 0px 5px 0px; margin : 0px 0px 5px 0px;
padding : 0px; padding : 0px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
} }
ul#filterDlg_Matches li, ul#filterDlg_Actions li { ul#filterDlg_Matches li, ul#filterDlg_Actions li {
@ -955,20 +945,14 @@ ul#filterDlg_Matches li, ul#filterDlg_Actions li {
padding : 0px 0px 0px 5px; padding : 0px 0px 0px 5px;
} }
ul#filterDlg_Matches li div.dijitCheckBox, ul#filterDlg_Actions li div.dijitCheckBox {
margin-right : 5px;
}
ul.helpKbList { ul.helpKbList {
max-height : 300px; max-height : 300px;
overflow : auto; overflow : auto;
list-style-type : none; list-style-type : none;
border : 1px solid #c0c0c0; border : 1px solid #ddd;
background-color : #ecf4ff; background-color : #f5f5f5;
margin : 0px 0px 5px 0px; margin : 0px 0px 5px 0px;
padding : 5px; padding : 5px;
border-radius : 4px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
} }
ul.helpKbList span.hksequence { ul.helpKbList span.hksequence {
@ -983,18 +967,22 @@ ul.helpKbList h2 {
margin-top : 0px; margin-top : 0px;
} }
.dijitTreeNode .loadingNode {
margin-left : 3px;
height : 9px;
}
span.collapseBtn { span.collapseBtn {
cursor : pointer; cursor : pointer;
} }
div.postContent h1 {
font-size : 16px;
}
div.postContent h2,
div.postContent h3,
div.postContent h4 {
font-size : 15px;
}
div.postContent p { div.postContent p {
max-width : 650px; max-width : 650px;
text-align : justify;
-webkit-hyphens: auto; -webkit-hyphens: auto;
-moz-hyphens: auto; -moz-hyphens: auto;
hyphens: auto; hyphens: auto;
@ -1010,90 +998,12 @@ div.postHeader span.author {
font-weight : normal; font-weight : normal;
} }
body#ttrssZoom {
margin-left : auto;
margin-right : auto;
padding : 20px;
max-width : 670px;
background : #f9fbff;
}
body#ttrssZoom div.postHeader div.postFeedTitle {
float : left;
text-align : right;
padding-left : 0px;
font-size : 10px;
}
body#ttrssZoom div.postHeader a.postComments {
text-align : right;
padding-left : 0px;
font-size : 10px;
}
body#ttrssZoom div.postHeader div.postDate {
float : none;
text-align : right;
padding-left : 0px;
color : #777;
font-size : 10px;
}
body#ttrssZoom div.postHeader div.postTags {
color : #777;
font-size : 10px;
}
body#ttrssZoom div.postHeader div.postTitle {
white-space : normal;
}
body#ttrssZoom div.postContent p {
max-width : 650px;
text-align : justify;
-webkit-hyphens: auto;
-moz-hyphens: auto;
hyphens: auto;
}
body#ttrssZoom div.postHeader {
margin : 10px;
border : 1px solid #ccc;
box-shadow : none;
border-radius : 4px;
}
body#ttrssZoom div.postReply {
border : 1px solid #ccc;
border-radius : 4px;
box-shadow : inset 0px 0px 3px rgba(0,0,0,0.1);
background : white;
}
body#ttrssZoom div.postContent {
}
body#ttrssZoom div.footer {
margin-top : 1em;
text-align : center;
}
body#ttrssZoom div.postContent img {
max-width : 650px;
height : auto;
}
select.attachments { select.attachments {
display : block; display : block;
margin-top : 10px; margin-top : 10px;
max-width : 120px; max-width : 120px;
} }
div.hl.active {
border-color : #88b0f0;
}
#selected_prompt { #selected_prompt {
margin-right : 25px; margin-right : 25px;
} }
@ -1107,46 +1017,28 @@ body#ttrssMain #feedTree .dijitTreeRow {
padding : 2px 0px 2px; padding : 2px 0px 2px;
height : 22px; height : 22px;
border-width : 1px; border-width : 1px;
border-color : transparent;
color : #333; color : #333;
} }
body#ttrssMain #feedTree .dijitFolderClosed, ul#filterDlg_Matches li div.dijitCheckBox, ul#filterDlg_Actions li div.dijitCheckBox {
body#ttrssMain #feedTree .dijitFolderOpened { margin-right : 5px;
display : none;
}
.dijitTreeNode .dijitCheckBox {
margin-left : 4px;
}
body#ttrssMain #feedTree .dijitTreeIsRoot > .dijitTreeRow > .dijitTreeExpando {
margin-left : 5px;
}
body#ttrssMain #feedTree .dijitTreeExpando {
margin-top : 0px;
opacity : 0.6;
}
body#ttrssMain #feedTree .dijitTreeNode {
padding : 0px;
border-width : 0px;
} }
body#ttrssMain #feedTree { body#ttrssMain #feedTree {
height : 100%; height : 100%;
overflow-x : hidden; overflow-x : hidden;
font-family : "Segoe UI", Tahoma, sans-serif; text-rendering: optimizelegibility;
font-family : "Segoe UI Web", "Segoe UI", Ubuntu, "DejaVu Sans", "Helvetica Neue",
Helvetica, Arial, sans-serif;
} }
#feedTree .counterNode.aux { body#ttrssMain #feedTree .counterNode.aux {
background : #f0f0f0; background : #f0f0f0;
color : #999; color : #999;
border-color : #f0f0f0; border-color : #f0f0f0;
} }
#feedTree .counterNode { body#ttrssMain #feedTree .counterNode {
font-weight : bold; font-weight : bold;
display : inline-block; display : inline-block;
font-size : 9px; font-size : 9px;
@ -1159,31 +1051,12 @@ body#ttrssMain #feedTree {
float : right; float : right;
position : relative; position : relative;
line-height : 14px; line-height : 14px;
margin-right : 4px; margin-right : 8px;
margin-top : 3px; margin-top : 3px;
min-width : 23px; min-width : 23px;
height : 14px; height : 14px;
} }
#feedTree .dijitTreeRow {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
body#ttrssPrefs hr {
border-color : #ecf4ff;
max-width : 100%;
}
.dijitMenuItemLabel {
font-size : 13px;
}
.dijitTreeRowSelected .dijitTreeLabel {
text-shadow : 1px 1px 2px #fff;
}
#feedTree img[src*='indicator_white.gif'] { #feedTree img[src*='indicator_white.gif'] {
position : relative; position : relative;
top : -2px; top : -2px;
@ -1198,3 +1071,23 @@ div.enclosure_title {
} }
body#ttrssMain #headlines-frame .dijitCheckBox {
border-width : 0px;
opacity : 0.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;
}
:focus {
outline: none;
}

View file

@ -1,5 +1,5 @@
body { body {
background : #f9fbff; background : #f5f5f5;
color : black; color : black;
padding : 0px; padding : 0px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
@ -15,11 +15,11 @@ form {
} }
div.content { div.content {
overflow : hidden;
background : white; background : white;
border : 1px solid #ccc; border : 1px solid #ddd;
padding : 10px; padding : 10px;
border-radius : 4px; box-shadow : 0px 1px 1px -1px rgba(0,0,0,0.1);
box-shadow : inset 0 0 3px rgba(0,0,0,0.1);
} }
p.warning { p.warning {
@ -40,9 +40,7 @@ div.insensitive-small {
} }
.floatingLogo { .floatingLogo {
float : right; display : none;
position : relative;
top : -10px;
} }
a { a {
@ -61,7 +59,6 @@ div.notice, div.warning, div.error {
font-size : 12px; font-size : 12px;
border-style : solid; border-style : solid;
border-color : #ccc; border-color : #ccc;
border-radius : 4px;
border-width : 1px; border-width : 1px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1); box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
} }

105
source/css/zoom.css Normal file
View file

@ -0,0 +1,105 @@
body#ttrssZoom {
margin-left : auto;
margin-right : auto;
padding : 20px;
max-width : 670px;
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 {
max-width : 650px;
-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);
}
body#ttrssZoom div.footer {
margin-top : 1em;
text-align : center;
}
body#ttrssZoom div.postContent img {
max-width : 630px;
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;
}

0
source/feed-icons/.empty Executable file
View file

View file

View file

@ -41,8 +41,6 @@
header("Content-type: image/png"); header("Content-type: image/png");
$stamp = gmdate("D, d M Y H:i:s", filemtime($filename)). " GMT"; $stamp = gmdate("D, d M Y H:i:s", filemtime($filename)). " GMT";
header("Last-Modified: $stamp", true); header("Last-Modified: $stamp", true);
ob_clean(); // discard any data in the output buffer (if possible)
flush(); // flush headers (if possible)
readfile($filename); readfile($filename);
} }
} else { } else {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 610 B

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 718 B

After

Width:  |  Height:  |  Size: 717 B

BIN
source/images/tick.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

BIN
source/images/untick.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

View file

@ -92,7 +92,7 @@
} }
function ccache_update($feed_id, $owner_uid, $is_cat = false, function ccache_update($feed_id, $owner_uid, $is_cat = false,
$update_pcat = true) { $update_pcat = true, $pcat_fast = false) {
if (!is_numeric($feed_id)) return; if (!is_numeric($feed_id)) return;
@ -127,11 +127,13 @@
/* Recalculate counters for child feeds */ /* Recalculate counters for child feeds */
$result = db_query("SELECT id FROM ttrss_feeds if (!$pcat_fast) {
$result = db_query("SELECT id FROM ttrss_feeds
WHERE owner_uid = '$owner_uid' AND $cat_qpart"); WHERE owner_uid = '$owner_uid' AND $cat_qpart");
while ($line = db_fetch_assoc($result)) { while ($line = db_fetch_assoc($result)) {
ccache_update($line["id"], $owner_uid, false, false); ccache_update($line["id"], $owner_uid, false, false);
}
} }
$result = db_query("SELECT SUM(value) AS sv $result = db_query("SELECT SUM(value) AS sv
@ -177,7 +179,7 @@
$cat_id = (int) db_fetch_result($result, 0, "cat_id"); $cat_id = (int) db_fetch_result($result, 0, "cat_id");
ccache_update($cat_id, $owner_uid, true); ccache_update($cat_id, $owner_uid, true, true, true);
} }
} }

View file

@ -127,8 +127,6 @@
ORDER BY ttrss_feed_categories.title, ttrss_feeds.title, score DESC, date_updated DESC ORDER BY ttrss_feed_categories.title, ttrss_feeds.title, score DESC, date_updated DESC
LIMIT $limit"); LIMIT $limit");
$cur_feed_title = "";
$headlines_count = db_num_rows($result); $headlines_count = db_num_rows($result);
$headlines = array(); $headlines = array();

View file

@ -63,7 +63,7 @@
htmlspecialchars($line["title"])."</span></a>"; htmlspecialchars($line["title"])."</span></a>";
$feed_url = "<a target=\"_blank\" class=\"fb_feedUrl\" $feed_url = "<a target=\"_blank\" class=\"fb_feedUrl\"
href=\"$feed_url\"><img src='images/pub_set.svg' href=\"$feed_url\"><img src='images/pub_set.png'
style='vertical-align : middle'></a>"; style='vertical-align : middle'></a>";
$rv .= "<li>$check_box $feed_url $site_url". $rv .= "<li>$check_box $feed_url $site_url".
@ -72,7 +72,6 @@
} else if ($mode == 2) { } else if ($mode == 2) {
$feed_url = htmlspecialchars($line["feed_url"]); $feed_url = htmlspecialchars($line["feed_url"]);
$site_url = htmlspecialchars($line["site_url"]); $site_url = htmlspecialchars($line["site_url"]);
$title = htmlspecialchars($line["title"]);
$check_box = "<input onclick='toggleSelectListRow2(this)' dojoType=\"dijit.form.CheckBox\" $check_box = "<input onclick='toggleSelectListRow2(this)' dojoType=\"dijit.form.CheckBox\"
type=\"checkbox\">"; type=\"checkbox\">";
@ -92,7 +91,7 @@
htmlspecialchars($line["title"])."</span></a>"; htmlspecialchars($line["title"])."</span></a>";
$feed_url = "<a target=\"_blank\" class=\"fb_feedUrl\" $feed_url = "<a target=\"_blank\" class=\"fb_feedUrl\"
href=\"$feed_url\"><img src='images/pub_set.svg' href=\"$feed_url\"><img src='images/pub_set.png'
style='vertical-align : middle'></a>"; style='vertical-align : middle'></a>";

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -109,7 +109,7 @@
if (!$label_id) return; if (!$label_id) return;
$result = db_query( db_query(
"DELETE FROM ttrss_user_labels2 "DELETE FROM ttrss_user_labels2
WHERE WHERE
label_id = '$label_id' AND label_id = '$label_id' AND

View file

@ -2,15 +2,22 @@
<html> <html>
<head> <head>
<title>Tiny Tiny RSS : Login</title> <title>Tiny Tiny RSS : Login</title>
<link rel="stylesheet" type="text/css" href="lib/dijit/themes/claro/claro.css"/> <?php echo stylesheet_tag("lib/dijit/themes/claro/claro.css") ?>
<link rel="stylesheet" type="text/css" href="css/tt-rss.css"> <?php echo stylesheet_tag("css/tt-rss.css") ?>
<?php echo stylesheet_tag("css/dijit.css") ?>
<link rel="shortcut icon" type="image/png" href="images/favicon.png"> <link rel="shortcut icon" type="image/png" href="images/favicon.png">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="lib/dojo/dojo.js"></script> <?php
<script type="text/javascript" src="lib/dojo/tt-rss-layer.js"></script> foreach (array("lib/prototype.js",
<script type="text/javascript" src="lib/prototype.js"></script> "lib/dojo/dojo.js",
<script type="text/javascript" src="js/functions.js"></script> "lib/dojo/tt-rss-layer.js",
<script type="text/javascript" charset="utf-8" src="errors.php?mode=js"></script> "js/functions.js",
"errors.php?mode=js") as $jsfile) {
echo javascript_tag($jsfile);
} ?>
<script type="text/javascript"> <script type="text/javascript">
require({cache:{}}); require({cache:{}});
Event.observe(window, 'load', function() { Event.observe(window, 'load', function() {

View file

@ -2,6 +2,19 @@
define_default('DAEMON_UPDATE_LOGIN_LIMIT', 30); define_default('DAEMON_UPDATE_LOGIN_LIMIT', 30);
define_default('DAEMON_FEED_LIMIT', 500); define_default('DAEMON_FEED_LIMIT', 500);
define_default('DAEMON_SLEEP_INTERVAL', 120); define_default('DAEMON_SLEEP_INTERVAL', 120);
define_default('_MIN_CACHE_IMAGE_SIZE', 1024);
function calculate_article_hash($article, $pluginhost) {
$tmp = "";
foreach ($article as $k => $v) {
if ($k != "feed" && isset($v)) {
$tmp .= sha1("$k:" . (is_array($v) ? implode(",", $v) : $v));
}
}
return sha1(implode(",", $pluginhost->get_plugin_names()) . $tmp);
}
function update_feedbrowser_cache() { function update_feedbrowser_cache() {
@ -79,7 +92,7 @@
$login_thresh_qpart = ""; $login_thresh_qpart = "";
} }
// Test if the feed need a update (update interval exceded). // Test if the feed need a update (update interval exceeded).
if (DB_TYPE == "pgsql") { if (DB_TYPE == "pgsql") {
$update_limit_qpart = "AND (( $update_limit_qpart = "AND ((
ttrss_feeds.update_interval = 0 ttrss_feeds.update_interval = 0
@ -88,8 +101,10 @@
) OR ( ) OR (
ttrss_feeds.update_interval > 0 ttrss_feeds.update_interval > 0
AND ttrss_feeds.last_updated < NOW() - CAST((ttrss_feeds.update_interval || ' minutes') AS INTERVAL) AND ttrss_feeds.last_updated < NOW() - CAST((ttrss_feeds.update_interval || ' minutes') AS INTERVAL)
) OR ttrss_feeds.last_updated IS NULL ) OR (ttrss_feeds.last_updated IS NULL
OR last_updated = '1970-01-01 00:00:00')"; AND ttrss_user_prefs.value != '-1')
OR (last_updated = '1970-01-01 00:00:00'
AND ttrss_user_prefs.value != '-1'))";
} else { } else {
$update_limit_qpart = "AND (( $update_limit_qpart = "AND ((
ttrss_feeds.update_interval = 0 ttrss_feeds.update_interval = 0
@ -98,8 +113,10 @@
) OR ( ) OR (
ttrss_feeds.update_interval > 0 ttrss_feeds.update_interval > 0
AND ttrss_feeds.last_updated < DATE_SUB(NOW(), INTERVAL ttrss_feeds.update_interval MINUTE) AND ttrss_feeds.last_updated < DATE_SUB(NOW(), INTERVAL ttrss_feeds.update_interval MINUTE)
) OR ttrss_feeds.last_updated IS NULL ) OR (ttrss_feeds.last_updated IS NULL
OR last_updated = '1970-01-01 00:00:00')"; AND ttrss_user_prefs.value != '-1')
OR (last_updated = '1970-01-01 00:00:00'
AND ttrss_user_prefs.value != '-1'))";
} }
// Test if feed is currently being updated by another process. // Test if feed is currently being updated by another process.
@ -118,6 +135,7 @@
ttrss_feeds, ttrss_users, ttrss_user_prefs ttrss_feeds, ttrss_users, ttrss_user_prefs
WHERE WHERE
ttrss_feeds.owner_uid = ttrss_users.id ttrss_feeds.owner_uid = ttrss_users.id
AND ttrss_user_prefs.profile IS NULL
AND ttrss_users.id = ttrss_user_prefs.owner_uid AND ttrss_users.id = ttrss_user_prefs.owner_uid
AND ttrss_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL' AND ttrss_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL'
$login_thresh_qpart $update_limit_qpart $login_thresh_qpart $update_limit_qpart
@ -151,6 +169,7 @@
} }
$nf = 0; $nf = 0;
$bstarted = microtime(true);
// For each feed, we call the feed update function. // For each feed, we call the feed update function.
foreach ($feeds_to_update as $feed) { foreach ($feeds_to_update as $feed) {
@ -165,6 +184,7 @@
ttrss_user_prefs.owner_uid = ttrss_feeds.owner_uid AND ttrss_user_prefs.owner_uid = ttrss_feeds.owner_uid AND
ttrss_users.id = ttrss_user_prefs.owner_uid AND ttrss_users.id = ttrss_user_prefs.owner_uid AND
ttrss_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL' AND ttrss_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL' AND
ttrss_user_prefs.profile IS NULL AND
feed_url = '".db_escape_string($feed)."' AND feed_url = '".db_escape_string($feed)."' AND
(ttrss_feeds.update_interval > 0 OR (ttrss_feeds.update_interval > 0 OR
ttrss_user_prefs.value != '-1') ttrss_user_prefs.value != '-1')
@ -172,15 +192,27 @@
ORDER BY ttrss_feeds.id $query_limit"); ORDER BY ttrss_feeds.id $query_limit");
if (db_num_rows($tmp_result) > 0) { if (db_num_rows($tmp_result) > 0) {
$rss = false;
while ($tline = db_fetch_assoc($tmp_result)) { while ($tline = db_fetch_assoc($tmp_result)) {
if($debug) _debug(" => " . $tline["last_updated"] . ", " . $tline["id"] . " " . $tline["owner_uid"]); if($debug) _debug(" => " . $tline["last_updated"] . ", " . $tline["id"] . " " . $tline["owner_uid"]);
update_rss_feed($tline["id"], true);
$fstarted = microtime(true);
$rss = update_rss_feed($tline["id"], true, false);
_debug_suppress(false); _debug_suppress(false);
_debug(sprintf(" %.4f (sec)", microtime(true) - $fstarted));
++$nf; ++$nf;
} }
} }
} }
if ($nf > 0) {
_debug(sprintf("Processed %d feeds in %.4f (sec), %.4f (sec/feed avg)", $nf,
microtime(true) - $bstarted, (microtime(true) - $bstarted) / $nf));
}
require_once "digest.php"; require_once "digest.php";
// Send feed digests by email if needed. // Send feed digests by email if needed.
@ -191,7 +223,7 @@
} // function update_daemon_common } // function update_daemon_common
// ignore_daemon is not used // ignore_daemon is not used
function update_rss_feed($feed, $ignore_daemon = false, $no_cache = false) { function update_rss_feed($feed, $ignore_daemon = false, $no_cache = false, $rss = false) {
$debug_enabled = defined('DAEMON_EXTENDED_DEBUG') || $_REQUEST['xdebug']; $debug_enabled = defined('DAEMON_EXTENDED_DEBUG') || $_REQUEST['xdebug'];
@ -199,7 +231,7 @@
_debug("start", $debug_enabled); _debug("start", $debug_enabled);
$result = db_query("SELECT id,update_interval,auth_login, $result = db_query("SELECT id,update_interval,auth_login,
feed_url,auth_pass,cache_images,last_updated, feed_url,auth_pass,cache_images,
mark_unread_on_update, owner_uid, mark_unread_on_update, owner_uid,
pubsub_state, auth_pass_encrypted, pubsub_state, auth_pass_encrypted,
(SELECT max(date_entered) FROM (SELECT max(date_entered) FROM
@ -211,7 +243,6 @@
return false; return false;
} }
$last_updated = db_fetch_result($result, 0, "last_updated");
$last_article_timestamp = @strtotime(db_fetch_result($result, 0, "last_article_timestamp")); $last_article_timestamp = @strtotime(db_fetch_result($result, 0, "last_article_timestamp"));
if (defined('_DISABLE_HTTP_304')) if (defined('_DISABLE_HTTP_304'))
@ -252,34 +283,37 @@
$pluginhost->load($user_plugins, PluginHost::KIND_USER, $owner_uid); $pluginhost->load($user_plugins, PluginHost::KIND_USER, $owner_uid);
$pluginhost->load_data(); $pluginhost->load_data();
$rss = false; if ($rss && is_object($rss) && get_class($rss) == "FeedParser") {
$rss_hash = false; _debug("using previously initialized parser object");
$force_refetch = isset($_REQUEST["force_refetch"]);
if (file_exists($cache_filename) &&
is_readable($cache_filename) &&
!$auth_login && !$auth_pass &&
filemtime($cache_filename) > time() - 30) {
_debug("using local cache.", $debug_enabled);
@$feed_data = file_get_contents($cache_filename);
if ($feed_data) {
$rss_hash = sha1($feed_data);
}
} else { } else {
_debug("local cache will not be used for this feed", $debug_enabled); $rss_hash = false;
}
if (!$rss) { $force_refetch = isset($_REQUEST["force_refetch"]);
foreach ($pluginhost->get_hooks(PluginHost::HOOK_FETCH_FEED) as $plugin) { foreach ($pluginhost->get_hooks(PluginHost::HOOK_FETCH_FEED) as $plugin) {
$feed_data = $plugin->hook_fetch_feed($feed_data, $fetch_url, $owner_uid, $feed); $feed_data = $plugin->hook_fetch_feed($feed_data, $fetch_url, $owner_uid, $feed, $last_article_timestamp, $auth_login, $auth_pass);
} }
// try cache
if (!$feed_data &&
file_exists($cache_filename) &&
is_readable($cache_filename) &&
!$auth_login && !$auth_pass &&
filemtime($cache_filename) > time() - 30) {
_debug("using local cache [$cache_filename].", $debug_enabled);
@$feed_data = file_get_contents($cache_filename);
if ($feed_data) {
$rss_hash = sha1($feed_data);
}
} else {
_debug("local cache will not be used for this feed", $debug_enabled);
}
// fetch feed from source
if (!$feed_data) { if (!$feed_data) {
_debug("fetching [$fetch_url]...", $debug_enabled); _debug("fetching [$fetch_url]...", $debug_enabled);
_debug("If-Modified-Since: ".gmdate('D, d M Y H:i:s \G\M\T', $last_article_timestamp), $debug_enabled); _debug("If-Modified-Since: ".gmdate('D, d M Y H:i:s \G\M\T', $last_article_timestamp), $debug_enabled);
@ -318,6 +352,16 @@
} }
} }
} */ } */
// cache vanilla feed data for re-use
if ($feed_data && !$auth_pass && !$auth_login && is_writable(CACHE_DIR . "/simplepie")) {
$new_rss_hash = sha1($feed_data);
if ($new_rss_hash != $rss_hash) {
_debug("saving $cache_filename", $debug_enabled);
@file_put_contents($cache_filename, $feed_data);
}
}
} }
if (!$feed_data) { if (!$feed_data) {
@ -356,10 +400,12 @@
$rss->init(); $rss->init();
} }
require_once "lib/languagedetect/LanguageDetect.php"; if (DETECT_ARTICLE_LANGUAGE) {
require_once "lib/languagedetect/LanguageDetect.php";
$lang = new Text_LanguageDetect(); $lang = new Text_LanguageDetect();
$lang->setNameMode(2); $lang->setNameMode(2);
}
// print_r($rss); // print_r($rss);
@ -367,16 +413,6 @@
if (!$rss->error()) { if (!$rss->error()) {
// cache data for later
if (!$auth_pass && !$auth_login && is_writable(CACHE_DIR . "/simplepie")) {
$new_rss_hash = sha1($rss_data);
if ($new_rss_hash != $rss_hash && count($rss->get_items()) > 0 ) {
_debug("saving $cache_filename", $debug_enabled);
@file_put_contents($cache_filename, $feed_data);
}
}
// We use local pluginhost here because we need to load different per-user feed plugins // 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); $pluginhost->run_hooks(PluginHost::HOOK_FEED_PARSED, "hook_feed_parsed", $rss);
@ -495,7 +531,20 @@
_debug("feed hub url: $feed_hub_url", $debug_enabled); _debug("feed hub url: $feed_hub_url", $debug_enabled);
if ($feed_hub_url && function_exists('curl_init') && $feed_self_url = $fetch_url;
$links = $rss->get_links('self');
if ($links && is_array($links)) {
foreach ($links as $l) {
$feed_self_url = $l;
break;
}
}
_debug("feed self url = $feed_self_url");
if ($feed_hub_url && $feed_self_url && function_exists('curl_init') &&
!ini_get("open_basedir")) { !ini_get("open_basedir")) {
require_once 'lib/pubsubhubbub/subscriber.php'; require_once 'lib/pubsubhubbub/subscriber.php';
@ -505,9 +554,9 @@
$s = new Subscriber($feed_hub_url, $callback_url); $s = new Subscriber($feed_hub_url, $callback_url);
$rc = $s->subscribe($fetch_url); $rc = $s->subscribe($feed_self_url);
_debug("feed hub url found, subscribe request sent.", $debug_enabled); _debug("feed hub url found, subscribe request sent. [rc=$rc]", $debug_enabled);
db_query("UPDATE ttrss_feeds SET pubsub_state = 1 db_query("UPDATE ttrss_feeds SET pubsub_state = 1
WHERE id = '$feed'"); WHERE id = '$feed'");
@ -524,9 +573,6 @@
$entry_guid = $item->get_id(); $entry_guid = $item->get_id();
if (!$entry_guid) $entry_guid = $item->get_link(); if (!$entry_guid) $entry_guid = $item->get_link();
if (!$entry_guid) $entry_guid = make_guid_from_title($item->get_title()); if (!$entry_guid) $entry_guid = make_guid_from_title($item->get_title());
_debug("f_guid $entry_guid", $debug_enabled);
if (!$entry_guid) continue; if (!$entry_guid) continue;
$entry_guid = "$owner_uid,$entry_guid"; $entry_guid = "$owner_uid,$entry_guid";
@ -543,9 +589,6 @@
if ($entry_timestamp == -1 || !$entry_timestamp || $entry_timestamp > time()) { if ($entry_timestamp == -1 || !$entry_timestamp || $entry_timestamp > time()) {
$entry_timestamp = time(); $entry_timestamp = time();
$no_orig_date = 'true';
} else {
$no_orig_date = 'false';
} }
$entry_timestamp_fmt = strftime("%Y/%m/%d %H:%M:%S", $entry_timestamp); $entry_timestamp_fmt = strftime("%Y/%m/%d %H:%M:%S", $entry_timestamp);
@ -572,15 +615,19 @@
print "\n"; print "\n";
} }
$entry_language = $lang->detect($entry_title . " " . $entry_content, 1); $entry_language = "";
if (count($entry_language) > 0) { if (DETECT_ARTICLE_LANGUAGE) {
$entry_language = array_keys($entry_language); $entry_language = $lang->detect($entry_title . " " . $entry_content, 1);
$entry_language = db_escape_string(substr($entry_language[0], 0, 2));
_debug("detected language: $entry_language", $debug_enabled); if (count($entry_language) > 0) {
} else{ $possible = array_keys($entry_language);
$entry_language = ""; $entry_language = $possible[0];
_debug("detected language: $entry_language", $debug_enabled);
} else {
$entry_language = "";
}
} }
$entry_comments = $item->get_comments_url(); $entry_comments = $item->get_comments_url();
@ -618,24 +665,15 @@
_debug("done collecting data.", $debug_enabled); _debug("done collecting data.", $debug_enabled);
// TODO: less memory-hungry implementation $result = db_query("SELECT id, content_hash FROM ttrss_entries
WHERE guid = '".db_escape_string($entry_guid)."' OR guid = '$entry_guid_hashed'");
_debug("applying plugin filters..", $debug_enabled);
// FIXME not sure if owner_uid is a good idea here, we may have a base entry without user entry (?)
$result = db_query("SELECT plugin_data,title,content,link,tag_cache,author FROM ttrss_entries, ttrss_user_entries
WHERE ref_id = id AND (guid = '".db_escape_string($entry_guid)."' OR guid = '$entry_guid_hashed') AND owner_uid = $owner_uid");
if (db_num_rows($result) != 0) { if (db_num_rows($result) != 0) {
$entry_plugin_data = db_fetch_result($result, 0, "plugin_data"); $base_entry_id = db_fetch_result($result, 0, "id");
$stored_article = array("title" => db_fetch_result($result, 0, "title"), $entry_stored_hash = db_fetch_result($result, 0, "content_hash");
"content" => db_fetch_result($result, 0, "content"),
"link" => db_fetch_result($result, 0, "link"),
"tags" => explode(",", db_fetch_result($result, 0, "tag_cache")),
"author" => db_fetch_result($result, 0, "author"));
} else { } else {
$entry_plugin_data = ""; $base_entry_id = false;
$stored_article = array(); $entry_stored_hash = "";
} }
$article = array("owner_uid" => $owner_uid, // read only $article = array("owner_uid" => $owner_uid, // read only
@ -644,36 +682,63 @@
"content" => $entry_content, "content" => $entry_content,
"link" => $entry_link, "link" => $entry_link,
"tags" => $entry_tags, "tags" => $entry_tags,
"plugin_data" => $entry_plugin_data,
"author" => $entry_author, "author" => $entry_author,
"stored" => $stored_article, "language" => $entry_language, // read only
"feed" => array("id" => $feed, "feed" => array("id" => $feed,
"fetch_url" => $fetch_url, "fetch_url" => $fetch_url,
"site_url" => $site_url) "site_url" => $site_url)
); );
foreach ($pluginhost->get_hooks(PluginHost::HOOK_ARTICLE_FILTER) as $plugin) { $entry_plugin_data = "";
$article = $plugin->hook_article_filter($article); $entry_current_hash = calculate_article_hash($article, $pluginhost);
_debug("article hash: $entry_current_hash [stored=$entry_stored_hash]", $debug_enabled);
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);
// we keep encountering the entry in feeds, so we need to
// update date_updated column so that we don't get horrible
// 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'");
continue;
} }
_debug("hash differs, applying plugin filters:", $debug_enabled);
foreach ($pluginhost->get_hooks(PluginHost::HOOK_ARTICLE_FILTER) as $plugin) {
_debug("... " . get_class($plugin), $debug_enabled);
$start = microtime(true);
$article = $plugin->hook_article_filter($article);
_debug("=== " . sprintf("%.4f (sec)", microtime(true) - $start), $debug_enabled);
$entry_plugin_data .= mb_strtolower(get_class($plugin)) . ",";
}
$entry_plugin_data = db_escape_string($entry_plugin_data);
_debug("plugin data: $entry_plugin_data", $debug_enabled);
$entry_tags = $article["tags"]; $entry_tags = $article["tags"];
$entry_guid = db_escape_string($entry_guid); $entry_guid = db_escape_string($entry_guid);
$entry_title = db_escape_string($article["title"]); $entry_title = db_escape_string($article["title"]);
$entry_author = db_escape_string($article["author"]); $entry_author = db_escape_string($article["author"]);
$entry_link = db_escape_string($article["link"]); $entry_link = db_escape_string($article["link"]);
$entry_plugin_data = db_escape_string($article["plugin_data"]);
$entry_content = $article["content"]; // escaped below $entry_content = $article["content"]; // escaped below
_debug("plugin data: $entry_plugin_data", $debug_enabled);
if ($cache_images && is_writable(CACHE_DIR . '/images')) if ($cache_images && is_writable(CACHE_DIR . '/images'))
cache_images($entry_content, $site_url, $debug_enabled); cache_images($entry_content, $site_url, $debug_enabled);
$entry_content = db_escape_string($entry_content, false); $entry_content = db_escape_string($entry_content, false);
$content_hash = "SHA1:" . sha1($entry_content);
db_query("BEGIN"); db_query("BEGIN");
$result = db_query("SELECT id FROM ttrss_entries $result = db_query("SELECT id FROM ttrss_entries
@ -707,8 +772,8 @@
'$entry_link', '$entry_link',
'$entry_timestamp_fmt', '$entry_timestamp_fmt',
'$entry_content', '$entry_content',
'$content_hash', '$entry_current_hash',
$no_orig_date, false,
NOW(), NOW(),
'$date_feed_processed', '$date_feed_processed',
'$entry_comments', '$entry_comments',
@ -720,28 +785,14 @@
$article_labels = array(); $article_labels = array();
} else { } else {
// we keep encountering the entry in feeds, so we need to
// update date_updated column so that we don't get horrible
// 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"); $base_entry_id = db_fetch_result($result, 0, "id");
db_query("UPDATE ttrss_entries SET date_updated = NOW()
WHERE id = '$base_entry_id'");
$article_labels = get_article_labels($base_entry_id, $owner_uid); $article_labels = get_article_labels($base_entry_id, $owner_uid);
} }
// now it should exist, if not - bad luck then // now it should exist, if not - bad luck then
$result = db_query("SELECT $result = db_query("SELECT id FROM ttrss_entries
id,content_hash,no_orig_date,title,plugin_data,guid,
".SUBSTRING_FOR_DATE."(date_updated,1,19) as date_updated,
".SUBSTRING_FOR_DATE."(updated,1,19) as updated,
num_comments
FROM
ttrss_entries
WHERE guid = '$entry_guid' OR guid = '$entry_guid_hashed'"); WHERE guid = '$entry_guid' OR guid = '$entry_guid_hashed'");
$entry_ref_id = 0; $entry_ref_id = 0;
@ -751,14 +802,6 @@
_debug("base guid found, checking for user record", $debug_enabled); _debug("base guid found, checking for user record", $debug_enabled);
// this will be used below in update handler
$orig_content_hash = db_fetch_result($result, 0, "content_hash");
$orig_title = db_fetch_result($result, 0, "title");
$orig_num_comments = db_fetch_result($result, 0, "num_comments");
$orig_date_updated = strtotime(db_fetch_result($result,
0, "date_updated"));
$orig_plugin_data = db_fetch_result($result, 0, "plugin_data");
$ref_id = db_fetch_result($result, 0, "id"); $ref_id = db_fetch_result($result, 0, "id");
$entry_ref_id = $ref_id; $entry_ref_id = $ref_id;
@ -872,7 +915,7 @@
$p = new Publisher(PUBSUBHUBBUB_HUB); $p = new Publisher(PUBSUBHUBBUB_HUB);
$pubsub_result = $p->publish_update($rss_link); /* $pubsub_result = */ $p->publish_update($rss_link);
} }
$result = db_query( $result = db_query(
@ -892,53 +935,20 @@
_debug("RID: $entry_ref_id, IID: $entry_int_id", $debug_enabled); _debug("RID: $entry_ref_id, IID: $entry_int_id", $debug_enabled);
$post_needs_update = false; db_query("UPDATE ttrss_entries
$update_insignificant = false; SET title = '$entry_title',
content = '$entry_content',
content_hash = '$entry_current_hash',
updated = '$entry_timestamp_fmt',
num_comments = '$num_comments',
plugin_data = '$entry_plugin_data',
author = '$entry_author',
lang = '$entry_language'
WHERE id = '$ref_id'");
if ($orig_num_comments != $num_comments) { if ($mark_unread_on_update) {
$post_needs_update = true; db_query("UPDATE ttrss_user_entries
$update_insignificant = true; SET last_read = null, unread = true WHERE ref_id = '$ref_id'");
}
if ($entry_plugin_data != $orig_plugin_data) {
$post_needs_update = true;
$update_insignificant = true;
}
if ($content_hash != $orig_content_hash) {
$post_needs_update = true;
$update_insignificant = false;
}
if (db_escape_string($orig_title) != $entry_title) {
$post_needs_update = true;
$update_insignificant = false;
}
// if post needs update, update it and mark all user entries
// linking to this post as updated
if ($post_needs_update) {
if (defined('DAEMON_EXTENDED_DEBUG')) {
_debug("post $entry_guid_hashed needs update...", $debug_enabled);
}
// print "<!-- post $orig_title needs update : $post_needs_update -->";
db_query("UPDATE ttrss_entries
SET title = '$entry_title', content = '$entry_content',
content_hash = '$content_hash',
updated = '$entry_timestamp_fmt',
num_comments = '$num_comments',
plugin_data = '$entry_plugin_data'
WHERE id = '$ref_id'");
if (!$update_insignificant) {
if ($mark_unread_on_update) {
db_query("UPDATE ttrss_user_entries
SET last_read = null, unread = true WHERE ref_id = '$ref_id'");
}
}
} }
} }
@ -960,7 +970,7 @@
if (is_array($encs)) { if (is_array($encs)) {
foreach ($encs as $e) { foreach ($encs as $e) {
$e_item = array( $e_item = array(
$e->link, $e->type, $e->length, $e->title); $e->link, $e->type, $e->length, $e->title, $e->width, $e->height);
array_push($enclosures, $e_item); array_push($enclosures, $e_item);
} }
} }
@ -980,14 +990,16 @@
$enc_type = db_escape_string($enc[1]); $enc_type = db_escape_string($enc[1]);
$enc_dur = db_escape_string($enc[2]); $enc_dur = db_escape_string($enc[2]);
$enc_title = db_escape_string($enc[3]); $enc_title = db_escape_string($enc[3]);
$enc_width = intval($enc[4]);
$enc_height = intval($enc[5]);
$result = db_query("SELECT id FROM ttrss_enclosures $result = db_query("SELECT id FROM ttrss_enclosures
WHERE content_url = '$enc_url' AND post_id = '$entry_ref_id'"); WHERE content_url = '$enc_url' AND post_id = '$entry_ref_id'");
if (db_num_rows($result) == 0) { if (db_num_rows($result) == 0) {
db_query("INSERT INTO ttrss_enclosures db_query("INSERT INTO ttrss_enclosures
(content_url, content_type, title, duration, post_id) VALUES (content_url, content_type, title, duration, post_id, width, height) VALUES
('$enc_url', '$enc_type', '$enc_title', '$enc_dur', '$entry_ref_id')"); ('$enc_url', '$enc_type', '$enc_title', '$enc_dur', '$entry_ref_id', $enc_width, $enc_height)");
} }
} }
@ -1101,21 +1113,27 @@
$error_msg = db_escape_string(mb_substr($rss->error(), 0, 245)); $error_msg = db_escape_string(mb_substr($rss->error(), 0, 245));
_debug("error fetching feed: $error_msg", $debug_enabled); _debug("fetch error: $error_msg", $debug_enabled);
if (count($rss->errors()) > 1) {
foreach ($rss->errors() as $error) {
_debug("+ $error");
}
}
db_query( db_query(
"UPDATE ttrss_feeds SET last_error = '$error_msg', "UPDATE ttrss_feeds SET last_error = '$error_msg',
last_updated = NOW() WHERE id = '$feed'"); last_updated = NOW() WHERE id = '$feed'");
unset($rss);
} }
unset($rss);
_debug("done", $debug_enabled); _debug("done", $debug_enabled);
return $rss;
} }
function cache_images($html, $site_url, $debug) { function cache_images($html, $site_url, $debug) {
$cache_dir = CACHE_DIR . "/images";
libxml_use_internal_errors(true); libxml_use_internal_errors(true);
$charset_hack = '<head> $charset_hack = '<head>
@ -1139,7 +1157,7 @@
if (!file_exists($local_filename)) { if (!file_exists($local_filename)) {
$file_content = fetch_file_contents($src); $file_content = fetch_file_contents($src);
if ($file_content && strlen($file_content) > 1024) { if ($file_content && strlen($file_content) > _MIN_CACHE_IMAGE_SIZE) {
file_put_contents($local_filename, $file_content); file_put_contents($local_filename, $file_content);
} }
} }

View file

@ -146,11 +146,6 @@
array_push($errors, "PHP support for CURL is required for PubSubHubbub."); array_push($errors, "PHP support for CURL is required for PubSubHubbub.");
} }
if (SPHINX_ENABLED && class_exists("SphinxClient")) {
array_push($errors, "Your PHP has a separate systemwide Sphinx client installed which conflicts with the client library used by tt-rss. Either remove the system library or disable Sphinx support.");
}
if (!class_exists("DOMDocument")) { if (!class_exists("DOMDocument")) {
array_push($errors, "PHP support for DOMDocument is required, but was not found."); array_push($errors, "PHP support for DOMDocument is required, but was not found.");
} }

View file

@ -1,3 +1,3 @@
<?php # This file has been generated at: Thu May 30 08:39:20 MSK 2013 <?php # This file has been generated at: Fri Sep 27 13:42:37 MSK 2013
define('GENERATED_CONFIG_CHECK', 26); define('GENERATED_CONFIG_CHECK', 26);
$requred_defines = array( 'DB_TYPE', 'DB_HOST', 'DB_USER', 'DB_NAME', 'DB_PASS', 'MYSQL_CHARSET', 'SELF_URL_PATH', 'FEED_CRYPT_KEY', 'SINGLE_USER_MODE', 'SIMPLE_UPDATE_MODE', 'PHP_EXECUTABLE', 'LOCK_DIRECTORY', 'CACHE_DIR', 'ICONS_DIR', 'ICONS_URL', 'AUTH_AUTO_CREATE', 'AUTH_AUTO_LOGIN', 'FORCE_ARTICLE_PURGE', 'PUBSUBHUBBUB_HUB', 'PUBSUBHUBBUB_ENABLED', 'SPHINX_ENABLED', 'SPHINX_SERVER', 'SPHINX_INDEX', 'ENABLE_REGISTRATION', 'REG_NOTIFY_ADDRESS', 'REG_MAX_USERS', 'SESSION_COOKIE_LIFETIME', 'SESSION_CHECK_ADDRESS', 'SMTP_FROM_NAME', 'SMTP_FROM_ADDRESS', 'DIGEST_SUBJECT', 'SMTP_SERVER', 'SMTP_LOGIN', 'SMTP_PASSWORD', 'SMTP_SECURE', 'CHECK_FOR_NEW_VERSION', 'ENABLE_GZIP_OUTPUT', 'PLUGINS', 'LOG_DESTINATION', 'CONFIG_VERSION'); ?> $requred_defines = array( 'DB_TYPE', 'DB_HOST', 'DB_USER', 'DB_NAME', 'DB_PASS', 'MYSQL_CHARSET', 'SELF_URL_PATH', 'FEED_CRYPT_KEY', 'SINGLE_USER_MODE', 'SIMPLE_UPDATE_MODE', 'PHP_EXECUTABLE', 'LOCK_DIRECTORY', 'CACHE_DIR', 'ICONS_DIR', 'ICONS_URL', 'AUTH_AUTO_CREATE', 'AUTH_AUTO_LOGIN', 'FORCE_ARTICLE_PURGE', 'PUBSUBHUBBUB_HUB', 'PUBSUBHUBBUB_ENABLED', 'ENABLE_REGISTRATION', 'REG_NOTIFY_ADDRESS', 'REG_MAX_USERS', 'SESSION_COOKIE_LIFETIME', 'SESSION_CHECK_ADDRESS', 'SMTP_FROM_NAME', 'SMTP_FROM_ADDRESS', 'DIGEST_SUBJECT', 'SMTP_SERVER', 'SMTP_LOGIN', 'SMTP_PASSWORD', 'SMTP_SECURE', 'CHECK_FOR_NEW_VERSION', 'DETECT_ARTICLE_LANGUAGE', 'ENABLE_GZIP_OUTPUT', 'PLUGINS', 'LOG_DESTINATION', 'CONFIG_VERSION'); ?>

View file

@ -1,5 +1,5 @@
<?php <?php
define('VERSION_STATIC', '1.10'); define('VERSION_STATIC', '1.14');
function get_version() { function get_version() {
date_default_timezone_set('UTC'); date_default_timezone_set('UTC');

View file

@ -56,15 +56,19 @@
<head> <head>
<title>Tiny Tiny RSS</title> <title>Tiny Tiny RSS</title>
<?php stylesheet_tag("lib/dijit/themes/claro/claro.css"); ?> <script type="text/javascript">
<?php stylesheet_tag("css/layout.css"); ?> var __ttrss_version = "<?php echo VERSION ?>"
</script>
<?php echo stylesheet_tag("lib/dijit/themes/claro/claro.css"); ?>
<?php echo stylesheet_tag("css/layout.css"); ?>
<?php if ($_SESSION["uid"]) { <?php if ($_SESSION["uid"]) {
$theme = get_pref( "USER_CSS_THEME", $_SESSION["uid"], false); $theme = get_pref( "USER_CSS_THEME", $_SESSION["uid"], false);
if ($theme && file_exists("themes/$theme")) { if ($theme && file_exists("themes/$theme")) {
stylesheet_tag("themes/$theme"); echo stylesheet_tag("themes/$theme");
} else { } else {
stylesheet_tag("themes/default.css"); echo stylesheet_tag("themes/default.css");
} }
} }
?> ?>
@ -86,12 +90,12 @@
<?php <?php
foreach (array("lib/prototype.js", foreach (array("lib/prototype.js",
"lib/scriptaculous/scriptaculous.js?load=effects,dragdrop,controls", "lib/scriptaculous/scriptaculous.js?load=effects,controls",
"lib/dojo/dojo.js", "lib/dojo/dojo.js",
"lib/dojo/tt-rss-layer.js", "lib/dojo/tt-rss-layer.js",
"errors.php?mode=js") as $jsfile) { "errors.php?mode=js") as $jsfile) {
javascript_tag($jsfile); echo javascript_tag($jsfile);
} ?> } ?>
@ -153,6 +157,10 @@
<div id="toolbar" dojoType="dijit.layout.ContentPane" region="top"> <div id="toolbar" dojoType="dijit.layout.ContentPane" region="top">
<div id="main-toolbar" dojoType="dijit.Toolbar"> <div id="main-toolbar" dojoType="dijit.Toolbar">
<form id="headlines-toolbar" action="" onsubmit='return false'>
</form>
<form id="main_toolbar_form" action="" onsubmit='return false'> <form id="main_toolbar_form" action="" onsubmit='return false'>
<button dojoType="dijit.form.Button" id="collapse_feeds_btn" <button dojoType="dijit.form.Button" id="collapse_feeds_btn"
@ -257,9 +265,6 @@
<div id="headlines-wrap-inner" dojoType="dijit.layout.BorderContainer" region="center"> <div id="headlines-wrap-inner" dojoType="dijit.layout.BorderContainer" region="center">
<div id="headlines-toolbar" dojoType="dijit.layout.ContentPane" region="top">
</div>
<div id="floatingTitle" style="display : none"></div> <div id="floatingTitle" style="display : none"></div>
<div id="headlines-frame" dojoType="dijit.layout.ContentPane" <div id="headlines-frame" dojoType="dijit.layout.ContentPane"

View file

@ -3,6 +3,7 @@
<title>Tiny Tiny RSS - Installer</title> <title>Tiny Tiny RSS - Installer</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <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/utility.css">
<link rel="stylesheet" type="text/css" href="../css/dijit.css">
<style type="text/css"> <style type="text/css">
textarea { font-size : 12px; } textarea { font-size : 12px; }
</style> </style>
@ -10,6 +11,12 @@
<body> <body>
<?php <?php
// could be needed because of existing config.php
function define_default($param, $value) {
//
}
function make_password($length = 8) { function make_password($length = 8) {
$password = ""; $password = "";
@ -263,7 +270,7 @@
<fieldset> <fieldset>
<label>Password</label> <label>Password</label>
<input required name="DB_PASS" size="20" type="password" value="<?php echo $DB_PASS ?>"/> <input name="DB_PASS" size="20" type="password" value="<?php echo $DB_PASS ?>"/>
</fieldset> </fieldset>
<fieldset> <fieldset>

View file

@ -58,12 +58,12 @@ dojo.declare("fox.FeedStoreModel", dijit.tree.ForestStoreModel, {
if (is_cat) { if (is_cat) {
treeItem = this.store._itemsByIdentity['CAT:' + feed]; treeItem = this.store._itemsByIdentity['CAT:' + feed];
items = this.store._arrayOfTopLevelItems;
} else { } else {
treeItem = this.store._itemsByIdentity['FEED:' + feed]; treeItem = this.store._itemsByIdentity['FEED:' + feed];
items = this.store._arrayOfAllItems;
} }
items = this.store._arrayOfAllItems;
for (var i = 0; i < items.length; i++) { for (var i = 0; i < items.length; i++) {
if (items[i] == treeItem) { if (items[i] == treeItem) {
@ -71,14 +71,18 @@ dojo.declare("fox.FeedStoreModel", dijit.tree.ForestStoreModel, {
var unread = this.store.getValue(items[j], 'unread'); var unread = this.store.getValue(items[j], 'unread');
var id = this.store.getValue(items[j], 'id'); var id = this.store.getValue(items[j], 'id');
if (unread > 0 && (is_cat || id.match("FEED:"))) return items[j]; if (unread > 0 && ((is_cat && id.match("CAT:")) || (!is_cat && id.match("FEED:")))) {
if( !is_cat || ! (this.store.hasAttribute(items[j], 'parent_id') && this.store.getValue(items[j], 'parent_id') == feed) ) return items[j];
}
} }
for (var j = 0; j < i; j++) { for (var j = 0; j < i; j++) {
var unread = this.store.getValue(items[j], 'unread'); var unread = this.store.getValue(items[j], 'unread');
var id = this.store.getValue(items[j], 'id'); var id = this.store.getValue(items[j], 'id');
if (unread > 0 && (is_cat || id.match("FEED:"))) return items[j]; if (unread > 0 && ((is_cat && id.match("CAT:")) || (!is_cat && id.match("FEED:")))) {
if( !is_cat || ! (this.store.hasAttribute(items[j], 'parent_id') && this.store.getValue(items[j], 'parent_id') == feed) ) return items[j];
}
} }
} }
} }
@ -539,7 +543,7 @@ dojo.declare("fox.FeedTree", dijit.Tree, {
} }
items = this.model.store._arrayOfAllItems; items = this.model.store._arrayOfAllItems;
var item = items[0]; var item = items[0] == treeItem ? items[items.length-1] : items[0];
for (var i = 0; i < items.length; i++) { for (var i = 0; i < items.length; i++) {
if (items[i] == treeItem) { if (items[i] == treeItem) {

View file

@ -24,6 +24,7 @@ dojo.declare("fox.PrefFilterTree", lib.CheckBoxTree, {
var enabled = this.model.store.getValue(args.item, 'enabled'); var enabled = this.model.store.getValue(args.item, 'enabled');
var param = this.model.store.getValue(args.item, 'param'); var param = this.model.store.getValue(args.item, 'param');
var rules = this.model.store.getValue(args.item, 'rules');
if (param) { if (param) {
param = dojo.doc.createElement('span'); param = dojo.doc.createElement('span');
@ -32,6 +33,13 @@ dojo.declare("fox.PrefFilterTree", lib.CheckBoxTree, {
dojo.place(param, tnode.rowNode, 'first'); dojo.place(param, tnode.rowNode, 'first');
} }
if (rules) {
param = dojo.doc.createElement('span');
param.className = 'filterRules';
param.innerHTML = rules;
dojo.place(param, tnode.rowNode, 'next');
}
if (this.model.store.getValue(args.item, 'id') != 'root') { if (this.model.store.getValue(args.item, 'id') != 'root') {
var img = dojo.doc.createElement('img'); var img = dojo.doc.createElement('img');
img.src ='images/filter.png'; img.src ='images/filter.png';

View file

@ -44,11 +44,8 @@ function exception_error(location, e, ext_info) {
try { try {
if (ext_info) { if (ext_info)
if (ext_info.responseText) { ext_info = JSON.stringify(ext_info);
ext_info = ext_info.responseText;
}
}
try { try {
new Ajax.Request("backend.php", { new Ajax.Request("backend.php", {
@ -104,13 +101,15 @@ function exception_error(location, e, ext_info) {
title: "Unhandled exception", title: "Unhandled exception",
style: "width: 600px", style: "width: 600px",
report: function() { report: function() {
if (confirm(__("Are you sure to report this exception to tt-rss.org? The report will include your browser information. Your IP would be saved in the database."))) { if (confirm(__("Are you sure to report this exception to tt-rss.org? The report will include information about your web browser and tt-rss configuration. Your IP will be saved in the database."))) {
document.forms['exceptionForm'].params.value = $H({ document.forms['exceptionForm'].params.value = $H({
browserName: navigator.appName, browserName: navigator.appName,
browserVersion: navigator.appVersion, browserVersion: navigator.appVersion,
browserPlatform: navigator.platform, browserPlatform: navigator.platform,
browserCookies: navigator.cookieEnabled, browserCookies: navigator.cookieEnabled,
ttrssVersion: __ttrss_version,
initParams: JSON.stringify(init_params),
}).toQueryString(); }).toQueryString();
document.forms['exceptionForm'].submit(); document.forms['exceptionForm'].submit();
@ -205,6 +204,7 @@ function notify_real(msg, no_hide, n_type) {
return; return;
} else { } else {
Element.show(n); Element.show(n);
new Effect.Highlight(n);
} }
/* types: /* types:
@ -829,7 +829,14 @@ function quickAddFeed() {
onComplete: function(transport) { onComplete: function(transport) {
try { try {
var reply = JSON.parse(transport.responseText); try {
var reply = JSON.parse(transport.responseText);
} catch (e) {
Element.hide("feed_add_spinner");
alert(__("Failed to parse output. This can indicate server timeout and/or network issues. Backend output was logged to browser console."));
console.log('quickAddFeed, backend returned:' + transport.responseText);
return;
}
var rc = reply['result']; var rc = reply['result'];
@ -854,6 +861,8 @@ function quickAddFeed() {
case 4: case 4:
feeds = rc['feeds']; feeds = rc['feeds'];
Element.show("fadd_multiple_notify");
var select = dijit.byId("feedDlg_feedContainerSelect"); var select = dijit.byId("feedDlg_feedContainerSelect");
while (select.getOptions().length > 0) while (select.getOptions().length > 0)
@ -1148,33 +1157,48 @@ function quickAddFilter() {
href: query}); href: query});
if (!inPreferences()) { if (!inPreferences()) {
var selectedText = getSelectionText();
var lh = dojo.connect(dialog, "onLoad", function(){ var lh = dojo.connect(dialog, "onLoad", function(){
dojo.disconnect(lh); dojo.disconnect(lh);
var query = "op=rpc&method=getlinktitlebyid&id=" + getActiveArticleId(); if (selectedText != "") {
new Ajax.Request("backend.php", { var feed_id = activeFeedIsCat() ? 'CAT:' + parseInt(getActiveFeedId()) :
parameters: query, getActiveFeedId();
onComplete: function(transport) {
var reply = JSON.parse(transport.responseText);
var title = false; var rule = { reg_exp: selectedText, feed_id: feed_id, filter_type: 1 };
if (reply && reply) title = reply.title; addFilterRule(null, dojo.toJson(rule));
if (title || getActiveFeedId() || activeFeedIsCat()) { } else {
console.log(title + " " + getActiveFeedId()); var query = "op=rpc&method=getlinktitlebyid&id=" + getActiveArticleId();
var feed_id = activeFeedIsCat() ? 'CAT:' + parseInt(getActiveFeedId()) : new Ajax.Request("backend.php", {
getActiveFeedId(); parameters: query,
onComplete: function(transport) {
var reply = JSON.parse(transport.responseText);
var rule = { reg_exp: title, feed_id: feed_id, filter_type: 1 }; var title = false;
addFilterRule(null, dojo.toJson(rule)); if (reply && reply) title = reply.title;
}
} }); if (title || getActiveFeedId() || activeFeedIsCat()) {
console.log(title + " " + getActiveFeedId());
var feed_id = activeFeedIsCat() ? 'CAT:' + parseInt(getActiveFeedId()) :
getActiveFeedId();
var rule = { reg_exp: title, feed_id: feed_id, filter_type: 1 };
addFilterRule(null, dojo.toJson(rule));
}
} });
}
}); });
} }
@ -1270,10 +1294,8 @@ function backend_sanity_check_callback(transport) {
console.log('reading init-params...'); console.log('reading init-params...');
for (k in params) { for (k in params) {
var v = params[k]; console.log("IP: " + k + " => " + JSON.stringify(params[k]));
console.log("IP: " + k + " => " + v); if (k == "label_base_index") _label_base_index = parseInt(params[k]);
if (k == "label_base_index") _label_base_index = parseInt(v);
} }
init_params = params; init_params = params;
@ -1934,3 +1956,25 @@ function feed_to_label_id(feed) {
return _label_base_index - 1 + Math.abs(feed); return _label_base_index - 1 + Math.abs(feed);
} }
// http://stackoverflow.com/questions/6251937/how-to-get-selecteduser-highlighted-text-in-contenteditable-element-and-replac
function getSelectionText() {
var text = "";
if (typeof window.getSelection != "undefined") {
var sel = window.getSelection();
if (sel.rangeCount) {
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
text = container.innerHTML;
}
} else if (typeof document.selection != "undefined") {
if (document.selection.type == "Text") {
text = document.selection.createRange().textText;
}
}
return text.stripTags();
}

View file

@ -231,6 +231,7 @@ function init() {
dojo.require("dijit.form.Select"); dojo.require("dijit.form.Select");
dojo.require("dijit.form.SimpleTextarea"); dojo.require("dijit.form.SimpleTextarea");
dojo.require("dijit.form.TextBox"); dojo.require("dijit.form.TextBox");
dojo.require("dijit.form.ComboBox");
dojo.require("dijit.form.ValidationTextBox"); dojo.require("dijit.form.ValidationTextBox");
dojo.require("dijit.InlineEditBox"); dojo.require("dijit.InlineEditBox");
dojo.require("dijit.layout.AccordionContainer"); dojo.require("dijit.layout.AccordionContainer");
@ -500,6 +501,10 @@ function init() {
if (!isCdmMode()) { if (!isCdmMode()) {
_widescreen_mode = !_widescreen_mode; _widescreen_mode = !_widescreen_mode;
// reset stored sizes because geometry changed
setCookie("ttrss_ci_width", 0);
setCookie("ttrss_ci_height", 0);
switchPanelMode(_widescreen_mode); switchPanelMode(_widescreen_mode);
} }
}; };
@ -550,25 +555,11 @@ function init_second_stage() {
updateFeedList(); updateFeedList();
closeArticlePanel(); closeArticlePanel();
_widescreen_mode = getInitParam("widescreen");
switchPanelMode(_widescreen_mode);
if (parseInt(getCookie("ttrss_fh_width")) > 0) { if (parseInt(getCookie("ttrss_fh_width")) > 0) {
dijit.byId("feeds-holder").domNode.setStyle( dijit.byId("feeds-holder").domNode.setStyle(
{width: getCookie("ttrss_fh_width") + "px" }); {width: getCookie("ttrss_fh_width") + "px" });
} }
if (parseInt(getCookie("ttrss_ci_width")) > 0) {
if (_widescreen_mode) {
dijit.byId("content-insert").domNode.setStyle(
{width: getCookie("ttrss_ci_width") + "px" });
} else {
dijit.byId("content-insert").domNode.setStyle(
{height: getCookie("ttrss_ci_height") + "px" });
}
}
dijit.byId("main").resize(); dijit.byId("main").resize();
var tmph = dojo.connect(dijit.byId('feeds-holder'), 'resize', var tmph = dojo.connect(dijit.byId('feeds-holder'), 'resize',
@ -624,6 +615,9 @@ function init_second_stage() {
hotkeys[1] = tmp; hotkeys[1] = tmp;
setInitParam("hotkeys", hotkeys); setInitParam("hotkeys", hotkeys);
_widescreen_mode = getInitParam("widescreen");
switchPanelMode(_widescreen_mode);
console.log("second stage ok"); console.log("second stage ok");
if (getInitParam("simple_update")) { if (getInitParam("simple_update")) {
@ -706,6 +700,10 @@ function quickMenuGo(opid) {
if (!isCdmMode()) { if (!isCdmMode()) {
_widescreen_mode = !_widescreen_mode; _widescreen_mode = !_widescreen_mode;
// reset stored sizes because geometry changed
setCookie("ttrss_ci_width", 0);
setCookie("ttrss_ci_height", 0);
switchPanelMode(_widescreen_mode); switchPanelMode(_widescreen_mode);
} }
break; break;
@ -989,6 +987,12 @@ function handle_rpc_json(transport, scheduled_call) {
try { try {
var reply = JSON.parse(transport.responseText); var reply = JSON.parse(transport.responseText);
var netalert_dijit = dijit.byId("net-alert");
var netalert = false;
if (netalert_dijit)
netalert = netalert_dijit.domNode;
if (reply) { if (reply) {
var error = reply['error']; var error = reply['error'];
@ -1035,16 +1039,21 @@ function handle_rpc_json(transport, scheduled_call) {
if (runtime_info) if (runtime_info)
parse_runtime_info(runtime_info); parse_runtime_info(runtime_info);
Element.hide(dijit.byId("net-alert").domNode); if (netalert) Element.hide(netalert);
} else { } else {
//notify_error("Error communicating with server."); if (netalert)
Element.show(dijit.byId("net-alert").domNode); Element.show(netalert);
else
notify_error("Communication problem with server.");
} }
} catch (e) { } catch (e) {
Element.show(dijit.byId("net-alert").domNode); if (netalert)
//notify_error("Error communicating with server."); Element.show(netalert);
else
notify_error("Communication problem with server.");
console.log(e); console.log(e);
//exception_error("handle_rpc_json", e, transport); //exception_error("handle_rpc_json", e, transport);
} }
@ -1064,11 +1073,13 @@ function switchPanelMode(wide) {
dijit.byId("content-insert").domNode.setStyle({width: '50%', dijit.byId("content-insert").domNode.setStyle({width: '50%',
height: 'auto', height: 'auto',
borderLeftWidth: '1px',
borderLeftColor: '#c0c0c0',
borderTopWidth: '0px' }); borderTopWidth: '0px' });
$("headlines-toolbar").setStyle({ borderBottomWidth: '0px' }); if (parseInt(getCookie("ttrss_ci_width")) > 0) {
dijit.byId("content-insert").domNode.setStyle(
{width: getCookie("ttrss_ci_width") + "px" });
}
$("headlines-frame").setStyle({ borderBottomWidth: '0px' }); $("headlines-frame").setStyle({ borderBottomWidth: '0px' });
$("headlines-frame").addClassName("wide"); $("headlines-frame").addClassName("wide");
@ -1078,10 +1089,12 @@ function switchPanelMode(wide) {
dijit.byId("content-insert").domNode.setStyle({width: 'auto', dijit.byId("content-insert").domNode.setStyle({width: 'auto',
height: '50%', height: '50%',
borderLeftWidth: '0px', borderTopWidth: '0px'});
borderTopWidth: '1px'});
$("headlines-toolbar").setStyle({ borderBottomWidth: '1px' }); if (parseInt(getCookie("ttrss_ci_height")) > 0) {
dijit.byId("content-insert").domNode.setStyle(
{height: getCookie("ttrss_ci_height") + "px" });
}
$("headlines-frame").setStyle({ borderBottomWidth: '1px' }); $("headlines-frame").setStyle({ borderBottomWidth: '1px' });
$("headlines-frame").removeClassName("wide"); $("headlines-frame").removeClassName("wide");

View file

@ -87,8 +87,12 @@ function headlines_callback2(transport, offset, background, infscroll_req) {
dijit.byId("headlines-frame").attr('content', dijit.byId("headlines-frame").attr('content',
reply['headlines']['content']); reply['headlines']['content']);
dijit.byId("headlines-toolbar").attr('content', //dijit.byId("headlines-toolbar").attr('content',
reply['headlines']['toolbar']); // reply['headlines']['toolbar']);
dojo.html.set($("headlines-toolbar"),
reply['headlines']['toolbar'],
{parseContent: true});
$$("#headlines-frame > div[id*=RROW]").each(function(row) { $$("#headlines-frame > div[id*=RROW]").each(function(row) {
if (loaded_article_ids.indexOf(row.id) != -1) { if (loaded_article_ids.indexOf(row.id) != -1) {
@ -104,6 +108,10 @@ function headlines_callback2(transport, offset, background, infscroll_req) {
initHeadlinesMenu(); initHeadlinesMenu();
if (_infscroll_disable)
hsp.innerHTML = "<a href='#' onclick='openNextUnreadFeed()'>" +
__("Click to open next unread feed.") + "</a>";
if (_search_query) { if (_search_query) {
$("feed_title").innerHTML += "<span id='cancel_search'>" + $("feed_title").innerHTML += "<span id='cancel_search'>" +
" (<a href='#' onclick='cancelSearch()'>" + __("Cancel search") + "</a>)" + " (<a href='#' onclick='cancelSearch()'>" + __("Cancel search") + "</a>)" +
@ -143,9 +151,9 @@ function headlines_callback2(transport, offset, background, infscroll_req) {
if (!hsp) hsp = new Element("DIV", {"id": "headlines-spacer"}); if (!hsp) hsp = new Element("DIV", {"id": "headlines-spacer"});
if (getInitParam("cdm_auto_catchup") == 1) { // if (getInitParam("cdm_auto_catchup") == 1) {
c.domNode.appendChild(hsp); c.domNode.appendChild(hsp);
} // }
console.log("added " + new_elems.size() + " headlines"); console.log("added " + new_elems.size() + " headlines");
@ -172,7 +180,8 @@ function headlines_callback2(transport, offset, background, infscroll_req) {
var hsp = $("headlines-spacer"); var hsp = $("headlines-spacer");
if (hsp) hsp.innerHTML = ""; if (hsp) hsp.innerHTML = "<a href='#' onclick='openNextUnreadFeed()'>" +
__("Click to open next unread feed.") + "</a>";
} }
} }
@ -190,14 +199,11 @@ function headlines_callback2(transport, offset, background, infscroll_req) {
else else
request_counters(true); request_counters(true);
} else if (transport.responseText) { } else {
console.error("Invalid object received: " + transport.responseText); console.error("Invalid object received: " + transport.responseText);
dijit.byId("headlines-frame").attr('content', "<div class='whiteBox'>" + dijit.byId("headlines-frame").attr('content', "<div class='whiteBox'>" +
__('Could not update headlines (invalid object received - see error console for details)') + __('Could not update headlines (invalid object received - see error console for details)') +
"</div>"); "</div>");
} else {
//notify_error("Error communicating with server.");
Element.show(dijit.byId("net-alert").domNode);
} }
_infscroll_request_sent = 0; _infscroll_request_sent = 0;
@ -314,13 +320,11 @@ function article_callback2(transport, id) {
// return; // return;
// } // }
} else if (transport.responseText) { } else {
console.error("Invalid object received: " + transport.responseText); console.error("Invalid object received: " + transport.responseText);
render_article("<div class='whiteBox'>" + render_article("<div class='whiteBox'>" +
__('Could not display article (invalid object received - see error console for details)') + "</div>"); __('Could not display article (invalid object received - see error console for details)') + "</div>");
} else {
Element.show(dijit.byId("net-alert").domNode);
} }
var unread_in_buffer = $$("#headlines-frame > div[id*=RROW][class*=Unread]").length var unread_in_buffer = $$("#headlines-frame > div[id*=RROW][class*=Unread]").length
@ -961,10 +965,12 @@ function getLoadedArticleIds() {
} }
// mode = all,none,unread,invert,marked,published // mode = all,none,unread,invert,marked,published
function selectArticles(mode) { function selectArticles(mode, query) {
try { try {
var children = $$("#headlines-frame > div[id*=RROW]"); if (!query) query = "#headlines-frame > div[id*=RROW]";
var children = $$(query);
children.each(function(child) { children.each(function(child) {
var id = child.id.replace("RROW-", ""); var id = child.id.replace("RROW-", "");
@ -1243,7 +1249,7 @@ function postMouseOut(id) {
function unpackVisibleHeadlines() { function unpackVisibleHeadlines() {
try { try {
if (!isCdmMode()) return; if (!isCdmMode() || !getInitParam("cdm_expanded")) return;
$$("#headlines-frame > div[id*=RROW]").each( $$("#headlines-frame > div[id*=RROW]").each(
function(child) { function(child) {
@ -1306,15 +1312,20 @@ function headlines_scroll_handler(e) {
((e.scrollTop + e.offsetHeight) / e.scrollHeight >= 0.7))) { ((e.scrollTop + e.offsetHeight) / e.scrollHeight >= 0.7))) {
if (hsp) if (hsp)
hsp.innerHTML = "<img src='images/indicator_tiny.gif'> " + hsp.innerHTML = "<span class='loading'><img src='images/indicator_tiny.gif'> " +
__("Loading, please wait..."); __("Loading, please wait...") + "</span>";
loadMoreHeadlines(); loadMoreHeadlines();
return; return;
} }
} else { } else {
if (hsp) hsp.innerHTML = ""; if (hsp)
if (_infscroll_disable)
hsp.innerHTML = "<a href='#' onclick='openNextUnreadFeed()'>" +
__("Click to open next unread feed.") + "</a>";
else
hsp.innerHTML = "";
} }
if (isCdmMode()) { if (isCdmMode()) {
@ -1350,6 +1361,20 @@ function headlines_scroll_handler(e) {
500); 500);
} }
} }
if (_infscroll_disable) {
var child = $$("#headlines-frame div[id*=RROW]").last();
if (child && $("headlines-frame").scrollTop >
(child.offsetTop + child.offsetHeight - 50)) {
console.log("we seem to be at an end");
if (getInitParam("on_catchup_show_next_feed") == "1") {
openNextUnreadFeed();
}
}
}
} }
} catch (e) { } catch (e) {
@ -1357,6 +1382,16 @@ function headlines_scroll_handler(e) {
} }
} }
function openNextUnreadFeed() {
try {
var is_cat = activeFeedIsCat();
var nuf = getNextUnreadFeed(getActiveFeedId(), is_cat);
if (nuf) viewfeed(nuf, '', is_cat);
} catch (e) {
exception_error("openNextUnreadFeed", e);
}
}
function catchupBatchedArticles() { function catchupBatchedArticles() {
try { try {
if (catchup_id_batch.length > 0 && !_infscroll_request_sent) { if (catchup_id_batch.length > 0 && !_infscroll_request_sent) {
@ -1761,7 +1796,8 @@ function cdmClicked(event, id) {
return !event.shiftKey; return !event.shiftKey;
} }
} else { } else if (event.target.parents(".cdmHeader").length > 0) {
toggleSelected(id, true); toggleSelected(id, true);
var elem = $("RROW-" + id); var elem = $("RROW-" + id);
@ -2107,6 +2143,72 @@ function initHeadlinesMenu() {
menu.startup(); menu.startup();
/* vgroup feed title menu */
var nodes = $$("#headlines-frame > div[class='cdmFeedTitle']");
var ids = [];
nodes.each(function(node) {
ids.push(node.id);
});
if (ids.length > 0) {
if (dijit.byId("headlinesFeedTitleMenu"))
dijit.byId("headlinesFeedTitleMenu").destroyRecursive();
var menu = new dijit.Menu({
id: "headlinesFeedTitleMenu",
targetNodeIds: ids,
});
var tmph = dojo.connect(menu, '_openMyself', function (event) {
var callerNode = event.target, match = null, tries = 0;
while (match == null && callerNode && tries <= 3) {
console.log(callerNode.id);
match = callerNode.id.match("^[A-Z]+[-]([0-9]+)$");
callerNode = callerNode.parentNode;
++tries;
console.log(match[1]);
}
if (match) this.callerRowId = parseInt(match[1]);
});
menu.addChild(new dijit.MenuItem({
label: __("Select articles in group"),
onClick: function(event) {
selectArticles("all",
"#headlines-frame > div[id*=RROW]"+
"[orig-feed-id='"+menu.callerRowId+"']");
}}));
menu.addChild(new dijit.MenuItem({
label: __("Mark group as read"),
onClick: function(event) {
selectArticles("none");
selectArticles("all",
"#headlines-frame > div[id*=RROW]"+
"[orig-feed-id='"+menu.callerRowId+"']");
catchupSelection();
}}));
menu.addChild(new dijit.MenuItem({
label: __("Mark feed as read"),
onClick: function(event) {
catchupFeedInGroup(menu.callerRowId);
}}));
menu.startup();
}
} catch (e) { } catch (e) {
exception_error("initHeadlinesMenu", e); exception_error("initHeadlinesMenu", e);
} }

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more