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
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
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 {
const API_LEVEL = 7;
const API_LEVEL = 9;
const STATUS_OK = 0;
const STATUS_ERR = 1;
@ -77,6 +77,7 @@ class API extends Handler {
$this->wrap(self::STATUS_OK, array("session_id" => session_id(),
"api_level" => self::API_LEVEL));
} 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"));
}
} else {
@ -199,9 +200,13 @@ class API extends Handler {
$include_nested = sql_bool_to_bool($_REQUEST["include_nested"]);
$sanitize_content = !isset($_REQUEST["sanitize"]) ||
sql_bool_to_bool($_REQUEST["sanitize"]);
$force_update = sql_bool_to_bool($_REQUEST["force_update"]);
$override_order = false;
switch ($_REQUEST["order_by"]) {
case "title":
$override_order = "ttrss_entries.title";
break;
case "date_reverse":
$override_order = "score DESC, date_entered, updated";
break;
@ -218,7 +223,7 @@ class API extends Handler {
$headlines = $this->api_get_headlines($feed_id, $limit, $offset,
$filter, $is_cat, $show_excerpt, $show_content, $view_mode, $override_order,
$include_attachments, $since_id, $search, $search_mode,
$include_nested, $sanitize_content);
$include_nested, $sanitize_content, $force_update);
$this->wrap(self::STATUS_OK, $headlines);
} else {
@ -310,7 +315,7 @@ class API extends Handler {
if ($article_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,
author,(SELECT title FROM ttrss_feeds WHERE id = feed_id) AS feed_title
FROM ttrss_entries,ttrss_user_entries
@ -342,7 +347,9 @@ class API extends Handler {
"feed_id" => $line["feed_id"],
"attachments" => $attachments,
"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) {
@ -423,7 +430,7 @@ class API extends Handler {
$checked = false;
foreach ($article_labels as $al) {
if ($al[0] == $line['id']) {
if (feed_to_label_id($al[0]) == $line['id']) {
$checked = true;
break;
}
@ -447,7 +454,7 @@ class API extends Handler {
$assign = (bool) $this->dbh->escape_string($_REQUEST['assign']) == "true";
$label = $this->dbh->escape_string(label_find_caption(
$label_id, $_SESSION["uid"]));
feed_to_label_id($label_id), $_SESSION["uid"]));
$num_updated = 0;
@ -511,7 +518,7 @@ class API extends Handler {
if ($unread || !$unread_only) {
$row = array(
"id" => $cv["id"],
"id" => (int) $cv["id"],
"title" => $cv["description"],
"unread" => $cv["counter"],
"cat_id" => -2,
@ -557,7 +564,7 @@ class API extends Handler {
if ($unread || !$unread_only) {
$row = array(
"id" => $line["id"],
"id" => (int) $line["id"],
"title" => $line["title"],
"unread" => $unread,
"is_cat" => true,
@ -626,7 +633,28 @@ class API extends Handler {
$filter, $is_cat, $show_excerpt, $show_content, $view_mode, $order,
$include_attachments, $since_id,
$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,
$view_mode, $is_cat, $search, $search_mode,
@ -638,7 +666,7 @@ class API extends Handler {
$headlines = array();
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) {
$line = $p->hook_query_headlines($line, 100, true);
}
@ -700,6 +728,8 @@ class API extends Handler {
$headline_row["author"] = $line["author"];
$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) {
$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"]);
$cids = explode(",", $this->dbh->escape_string($_REQUEST["cids"]));
$mode = $this->dbh->escape_string($_REQUEST["mode"]);
$omode = $this->dbh->escape_string($_REQUEST["omode"]);
// in prefetch mode we only output requested cids, main article
// 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
$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) {
$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 "<div class=\"prefFeedOPMLHolder\">";
$owner_uid = $_SESSION["uid"];
$this->dbh->query("BEGIN");
@ -176,7 +175,7 @@ class Dlg extends Handler_Protected {
while ($row = $this->dbh->fetch_assoc($result)) {
$tmp = htmlspecialchars($row["tag_name"]);
print "<option value=\"" . str_replace(" ", "%20", $tmp) . "\">$tmp</option>";
print "<option value=\"$tmp\">$tmp</option>";
}
print "</select>";

View file

@ -4,5 +4,7 @@ class FeedEnclosure {
public $type;
public $length;
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);
if ($base)
return rewrite_relative_url($base, $link->getAttribute("href"));
return rewrite_relative_url($base, trim($link->getAttribute("href")));
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);
if ($title) {
return $title->nodeValue;
return trim($title->nodeValue);
}
}
@ -106,13 +106,13 @@ class FeedItem_Atom extends FeedItem_Common {
foreach ($categories as $cat) {
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);
foreach ($categories as $cat) {
array_push($cats, $cat->nodeValue);
array_push($cats, trim($cat->nodeValue));
}
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) {
$enc = new FeedEnclosure();
@ -145,6 +145,8 @@ class FeedItem_Atom extends FeedItem_Common {
$enc->type = $enclosure->getAttribute("type");
$enc->link = $enclosure->getAttribute("url");
$enc->length = $enclosure->getAttribute("length");
$enc->height = $enclosure->getAttribute("height");
$enc->width = $enclosure->getAttribute("width");
$desc = $this->xpath->query("media:description", $enclosure)->item(0);
if ($desc) $enc->title = strip_tags($desc->nodeValue);
@ -152,6 +154,46 @@ class FeedItem_Atom extends FeedItem_Common {
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;
}

View file

@ -44,13 +44,26 @@ abstract class FeedItem_Common extends FeedItem {
}
}
// todo
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() {
$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) {
return $comments->nodeValue;

View file

@ -33,20 +33,20 @@ class FeedItem_RSS extends FeedItem_Common {
|| $link->getAttribute("rel") == "alternate"
|| $link->getAttribute("rel") == "standout")) {
return $link->getAttribute("href");
return trim($link->getAttribute("href"));
}
}
$link = $this->elem->getElementsByTagName("guid")->item(0);
if ($link && $link->hasAttributes() && $link->getAttribute("isPermaLink") == "true") {
return $link->nodeValue;
return trim($link->nodeValue);
}
$link = $this->elem->getElementsByTagName("link")->item(0);
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);
if ($title) {
return $title->nodeValue;
return trim($title->nodeValue);
}
}
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) {
return $content->nodeValue;
if ($contentA && !$contentB) {
return $contentA->nodeValue;
}
$content = $this->elem->getElementsByTagName("description")->item(0);
if ($content) {
return $content->nodeValue;
if ($contentB && !$contentA) {
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();
foreach ($categories as $cat) {
array_push($cats, $cat->nodeValue);
array_push($cats, trim($cat->nodeValue));
}
$categories = $this->xpath->query("dc:subject", $this->elem);
foreach ($categories as $cat) {
array_push($cats, $cat->nodeValue);
array_push($cats, trim($cat->nodeValue));
}
return $cats;
@ -108,11 +113,13 @@ class FeedItem_RSS extends FeedItem_Common {
$enc->type = $enclosure->getAttribute("type");
$enc->link = $enclosure->getAttribute("url");
$enc->length = $enclosure->getAttribute("length");
$enc->height = $enclosure->getAttribute("height");
$enc->width = $enclosure->getAttribute("width");
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) {
$enc = new FeedEnclosure();
@ -120,6 +127,8 @@ class FeedItem_RSS extends FeedItem_Common {
$enc->type = $enclosure->getAttribute("type");
$enc->link = $enclosure->getAttribute("url");
$enc->length = $enclosure->getAttribute("length");
$enc->height = $enclosure->getAttribute("height");
$enc->width = $enclosure->getAttribute("width");
$desc = $this->xpath->query("media:description", $enclosure)->item(0);
if ($desc) $enc->title = strip_tags($desc->nodeValue);
@ -127,6 +136,46 @@ class FeedItem_RSS extends FeedItem_Common {
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;
}

View file

@ -2,6 +2,7 @@
class FeedParser {
private $doc;
private $error;
private $libxml_errors = array();
private $items;
private $link;
private $title;
@ -12,6 +13,16 @@ class FeedParser {
const FEED_RSS = 1;
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) {
libxml_use_internal_errors(true);
libxml_clear_errors();
@ -23,32 +34,8 @@ class FeedParser {
$error = libxml_get_last_error();
// libxml compiled without iconv?
if ($error && ($error->code == 32 || $error->code == 9)) {
if (preg_match('/^(<\?xml[\t\n\r ].*?encoding=["\'])(.+?)(["\'].*?\?>)/s', $data, $matches) === 1) {
$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 ($error && $error->code == 32) {
$data = $this->normalize_encoding($data);
if ($data) {
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();
$this->items = array();
@ -76,12 +97,13 @@ class FeedParser {
$xpath->registerNamespace('slash', 'http://purl.org/rss/1.0/modules/slash/');
$xpath->registerNamespace('dc', 'http://purl.org/dc/elements/1.1/');
$xpath->registerNamespace('content', 'http://purl.org/rss/1.0/modules/content/');
$xpath->registerNamespace('thread', 'http://purl.org/syndication/thread/1.0');
$this->xpath = $xpath;
$root = $xpath->query("(//atom03:feed|//atom:feed|//channel|//rdf:rdf|//rdf:RDF)");
if ($root) {
if ($root && $root->length > 0) {
$root = $root->item(0);
if ($root) {
@ -183,6 +205,10 @@ class FeedParser {
break;
}
if ($this->title) $this->title = trim($this->title);
if ($this->link) $this->link = trim($this->link);
} else {
if( !isset($this->error) ){
$this->error = "Unknown/unsupported feed type";
@ -205,6 +231,10 @@ class FeedParser {
return $this->error;
}
function errors() {
return $this->libxml_errors;
}
function get_link() {
return $this->link;
}
@ -226,7 +256,7 @@ class FeedParser {
foreach ($links as $link) {
if (!$rel || $link->hasAttribute('rel') && $link->getAttribute('rel') == $rel) {
array_push($rv, $link->getAttribute('href'));
array_push($rv, trim($link->getAttribute('href')));
}
}
break;
@ -235,7 +265,7 @@ class FeedParser {
foreach ($links as $link) {
if (!$rel || $link->hasAttribute('rel') && $link->getAttribute('rel') == $rel) {
array_push($rv, $link->getAttribute('href'));
array_push($rv, trim($link->getAttribute('href')));
}
}
break;

View file

@ -13,12 +13,6 @@ class Feeds extends Handler_Protected {
$feed_id, $is_cat, $search,
$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()";
$archive_sel_link = "archiveSelection()";
@ -43,6 +37,8 @@ class Feeds extends Handler_Protected {
$search_q = "";
}
$reply .= "<span class=\"holder\">";
$rss_link = htmlspecialchars(get_self_url_prefix() .
"/public.php?op=rss&id=$feed_id$cat_q$search_q");
@ -50,8 +46,14 @@ class Feeds extends Handler_Protected {
$error_class = $error ? "error" : "";
$reply .= "<span class='r'>";
$reply .= "<span id='selected_prompt'></span>";
$reply .= "<span class='r'>
<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'>";
if ($feed_site_url) {
@ -60,11 +62,11 @@ class Feeds extends Handler_Protected {
$target = "target=\"_blank\"";
$reply .= "<a title=\"$last_updated\" $target href=\"$feed_site_url\">".
truncate_string($feed_title,30)."</a>";
truncate_string($feed_title, 30)."</a>";
if ($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 {
@ -73,17 +75,16 @@ class Feeds extends Handler_Protected {
$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>";
// 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_unread_link\">".__('Unread')."</a>,
<a href=\"#\" onclick=\"$sel_inv_link\">".__('Invert')."</a>,
@ -132,14 +133,14 @@ class Feeds extends Handler_Protected {
$reply .= "</select>";
//$reply .= "</div>";
//$reply .= "</h2";
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;
}
@ -148,7 +149,7 @@ class Feeds extends Handler_Protected {
$override_order = false, $include_children = false) {
if (isset($_REQUEST["DevForceUpdate"]))
header("Content-Type: text/plain");
header("Content-Type: text/plain; charset=utf-8");
$disable_cache = false;
@ -247,6 +248,8 @@ class Feeds extends Handler_Protected {
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);
$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) {
$lnum = $offset;
@ -285,14 +294,12 @@ class Feeds extends Handler_Protected {
$num_unread = 0;
$cur_feed_title = '';
$fresh_intl = get_pref("FRESH_ARTICLE_MAX_AGE") * 60 * 60;
if ($_REQUEST["debug"]) $timing_info = print_checkpoint("PS", $timing_info);
$expand_cdm = get_pref('CDM_EXPANDED');
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) {
$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('VFEED_GROUP_BY_FEED')) {
if ($vfeed_group_enabled) {
if ($feed_id != $vgroup_last_feed && $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);
$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'>".
"<div style=\"float : right\">$feed_icon_img</div>".
"<a class='title' href=\"#\" onclick=\"viewfeed($feed_id)\">".
$line["feed_title"]."</a> $vf_catchup_link</div>";
$reply['content'] .= "<div id='FTITLE-$feed_id' class='cdmFeedTitle'>".
"<div style='float : right'>$feed_icon_img</div>".
"<a class='title' href=\"#\" onclick=\"viewfeed($feed_id)\">". $line["feed_title"]."</a>
$vf_catchup_link</div>";
}
}
@ -443,7 +450,7 @@ class Feeds extends Handler_Protected {
$mouseover_attrs = "onmouseover='postMouseIn(event, $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'>";
@ -473,17 +480,18 @@ class Feeds extends Handler_Protected {
$reply['content'] .= "</div>";
$reply['content'] .= "<span class=\"hlUpdated\">";
if (!get_pref('VFEED_GROUP_BY_FEED')) {
if (!$vfeed_group_enabled) {
if (@$line["feed_title"]) {
$rgba = @$rgba_cache[$feed_id];
$reply['content'] .= "<a class=\"hlFeed\" style=\"background : rgba($rgba, 0.3)\" href=\"#\" onclick=\"viewfeed($feed_id)\">".
truncate_string($line["feed_title"],30)."</a>";
$reply['content'] .= "<span class=\"hlFeed\"><a style=\"background : rgba($rgba, 0.3)\" href=\"#\" onclick=\"viewfeed($feed_id)\">".
truncate_string($line["feed_title"],30)."</a></span>";
}
}
$reply['content'] .= "<span class=\"hlUpdated\">";
$reply['content'] .= "<div title='$date_entered_fmt'>$updated_fmt</div>
</span>";
@ -491,12 +499,12 @@ class Feeds extends Handler_Protected {
$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)\"
style=\"cursor : pointer\"
title=\"".htmlspecialchars($line['feed_title'])."\">
$feed_icon_img<span>";
$feed_icon_img</span>";
}
$reply['content'] .= "</div>";
@ -516,7 +524,7 @@ class Feeds extends Handler_Protected {
$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) {
$cur_feed_title = $line["feed_title"];
@ -524,7 +532,7 @@ class Feeds extends Handler_Protected {
$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);
@ -534,7 +542,7 @@ class Feeds extends Handler_Protected {
//$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>".
"<a href=\"#\" class='title' onclick=\"viewfeed($feed_id)\">".
$line["feed_title"]."</a> $vf_catchup_link</div>";
@ -547,9 +555,9 @@ class Feeds extends Handler_Protected {
$expanded_class = $expand_cdm ? "expanded" : "expandable";
$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'] .= "<input dojoType=\"dijit.form.CheckBox\"
@ -592,7 +600,7 @@ class Feeds extends Handler_Protected {
$reply['content'] .= "</span>";
if (!get_pref('VFEED_GROUP_BY_FEED')) {
if (!$vfeed_group_enabled) {
if (@$line["feed_title"]) {
$rgba = @$rgba_cache[$feed_id];
@ -725,7 +733,7 @@ class Feeds extends Handler_Protected {
$reply['content'] .= "</div>";
$reply['content'] .= "</div>";
$reply['content'] .= "</div><hr/>";
$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);
$omode = $this->dbh->escape_string($_REQUEST["omode"]);
$feed = $this->dbh->escape_string($_REQUEST["feed"]);
$method = $this->dbh->escape_string($_REQUEST["m"]);
$view_mode = $this->dbh->escape_string($_REQUEST["view_mode"]);
@ -897,7 +903,7 @@ class Feeds extends Handler_Protected {
//$topmost_article_ids = $ret[0];
$headlines_count = $ret[1];
$returned_feed = $ret[2];
/* $returned_feed = $ret[2]; */
$disable_cache = $ret[3];
$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=\"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=\"dlgSecCont\">";
@ -1073,20 +1083,18 @@ class Feeds extends Handler_Protected {
print " <select dojoType=\"dijit.form.Select\" name=\"limit\" onchange=\"dijit.byId('feedBrowserDlg').update()\">";
foreach (array(25, 50, 100, 200) as $l) {
$issel = ($l == $limit) ? "selected=\"1\"" : "";
print "<option $issel value=\"$l\">$l</option>";
//$issel = ($l == $limit) ? "selected=\"1\"" : "";
print "<option value=\"$l\">$l</option>";
}
print "</select> ";
print "</div>";
$owner_uid = $_SESSION["uid"];
require_once "feedbrowser.php";
print "<ul class='browseFeedList' id='browseFeedList'>";
print make_feed_browser($search, 25);
print make_feed_browser("", 25);
print "</ul>";
print "<div align='center'>
@ -1145,7 +1153,7 @@ class Feeds extends Handler_Protected {
print "<div class=\"dlgButtons\">";
if (!SPHINX_ENABLED) {
if (count(PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH)) == 0) {
print "<div style=\"float : left\">
<a class=\"visibleLink\" target=\"_blank\" href=\"http://tt-rss.org/wiki/SearchSyntax\">".__("Search syntax")."</a>
</div>";

View file

@ -3,7 +3,7 @@ class Handler_Public extends Handler {
private function generate_syndicated_feed($owner_uid, $feed, $is_cat,
$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";
@ -15,11 +15,15 @@ class Handler_Public extends Handler {
if (!$limit) $limit = 60;
$date_sort_field = "date_entered DESC, updated DESC";
$date_check_field = "date_entered";
if ($feed == -2)
if ($feed == -2 && !$is_cat) {
$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_check_field = "last_marked";
}
switch ($order) {
case "title":
@ -33,15 +37,18 @@ class Handler_Public extends Handler {
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,
1, $view_mode, $is_cat, $search, $search_mode,
$date_sort_field, $offset, $owner_uid,
false, 0, false, true);
false, 0, true, true, false, false, $start_ts);
$result = $qfh_ret[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']) &&
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $ts) {
@ -56,13 +63,13 @@ class Handler_Public extends Handler {
$qfh_ret = queryFeedHeadlines($feed,
$limit, $view_mode, $is_cat, $search, $search_mode,
$date_sort_field, $offset, $owner_uid,
false, 0, false, true);
false, 0, true, true, false, false, $start_ts);
$result = $qfh_ret[0];
$feed_title = htmlspecialchars($qfh_ret[1]);
$feed_site_url = $qfh_ret[2];
$last_error = $qfh_ret[3];
/* $last_error = $qfh_ret[3]; */
$feed_self_url = get_self_url_prefix() .
"/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);
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) {
$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_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']) {
$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_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);
@ -269,16 +277,22 @@ class Handler_Public extends Handler {
function pubsub() {
$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_url = $this->dbh->escape_string($_REQUEST['hub_topic']);
if (!$feed_url) $feed_url = $this->dbh->escape_string($_REQUEST['hub.topic']);
if (!PUBSUBHUBBUB_ENABLED) {
header('HTTP/1.0 404 Not Found');
echo "404 Not found";
echo "404 Not found (Disabled by server)";
return;
}
// 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
WHERE id = '$feed_id'");
@ -287,7 +301,8 @@ class Handler_Public extends Handler {
$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") {
$this->dbh->query("UPDATE ttrss_feeds SET pubsub_state = 2
@ -316,11 +331,11 @@ class Handler_Public extends Handler {
}
} else {
header('HTTP/1.0 404 Not Found');
echo "404 Not found";
echo "404 Not found (URL check failed)";
}
} else {
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"]);
$view_mode = $this->dbh->escape_string($_REQUEST["view-mode"]);
$order = $this->dbh->escape_string($_REQUEST["order"]);
$start_ts = $this->dbh->escape_string($_REQUEST["ts"]);
$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';
@ -385,24 +401,24 @@ class Handler_Public extends Handler {
if ($owner_id) {
$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 {
header('HTTP/1.1 403 Forbidden');
}
}
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() {
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() {
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() {
@ -411,11 +427,14 @@ class Handler_Public extends Handler {
}
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");
javascript_tag("lib/prototype.js");
javascript_tag("lib/scriptaculous/scriptaculous.js?load=effects,dragdrop,controls");
echo stylesheet_tag("css/utility.css");
echo stylesheet_tag("css/dijit.css");
echo javascript_tag("lib/prototype.js");
echo javascript_tag("lib/scriptaculous/scriptaculous.js?load=effects,controls");
print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
</head><body id='sharepopup'>";
@ -561,7 +580,7 @@ class Handler_Public extends Handler {
}
} else {
$_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']) {
@ -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() {
if (SINGLE_USER_MODE) {
login_sequence();
@ -587,6 +618,9 @@ class Handler_Public extends Handler {
<title>Tiny Tiny RSS</title>
<link rel=\"stylesheet\" type=\"text/css\" href=\"css/utility.css\">
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
<link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
<link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
</head>
<body>
<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() {
header("Content-Type: text/plain");
print json_encode(array("error" => array("code" => 7)));
@ -766,11 +713,15 @@ class Handler_Public extends Handler {
function forgotpass() {
startup_gettext();
header('Content-Type: text/html; charset=utf-8');
print "<html><head><title>Tiny Tiny RSS</title>";
@$hash = $_REQUEST["hash"];
stylesheet_tag("css/utility.css");
javascript_tag("lib/prototype.js");
header('Content-Type: text/html; charset=utf-8');
print "<html><head><title>Tiny Tiny RSS</title>
<link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
<link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">";
echo stylesheet_tag("css/utility.css");
echo javascript_tag("lib/prototype.js");
print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
</head><body id='forgotpass'>";
@ -781,8 +732,45 @@ class Handler_Public extends Handler {
@$method = $_POST['method'];
if (!$method) {
print_notice(__("You will need to provide valid account name and email. New password will be sent on your email address."));
if ($hash) {
$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 "<input type='hidden' name='method' value='do'>";
@ -823,17 +811,57 @@ class Handler_Public extends Handler {
} else {
print_notice("Password reset instructions are being sent to your email address.");
$result = $this->dbh->query("SELECT id FROM ttrss_users
WHERE login = '$login' AND email = '$email'");
if ($this->dbh->num_rows($result) != 0) {
$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\">
<input type=\"submit\" value=\"".__("Return to Tiny Tiny RSS")."\">
@ -872,6 +900,8 @@ class Handler_Public extends Handler {
<title>Database Updater</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="stylesheet" type="text/css" href="css/utility.css"/>
<link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
<link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">
</head>
<style type="text/css">
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));
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));
if (!$feed_url) $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($attrs->getNamedItem('xmlURL')->nodeValue);
$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_QUERY_HEADLINES = 23;
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_SYSTEM = 2;
@ -75,6 +79,16 @@ class PluginHost {
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() {
return $this->plugins;
}
@ -99,7 +113,7 @@ class PluginHost {
function del_hook($type, $sender) {
if (is_array($this->hooks[$type])) {
$key = array_Search($this->hooks[$type], $sender);
$key = array_Search($sender, $this->hooks[$type]);
if ($key !== FALSE) {
unset($this->hooks[$type][$key]);
}

View file

@ -55,6 +55,7 @@ class Pref_Feeds extends Handler_Protected {
$cat['unread'] = 0;
$cat['child_unread'] = 0;
$cat['auxcounter'] = 0;
$cat['parent_id'] = $cat_id;
$cat['items'] = $this->get_category_items($line['id']);
@ -395,7 +396,7 @@ class Pref_Feeds extends Handler_Protected {
# print_r($data['items']);
if (is_array($data) && is_array($data['items'])) {
$cat_order_id = 0;
# $cat_order_id = 0;
$data_map = array();
$root_item = false;
@ -494,7 +495,7 @@ class Pref_Feeds extends Handler_Protected {
$feed_id = $this->dbh->escape_string($_REQUEST["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
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=\"feed_id\" value=\"$feed_id\">
<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>
<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>
</form>";
@ -962,7 +963,7 @@ class Pref_Feeds extends Handler_Protected {
if (!$batch) {
$result = $this->dbh->query("UPDATE ttrss_feeds SET
$this->dbh->query("UPDATE ttrss_feeds SET
$category_qpart
title = '$feed_title', feed_url = '$feed_link',
update_interval = '$upd_intl',
@ -1259,13 +1260,18 @@ class Pref_Feeds extends Handler_Protected {
$interval_qpart = "DATE_SUB(NOW(), INTERVAL 3 MONTH)";
}
$result = $this->dbh->query("SELECT COUNT(*) AS num_inactive FROM ttrss_feeds WHERE
// 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
ttrss_entries.id = ref_id AND
ttrss_user_entries.feed_id = ttrss_feeds.id) < $interval_qpart AND
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) {
$inactive_button = "<button dojoType=\"dijit.form.Button\"
@ -1573,8 +1579,6 @@ class Pref_Feeds extends Handler_Protected {
# class needed for selectTableRows()
print "<tr class=\"placeholder\" $this_row_id>";
$edit_title = htmlspecialchars($line["title"]);
# id needed for selectTableRows()
print "<td width='5%' align='center'><input
onclick='toggleSelectRow2(this);' dojoType=\"dijit.form.CheckBox\"
@ -1639,8 +1643,6 @@ class Pref_Feeds extends Handler_Protected {
# class needed for selectTableRows()
print "<tr class=\"placeholder\" $this_row_id>";
$edit_title = htmlspecialchars($line["title"]);
# id needed for selectTableRows()
print "<td width='5%' align='center'><input
onclick='toggleSelectRow2(this);' dojoType=\"dijit.form.CheckBox\"
@ -1891,7 +1893,7 @@ class Pref_Feeds extends Handler_Protected {
AND owner_uid = " . $owner_uid);
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'
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];
$articles = array();
$found = 0;
print __("Articles matching this filter:");
@ -97,15 +96,12 @@ class Pref_Filters extends Handler_Protected {
print "<table width=\"100%\" cellspacing=\"0\" id=\"prefErrorFeedList\">";
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) {
$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"];
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() {
$root = array();
@ -174,24 +204,11 @@ class Pref_Filters extends Handler_Protected {
owner_uid = ".$_SESSION["uid"]." ORDER BY order_id, title");
$action_id = -1;
$folder = array();
$folder['items'] = array();
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"]);
$match_ok = false;
@ -227,6 +244,7 @@ class Pref_Filters extends Handler_Protected {
$filter['param'] = $name[1];
$filter['checkbox'] = false;
$filter['enabled'] = sql_bool_to_bool($line["enabled"]);
$filter['rules'] = $this->getfilterrules_concise($line['id']);
if (!$filter_search || $match_ok) {
array_push($folder['items'], $filter);
@ -433,8 +451,11 @@ class Pref_Filters extends Handler_Protected {
WHERE id = ".(int)$rule["filter_type"]);
$filter_type = $this->dbh->fetch_result($result, 0, "description");
return T_sprintf("%s on %s in %s %s", strip_tags($rule["reg_exp"]),
$filter_type, $feed, isset($rule["inverse"]) ? __("(inverse)") : "");
$inverse = 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() {
@ -471,7 +492,7 @@ class Pref_Filters extends Handler_Protected {
$inverse = checkbox_to_sql_bool($this->dbh->escape_string($_REQUEST["inverse"]));
$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,
inverse = $inverse,
title = '$title'

View file

@ -181,7 +181,8 @@ class Pref_Prefs extends Handler_Protected {
global $access_level_names;
$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" */
@ -887,8 +888,9 @@ class Pref_Prefs extends Handler_Protected {
if (!$otp_enabled) {
$secret = $base32->encode(sha1($this->dbh->fetch_result($result, 0, "salt")));
$topt = new \OTPHP\TOTP($secret);
print QRcode::png($topt->provisioning_uri($login));
print QRcode::png("otpauth://totp/".urlencode($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);
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'");
if ($show_password) {

View file

@ -95,7 +95,7 @@ class RPC extends Handler_Protected {
WHERE orig_feed_id = '$id') = 0 AND
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";
}
$result = $this->dbh->query("UPDATE ttrss_user_entries SET marked = $mark,
$this->dbh->query("UPDATE ttrss_user_entries SET marked = $mark,
last_marked = NOW()
WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
@ -148,8 +148,8 @@ class RPC extends Handler_Protected {
function delete() {
$ids = $this->dbh->escape_string($_REQUEST["ids"]);
$result = $this->dbh->query("DELETE FROM ttrss_user_entries
WHERE ref_id IN ($ids) AND owner_uid = " . $_SESSION["uid"]);
$this->dbh->query("DELETE FROM ttrss_user_entries
WHERE ref_id IN ($ids) AND owner_uid = " . $_SESSION["uid"]);
purge_orphans();
@ -258,7 +258,6 @@ class RPC extends Handler_Protected {
function publ() {
$pub = $_REQUEST["pub"];
$id = $this->dbh->escape_string($_REQUEST["id"]);
$note = trim(strip_tags($this->dbh->escape_string($_REQUEST["note"])));
if ($pub == "1") {
$pub = "true";
@ -266,7 +265,7 @@ class RPC extends Handler_Protected {
$pub = "false";
}
$result = $this->dbh->query("UPDATE ttrss_user_entries SET
$this->dbh->query("UPDATE ttrss_user_entries SET
published = $pub, last_published = NOW()
WHERE ref_id = '$id' AND owner_uid = " . $_SESSION["uid"]);
@ -620,7 +619,7 @@ class RPC extends Handler_Protected {
$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
// won't try to subscribe to PUSH feed updates.
// *********************
// *** Sphinx search ***
// *********************
define('SPHINX_ENABLED', false);
// Enable fulltext search using Sphinx (http://www.sphinxsearch.com)
// Please see http://tt-rss.org/wiki/SphinxSearch for more information.
// ****************************
// *** Sphinx search plugin ***
// ****************************
define('SPHINX_SERVER', 'localhost:9312');
// Hostname:port combination for the Sphinx server.
@ -184,6 +180,12 @@
define('CHECK_FOR_NEW_VERSION', true);
// 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);
// Selectively gzip output to improve wire performance. This requires
// PHP Zlib extension on the server.
@ -211,4 +213,3 @@
// if necessary (after migrating all new options from this file).
// vim:ft=php
?>

View file

@ -52,7 +52,8 @@ div.cdmHeader input {
div.cdmContentInner {
margin : 10px;
line-height : 20px;
line-height : 1.5;
font-size : 15px;
}
div.cdmContentInner img {
@ -61,6 +62,16 @@ div.cdmContentInner img {
height : auto;
}
div.cdmContentInner h1 {
font-size : 16px;
}
div.cdmContentInner h2,
div.cdmContentInner h3,
div.cdmContentInner h4 {
font-size : 15px;
}
div.cdmFooter {
padding : 5px;
font-weight : normal;
@ -68,15 +79,25 @@ div.cdmFooter {
clear : both;
}
div.cdm {
margin-right : 4px;
}
div.cdm.expanded {
margin-top : 4px;
margin-bottom : 4px;
}
div.cdm.expanded div.cdmFooter {
border-style : solid;
border-width : 0px 0px 1px 0px;
border-color : #ddd;
}
div.cdm.expandable {
background-color : #f0f0f0;
border-width : 0px 0px 1px 0px;
border-color : #c0c0c0;
border-color : #ddd;
border-style : solid;
}
@ -98,8 +119,6 @@ div.cdm.expandable.Selected {
}
div.cdm.expandable.active {
box-shadow : inset 0px 0px 3px 0px rgba(0,0,0,0.1);
border-color : #88b0f0;
background : white ! important;
}
@ -115,10 +134,15 @@ div.cdm.expandable.active div.cdmHeader span.titleWrap {
}
div.cdm.expandable div.cdmHeader a.title {
font-weight : bold;
font-weight : 600;
color : #555;
font-size : 14px;
-webkit-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 {
@ -127,6 +151,12 @@ div.cdm.expandable.Unread div.cdmHeader a.title {
div.cdm.expandable.active div.cdmHeader a.title {
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 {
@ -134,11 +164,15 @@ div.cdm.expanded div.cdmHeader {
}
div.cdm.expanded div.cdmHeader a.title {
font-size : 14px;
font-size : 16px;
color : #999;
font-weight : bold;
font-weight : 600;
-webkit-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 {
@ -167,7 +201,7 @@ div.cdm.active div.cdmContent {
span.cdmExcerpt {
font-size : 11px;
color : #555;
color : #999;
font-weight : normal;
cursor : pointer;
}
@ -222,7 +256,6 @@ div.cdm .hlFeed a {
div.cdmContentInner p {
max-width : 650px;
text-align : justify;
-webkit-hyphens: auto;
-moz-hyphens: auto;
hyphens: auto;
@ -242,15 +275,15 @@ div.cdmHeader span.author {
div#floatingTitle {
position : absolute;
z-index : 5;
top : 25px;
top : 0px;
right : 0px;
left : 0px;
border-color : #ccc;
border-width : 1px 0px 1px 0px;
border-color : #ddd;
border-width : 0px 0px 1px 0px;
border-style : solid;
background : #fcfcfc;
background : white;
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 > * {
@ -272,11 +305,15 @@ div#floatingTitle span.author {
}
div#floatingTitle a.title {
font-size : 14px;
font-size : 16px;
color : #999;
font-weight : bold;
-webkit-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 {
@ -284,7 +321,6 @@ div#floatingTitle.Unread a.title {
}
div#floatingTitle img.anchor {
margin-right : 1px;
margin-left : 0px;
}
@ -341,4 +377,24 @@ div#floatingTitle img.hlScorePic {
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 {
color : black;
}
@ -13,12 +24,12 @@ div#pref-tabs .dijitContentPane {
}
div#pref-tabs {
box-shadow : 0px 1px 1px -1px rgba(0,0,0,0.1);
margin : 0px 5px 0px 5px;
}
div#pref-tabs .dijitContentPane h3 {
font-size : 14px;
font-weight : bold;
}
#pref-filter-wrap, #pref-filter-header, #pref-filter-content,
@ -52,11 +63,10 @@ div.prefProfileHolder, div.prefFeedOPMLHolder, div.inactiveFeedHolder {
height : 300px;
overflow : auto;
border-width : 0px 1px 1px 1px;
border-color : #c0c0c0;
border-color : #ddd;
border-style : solid;
margin : 0px 0px 5px 0px;
background-color : #ecf4ff;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
background-color : white;
}
div.filterTestHolder, div.prefFeedOPMLHolder {
border-width : 1px;
@ -66,12 +76,10 @@ ul.selfUpdateList, ul.userFeedList {
height : 200px;
overflow : auto;
list-style-type : none;
border : 1px solid #c0c0c0;
background-color : #ecf4ff;
border : 1px solid #ddd;
background-color : #f5f5f5;
margin : 0px 0px 5px 0px;
padding : 5px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
border-radius : 4px;
}
div#feedlistLoading, div#filterlistLoading, div#labellistLoading {
@ -90,7 +98,8 @@ div#feedlistLoading img, div#filterlistLoading img, div#labellistLoading {
a.bookmarklet {
color : #4684ff;
border : 1px solid #ecf4ff;
border : 1px solid #ddd;
background : #f5f5f5;
padding : 2px;
}
@ -120,7 +129,9 @@ table.prefErrorLog td.filename, table.prefErrorLog td.login, table.prefErrorLog
color : #555;
}
.dijitAccordionContainer-child {
box-shadow : inset 0px 0px 3px rgba(0,0,0,0.2);
body#ttrssPrefs hr {
border-color : #ecf4ff;
max-width : 100%;
}

View file

@ -7,34 +7,28 @@ body#ttrssMain, body#ttrssPrefs, body#ttrssLogin, body {
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 {
padding : 0px;
}
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;
margin-right : 4px;
color : #909090;
border-width : 0px 0px 1px 0px;
border-color : #ddd;
border-style : solid;
}
div.postReply div.postTitle {
overflow : hidden;
text-overflow: ellipsis;
white-space : nowrap;
font-weight : 600;
text-rendering: optimizelegibility;
font-family : "Segoe WP Semibold", "Segoe UI Semibold",
"Segoe UI Web Semibold", "Segoe UI", Ubuntu, "DejaVu Sans", "Helvetica Neue",
Helvetica, Arial, sans-serif;
}
div.postReply div.postDate {
@ -66,13 +60,10 @@ div.postReply img.tagsPic {
div.articleNote {
background-color : #fff7d5;
padding : 5px;
border-radius : 4px;
margin : 5px;
border-style : solid;
border-color : #e7d796;
border-width : 1px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
background-color : #fff7d5;
color : #9a8c59;
}
@ -87,29 +78,50 @@ div.postReply span.author {
h1 {
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 {
font-size : 16px;
font-weight : bold;
font-weight : 600;
border-width : 0px 0px 1px 0px;
border-style : solid;
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 {
font-size : 12px;
font-weight : bold;
font-size : 13px;
border-width : 0px 0px 1px 0px;
border-style : solid;
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 {
border-width : 0px 0px 1px 0px;
border-style : solid;
border-color : #c0c0c0;
max-width : 90%;
border-color : #ccc;
}
a {
@ -138,7 +150,6 @@ a:hover {
min-width : 100px;
padding : 5px;
-width : 200px;
box-shadow : 0px 0px 2px rgba(0,0,0,0.2);
}
#notify img {
@ -181,7 +192,11 @@ a:hover {
}
.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;
}
@ -189,13 +204,9 @@ a:hover {
color : black;
}
.hl.active {
box-shadow : inset 0px 0px 3px 0px rgba(0,0,0,0.1);
}
.hl.active div.hlTitle a {
color : #4684ff;
text-shadow : 1px 1px 2px #fff;
/* text-shadow : 1px 1px 2px #fff; */
}
.hl.Selected {
@ -206,18 +217,11 @@ a:hover {
color : #909090;
}
/* #headlines-frame div.hl:nth-child(even) {
background : #fafafa;
} */
#headlines-frame.normal {
}
.hl {
border-width : 0px 0px 1px 0px;
border-style : solid;
border-color : #c0c0c0;
border-color : #ddd;
padding : 1px;
}
.hl.active {
@ -227,13 +231,11 @@ a:hover {
div.filterTestHolder {
height : 300px;
overflow : auto;
border-color : #c0c0c0;
border-color : #ddd;
border-style : solid;
margin : 0px 0px 5px 0px;
background-color : #ecf4ff;
background-color : #f5f5f5;
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;
padding-left : 10px;
border-width : 0px 0px 0px 4px;
border-color : #c0c0c0;
border-color : #ccc;
border-style : solid;
}
@ -259,9 +261,9 @@ div.filterTestHolder {
font-family : monospace;
font-size : 12px;
border-width : 0px;
border-color : #c0c0c0;
border-color : #ccc;
border-style : solid;
background : #fafafa;
background : #f5f5f5;
display : block;
max-width : 98%;
overflow : auto;
@ -274,9 +276,7 @@ div.notice, div.warning, div.error {
font-size : 12px;
border-style : solid;
border-color : #ccc;
border-radius : 4px;
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 {
@ -324,47 +324,66 @@ div.prefHelp {
color : #555;
}
div#headlines-toolbar {
border-width : 0px 0px 1px 0px;
background-color : #fcfcfc;
border-color : #c0c0c0;
#main-toolbar > * {
white-space : nowrap;
display : table-cell;
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-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 {
font-size : 11px;
position : relative;
top : -2px;
#main-toolbar #headlines-toolbar {
padding-right : 4px;
width : 100%;
}
div#headlines-toolbar span.r {
float: right;
position: relative;
padding : 0 4px 0px 4px;
#main-toolbar #headlines-toolbar span.holder {
display : table;
width : 100%;
}
#main-toolbar #headlines-toolbar span.holder > * {
display : table-cell;
}
#main-toolbar #headlines-toolbar .main {
text-align : right;
}
#main-toolbar #headlines-toolbar .main,
#main-toolbar #headlines-toolbar .r {
line-height : 24px;
}
#headlines-toolbar span.r img {
margin-right : 4px;
position : relative;
top : 3px;
}
#headlines-toolbar span.r .error a {
color : red;
}
div#headlines-toolbar span.r a {
color : #555;
#main-toolbar #selected_prompt {
font-style : italic;
text-align : right;
margin-right : 4px;
}
span.contentPreview {
color : #999;
font-weight : normal;
text-shadow : 1px 1px 2px #fff;
font-size : 11px;
font-size : 12px;
padding-left : 4px;
}
span.hlLabelRef {
@ -397,63 +416,50 @@ div.postHeader div {
#allEntryTags {
border-width : 0px 0px 1px 0px;
border-style : solid;
border-color : #c0c0c0;
border-color : #ddd;
padding-bottom : 5px;
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 {
cursor : pointer;
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 {
border : 1px solid #c0c0c0;
background-color : #ecf4ff;
border : 1px solid #ddd;
background-color : #f5f5f5;
margin : 5px 0px 5px 0px;
padding : 5px;
text-align : center;
border-radius : 4px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
}
div.errorExplained {
border : 1px solid #c0c0c0;
background-color : #ecf4ff;
border : 1px solid #ddd;
background-color : #f5f5f5;
margin : 5px 0px 5px 0px;
padding : 5px;
border-radius : 4px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
}
ul.feedErrorsList {
max-height : 300px;
overflow : auto;
list-style-type : none;
border : 1px solid #c0c0c0;
background-color : #ecf4ff;
border : 1px solid #ddd;
background-color : #f5f5f5;
margin : 0px 0px 5px 0px;
padding : 5px;
border-radius : 4px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
}
ul.feedErrorsList em {
@ -464,7 +470,7 @@ ul.browseFeedList {
height : 300px;
overflow : auto;
border-width : 0px 1px 1px 1px;
border-color : #c0c0c0;
border-color : #ddd;
border-style : solid;
margin : 0px 0px 5px 0px;
background-color : white;
@ -528,14 +534,6 @@ form {
padding : 0px;
}
#main_toolbar_form {
margin : 0px;
padding : 0px;
display : table-cell;
white-space : nowrap;
width : 100%;
}
div.loadingPrompt {
padding : 1em;
text-align : center;
@ -545,20 +543,11 @@ div.loadingPrompt {
div.whiteBox {
margin-left : 1px;
text-align : center;
padding : 1em;
}
/* html, body#ttrssMain, #main {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
} */
#toolbar div.actionChooser {
display : table-cell;
text-align : right;
padding-right : 3px;
padding : 1em 1em 0px 1em;
font-size : 11px;
border-width : 0px 0px 1px 0px;
border-style : solid;
border-color : #ddd;
}
div.autocomplete {
@ -589,11 +578,13 @@ div.autocomplete ul li {
cursor : pointer;
}
.hlTitle {
.hl .hlTitle {
overflow : hidden;
white-space : nowrap;
max-width : 500px;
text-overflow : ellipsis;
padding-left : 6px;
padding-right : 6px;
}
div#headlines-frame.wide .hlTitle {
@ -602,6 +593,10 @@ div#headlines-frame.wide .hlTitle {
white-space : normal;
}
div#headlines-frame.wide .hl .hlFeed {
display : none;
}
.hl a.title.high, span.hlContent.high .contentPreview {
color : #00aa00;
}
@ -667,7 +662,6 @@ span.labelColorIndicator {
background-color : #fff7d5;
color : #063064;
text-align : center;
box-shadow : 0px 0px 1px 0px rgba(0,0,0,0.1);
}
div#cmdline {
@ -716,14 +710,39 @@ div.hlRight img {
max-height : 16px;
}
span.hlUpdated {
color : #555;
min-width : 100px;
.hl span.hlFeed {
display : table-cell;
width : 100%;
vertical-align : middle;
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 {
@ -771,21 +790,19 @@ div.fatalError textarea {
#content-wrap {
padding : 0px;
border-width : 0px 0px 0px 1px;
border-style : solid;
border-color : #c0c0c0;
border-width : 0px;
margin : 0px;
}
#feeds-holder {
padding : 0px;
border-color : #c0c0c0;
border-left-width : 0px;
border-bottom-width : 0px;
border-top-width : 0px;
border-width : 0px 1px 0px 0px;
border-style : solid;
border-color : #ddd;
overflow : hidden;
box-shadow : inset 0px 0px 3px rgba(0,0,0,0.1);
background : #f9fbff;
background : #f5f5f5;
box-shadow : inset -1px 0px 2px -1px rgba(0,0,0,0.1);
-webkit-overflow-scrolling : touch;
}
#headlines-wrap-inner {
@ -796,11 +813,10 @@ div.fatalError textarea {
#headlines-frame {
padding : 0px;
border-color : #c0c0c0;
border-style : solid;
border-width : 0px;
border-color : #ddd;
margin-top : 0px;
box-shadow : inset 0px 0px 3px rgba(0,0,0,0.1);
-webkit-overflow-scrolling : touch;
}
#headlines-toolbar_splitter, #toolbar_splitter {
@ -813,7 +829,16 @@ div.fatalError textarea {
border-width : 0px;
white-space: nowrap;
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 {
@ -837,12 +862,12 @@ div.fatalError textarea {
#content-insert {
padding : 0px;
border-color : #c0c0c0;
border-bottom-width : 0px;
border-right-width : 0px;
border-left-width : 0px;
line-height: 20px;
border-color : #ddd;
border-width : 0px;
line-height: 1.5;
font-size : 15px;
overflow : auto;
-webkit-overflow-scrolling : touch;
}
#feedTree .dijitTreeRow .dijitTreeLabel.Unread {
@ -853,33 +878,6 @@ div.fatalError textarea {
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 {
width : 16px;
height : 16px;
@ -888,24 +886,6 @@ img.feedIcon, img.tinyFeedIcon {
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 {
display : inline-block;
color : #555;
@ -933,8 +913,19 @@ img.feedIcon, img.tinyFeedIcon {
height : 100%;
margin-left : 1px;
text-align : center;
padding : 1em;
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 {
@ -942,12 +933,11 @@ ul#filterDlg_Matches, ul#filterDlg_Actions {
overflow : auto;
list-style-type : none;
border-style : solid;
border-color : #c0c0c0;
border-color : #ddd;
border-width : 0px 1px 1px 1px;
background-color : #ecf4ff;
background-color : white;
margin : 0px 0px 5px 0px;
padding : 0px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
}
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;
}
ul#filterDlg_Matches li div.dijitCheckBox, ul#filterDlg_Actions li div.dijitCheckBox {
margin-right : 5px;
}
ul.helpKbList {
max-height : 300px;
overflow : auto;
list-style-type : none;
border : 1px solid #c0c0c0;
background-color : #ecf4ff;
border : 1px solid #ddd;
background-color : #f5f5f5;
margin : 0px 0px 5px 0px;
padding : 5px;
border-radius : 4px;
box-shadow : inset 0px 0px 2px rgba(0,0,0,0.1);
}
ul.helpKbList span.hksequence {
@ -983,18 +967,22 @@ ul.helpKbList h2 {
margin-top : 0px;
}
.dijitTreeNode .loadingNode {
margin-left : 3px;
height : 9px;
}
span.collapseBtn {
cursor : pointer;
}
div.postContent h1 {
font-size : 16px;
}
div.postContent h2,
div.postContent h3,
div.postContent h4 {
font-size : 15px;
}
div.postContent p {
max-width : 650px;
text-align : justify;
-webkit-hyphens: auto;
-moz-hyphens: auto;
hyphens: auto;
@ -1010,90 +998,12 @@ div.postHeader span.author {
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 {
display : block;
margin-top : 10px;
max-width : 120px;
}
div.hl.active {
border-color : #88b0f0;
}
#selected_prompt {
margin-right : 25px;
}
@ -1107,46 +1017,28 @@ body#ttrssMain #feedTree .dijitTreeRow {
padding : 2px 0px 2px;
height : 22px;
border-width : 1px;
border-color : transparent;
color : #333;
}
body#ttrssMain #feedTree .dijitFolderClosed,
body#ttrssMain #feedTree .dijitFolderOpened {
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;
ul#filterDlg_Matches li div.dijitCheckBox, ul#filterDlg_Actions li div.dijitCheckBox {
margin-right : 5px;
}
body#ttrssMain #feedTree {
height : 100%;
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;
color : #999;
border-color : #f0f0f0;
}
#feedTree .counterNode {
body#ttrssMain #feedTree .counterNode {
font-weight : bold;
display : inline-block;
font-size : 9px;
@ -1159,31 +1051,12 @@ body#ttrssMain #feedTree {
float : right;
position : relative;
line-height : 14px;
margin-right : 4px;
margin-right : 8px;
margin-top : 3px;
min-width : 23px;
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'] {
position : relative;
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 {
background : #f9fbff;
background : #f5f5f5;
color : black;
padding : 0px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
@ -15,11 +15,11 @@ form {
}
div.content {
overflow : hidden;
background : white;
border : 1px solid #ccc;
border : 1px solid #ddd;
padding : 10px;
border-radius : 4px;
box-shadow : inset 0 0 3px rgba(0,0,0,0.1);
box-shadow : 0px 1px 1px -1px rgba(0,0,0,0.1);
}
p.warning {
@ -40,9 +40,7 @@ div.insensitive-small {
}
.floatingLogo {
float : right;
position : relative;
top : -10px;
display : none;
}
a {
@ -61,7 +59,6 @@ div.notice, div.warning, div.error {
font-size : 12px;
border-style : solid;
border-color : #ccc;
border-radius : 4px;
border-width : 1px;
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");
$stamp = gmdate("D, d M Y H:i:s", filemtime($filename)). " GMT";
header("Last-Modified: $stamp", true);
ob_clean(); // discard any data in the output buffer (if possible)
flush(); // flush headers (if possible)
readfile($filename);
}
} 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,
$update_pcat = true) {
$update_pcat = true, $pcat_fast = false) {
if (!is_numeric($feed_id)) return;
@ -127,11 +127,13 @@
/* 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");
while ($line = db_fetch_assoc($result)) {
ccache_update($line["id"], $owner_uid, false, false);
while ($line = db_fetch_assoc($result)) {
ccache_update($line["id"], $owner_uid, false, false);
}
}
$result = db_query("SELECT SUM(value) AS sv
@ -177,7 +179,7 @@
$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
LIMIT $limit");
$cur_feed_title = "";
$headlines_count = db_num_rows($result);
$headlines = array();

View file

@ -63,7 +63,7 @@
htmlspecialchars($line["title"])."</span></a>";
$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>";
$rv .= "<li>$check_box $feed_url $site_url".
@ -72,7 +72,6 @@
} else if ($mode == 2) {
$feed_url = htmlspecialchars($line["feed_url"]);
$site_url = htmlspecialchars($line["site_url"]);
$title = htmlspecialchars($line["title"]);
$check_box = "<input onclick='toggleSelectListRow2(this)' dojoType=\"dijit.form.CheckBox\"
type=\"checkbox\">";
@ -92,7 +91,7 @@
htmlspecialchars($line["title"])."</span></a>";
$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>";

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;
$result = db_query(
db_query(
"DELETE FROM ttrss_user_labels2
WHERE
label_id = '$label_id' AND

View file

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

View file

@ -2,6 +2,19 @@
define_default('DAEMON_UPDATE_LOGIN_LIMIT', 30);
define_default('DAEMON_FEED_LIMIT', 500);
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() {
@ -79,7 +92,7 @@
$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") {
$update_limit_qpart = "AND ((
ttrss_feeds.update_interval = 0
@ -88,8 +101,10 @@
) OR (
ttrss_feeds.update_interval > 0
AND ttrss_feeds.last_updated < NOW() - CAST((ttrss_feeds.update_interval || ' minutes') AS INTERVAL)
) OR ttrss_feeds.last_updated IS NULL
OR last_updated = '1970-01-01 00:00:00')";
) OR (ttrss_feeds.last_updated IS NULL
AND ttrss_user_prefs.value != '-1')
OR (last_updated = '1970-01-01 00:00:00'
AND ttrss_user_prefs.value != '-1'))";
} else {
$update_limit_qpart = "AND ((
ttrss_feeds.update_interval = 0
@ -98,8 +113,10 @@
) OR (
ttrss_feeds.update_interval > 0
AND ttrss_feeds.last_updated < DATE_SUB(NOW(), INTERVAL ttrss_feeds.update_interval MINUTE)
) OR ttrss_feeds.last_updated IS NULL
OR last_updated = '1970-01-01 00:00:00')";
) OR (ttrss_feeds.last_updated IS NULL
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.
@ -118,6 +135,7 @@
ttrss_feeds, ttrss_users, ttrss_user_prefs
WHERE
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_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL'
$login_thresh_qpart $update_limit_qpart
@ -151,6 +169,7 @@
}
$nf = 0;
$bstarted = microtime(true);
// For each feed, we call the feed update function.
foreach ($feeds_to_update as $feed) {
@ -165,6 +184,7 @@
ttrss_user_prefs.owner_uid = ttrss_feeds.owner_uid AND
ttrss_users.id = ttrss_user_prefs.owner_uid AND
ttrss_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL' AND
ttrss_user_prefs.profile IS NULL AND
feed_url = '".db_escape_string($feed)."' AND
(ttrss_feeds.update_interval > 0 OR
ttrss_user_prefs.value != '-1')
@ -172,15 +192,27 @@
ORDER BY ttrss_feeds.id $query_limit");
if (db_num_rows($tmp_result) > 0) {
$rss = false;
while ($tline = db_fetch_assoc($tmp_result)) {
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(sprintf(" %.4f (sec)", microtime(true) - $fstarted));
++$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";
// Send feed digests by email if needed.
@ -191,7 +223,7 @@
} // function update_daemon_common
// 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'];
@ -199,7 +231,7 @@
_debug("start", $debug_enabled);
$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,
pubsub_state, auth_pass_encrypted,
(SELECT max(date_entered) FROM
@ -211,7 +243,6 @@
return false;
}
$last_updated = db_fetch_result($result, 0, "last_updated");
$last_article_timestamp = @strtotime(db_fetch_result($result, 0, "last_article_timestamp"));
if (defined('_DISABLE_HTTP_304'))
@ -252,34 +283,37 @@
$pluginhost->load($user_plugins, PluginHost::KIND_USER, $owner_uid);
$pluginhost->load_data();
$rss = false;
$rss_hash = false;
$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);
}
if ($rss && is_object($rss) && get_class($rss) == "FeedParser") {
_debug("using previously initialized parser object");
} 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) {
$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) {
_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);
@ -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) {
@ -356,10 +400,12 @@
$rss->init();
}
require_once "lib/languagedetect/LanguageDetect.php";
if (DETECT_ARTICLE_LANGUAGE) {
require_once "lib/languagedetect/LanguageDetect.php";
$lang = new Text_LanguageDetect();
$lang->setNameMode(2);
$lang = new Text_LanguageDetect();
$lang->setNameMode(2);
}
// print_r($rss);
@ -367,16 +413,6 @@
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
$pluginhost->run_hooks(PluginHost::HOOK_FEED_PARSED, "hook_feed_parsed", $rss);
@ -495,7 +531,20 @@
_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")) {
require_once 'lib/pubsubhubbub/subscriber.php';
@ -505,9 +554,9 @@
$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
WHERE id = '$feed'");
@ -524,9 +573,6 @@
$entry_guid = $item->get_id();
if (!$entry_guid) $entry_guid = $item->get_link();
if (!$entry_guid) $entry_guid = make_guid_from_title($item->get_title());
_debug("f_guid $entry_guid", $debug_enabled);
if (!$entry_guid) continue;
$entry_guid = "$owner_uid,$entry_guid";
@ -543,9 +589,6 @@
if ($entry_timestamp == -1 || !$entry_timestamp || $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);
@ -572,15 +615,19 @@
print "\n";
}
$entry_language = $lang->detect($entry_title . " " . $entry_content, 1);
$entry_language = "";
if (count($entry_language) > 0) {
$entry_language = array_keys($entry_language);
$entry_language = db_escape_string(substr($entry_language[0], 0, 2));
if (DETECT_ARTICLE_LANGUAGE) {
$entry_language = $lang->detect($entry_title . " " . $entry_content, 1);
_debug("detected language: $entry_language", $debug_enabled);
} else{
$entry_language = "";
if (count($entry_language) > 0) {
$possible = array_keys($entry_language);
$entry_language = $possible[0];
_debug("detected language: $entry_language", $debug_enabled);
} else {
$entry_language = "";
}
}
$entry_comments = $item->get_comments_url();
@ -618,24 +665,15 @@
_debug("done collecting data.", $debug_enabled);
// TODO: less memory-hungry implementation
_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");
$result = db_query("SELECT id, content_hash FROM ttrss_entries
WHERE guid = '".db_escape_string($entry_guid)."' OR guid = '$entry_guid_hashed'");
if (db_num_rows($result) != 0) {
$entry_plugin_data = db_fetch_result($result, 0, "plugin_data");
$stored_article = array("title" => db_fetch_result($result, 0, "title"),
"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"));
$base_entry_id = db_fetch_result($result, 0, "id");
$entry_stored_hash = db_fetch_result($result, 0, "content_hash");
} else {
$entry_plugin_data = "";
$stored_article = array();
$base_entry_id = false;
$entry_stored_hash = "";
}
$article = array("owner_uid" => $owner_uid, // read only
@ -644,36 +682,63 @@
"content" => $entry_content,
"link" => $entry_link,
"tags" => $entry_tags,
"plugin_data" => $entry_plugin_data,
"author" => $entry_author,
"stored" => $stored_article,
"language" => $entry_language, // read only
"feed" => array("id" => $feed,
"fetch_url" => $fetch_url,
"site_url" => $site_url)
);
foreach ($pluginhost->get_hooks(PluginHost::HOOK_ARTICLE_FILTER) as $plugin) {
$article = $plugin->hook_article_filter($article);
$entry_plugin_data = "";
$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_guid = db_escape_string($entry_guid);
$entry_title = db_escape_string($article["title"]);
$entry_author = db_escape_string($article["author"]);
$entry_link = db_escape_string($article["link"]);
$entry_plugin_data = db_escape_string($article["plugin_data"]);
$entry_content = $article["content"]; // escaped below
_debug("plugin data: $entry_plugin_data", $debug_enabled);
if ($cache_images && is_writable(CACHE_DIR . '/images'))
cache_images($entry_content, $site_url, $debug_enabled);
$entry_content = db_escape_string($entry_content, false);
$content_hash = "SHA1:" . sha1($entry_content);
db_query("BEGIN");
$result = db_query("SELECT id FROM ttrss_entries
@ -707,8 +772,8 @@
'$entry_link',
'$entry_timestamp_fmt',
'$entry_content',
'$content_hash',
$no_orig_date,
'$entry_current_hash',
false,
NOW(),
'$date_feed_processed',
'$entry_comments',
@ -720,28 +785,14 @@
$article_labels = array();
} 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");
db_query("UPDATE ttrss_entries SET date_updated = NOW()
WHERE id = '$base_entry_id'");
$article_labels = get_article_labels($base_entry_id, $owner_uid);
}
// now it should exist, if not - bad luck then
$result = db_query("SELECT
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
$result = db_query("SELECT id FROM ttrss_entries
WHERE guid = '$entry_guid' OR guid = '$entry_guid_hashed'");
$entry_ref_id = 0;
@ -751,14 +802,6 @@
_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");
$entry_ref_id = $ref_id;
@ -872,7 +915,7 @@
$p = new Publisher(PUBSUBHUBBUB_HUB);
$pubsub_result = $p->publish_update($rss_link);
/* $pubsub_result = */ $p->publish_update($rss_link);
}
$result = db_query(
@ -892,53 +935,20 @@
_debug("RID: $entry_ref_id, IID: $entry_int_id", $debug_enabled);
$post_needs_update = false;
$update_insignificant = false;
db_query("UPDATE ttrss_entries
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) {
$post_needs_update = true;
$update_insignificant = true;
}
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'");
}
}
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)) {
foreach ($encs as $e) {
$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);
}
}
@ -980,14 +990,16 @@
$enc_type = db_escape_string($enc[1]);
$enc_dur = db_escape_string($enc[2]);
$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
WHERE content_url = '$enc_url' AND post_id = '$entry_ref_id'");
if (db_num_rows($result) == 0) {
db_query("INSERT INTO ttrss_enclosures
(content_url, content_type, title, duration, post_id) VALUES
('$enc_url', '$enc_type', '$enc_title', '$enc_dur', '$entry_ref_id')");
(content_url, content_type, title, duration, post_id, width, height) VALUES
('$enc_url', '$enc_type', '$enc_title', '$enc_dur', '$entry_ref_id', $enc_width, $enc_height)");
}
}
@ -1101,21 +1113,27 @@
$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(
"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);
return $rss;
}
function cache_images($html, $site_url, $debug) {
$cache_dir = CACHE_DIR . "/images";
libxml_use_internal_errors(true);
$charset_hack = '<head>
@ -1139,7 +1157,7 @@
if (!file_exists($local_filename)) {
$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);
}
}

View file

@ -146,11 +146,6 @@
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")) {
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);
$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
define('VERSION_STATIC', '1.10');
define('VERSION_STATIC', '1.14');
function get_version() {
date_default_timezone_set('UTC');

View file

@ -56,15 +56,19 @@
<head>
<title>Tiny Tiny RSS</title>
<?php stylesheet_tag("lib/dijit/themes/claro/claro.css"); ?>
<?php stylesheet_tag("css/layout.css"); ?>
<script type="text/javascript">
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"]) {
$theme = get_pref( "USER_CSS_THEME", $_SESSION["uid"], false);
if ($theme && file_exists("themes/$theme")) {
stylesheet_tag("themes/$theme");
echo stylesheet_tag("themes/$theme");
} else {
stylesheet_tag("themes/default.css");
echo stylesheet_tag("themes/default.css");
}
}
?>
@ -86,12 +90,12 @@
<?php
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/tt-rss-layer.js",
"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="main-toolbar" dojoType="dijit.Toolbar">
<form id="headlines-toolbar" action="" onsubmit='return false'>
</form>
<form id="main_toolbar_form" action="" onsubmit='return false'>
<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-toolbar" dojoType="dijit.layout.ContentPane" region="top">
</div>
<div id="floatingTitle" style="display : none"></div>
<div id="headlines-frame" dojoType="dijit.layout.ContentPane"

View file

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

View file

@ -58,12 +58,12 @@ dojo.declare("fox.FeedStoreModel", dijit.tree.ForestStoreModel, {
if (is_cat) {
treeItem = this.store._itemsByIdentity['CAT:' + feed];
items = this.store._arrayOfTopLevelItems;
} else {
treeItem = this.store._itemsByIdentity['FEED:' + feed];
items = this.store._arrayOfAllItems;
}
items = this.store._arrayOfAllItems;
for (var i = 0; i < items.length; i++) {
if (items[i] == treeItem) {
@ -71,14 +71,18 @@ dojo.declare("fox.FeedStoreModel", dijit.tree.ForestStoreModel, {
var unread = this.store.getValue(items[j], 'unread');
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++) {
var unread = this.store.getValue(items[j], 'unread');
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;
var item = items[0];
var item = items[0] == treeItem ? items[items.length-1] : items[0];
for (var i = 0; i < items.length; i++) {
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 param = this.model.store.getValue(args.item, 'param');
var rules = this.model.store.getValue(args.item, 'rules');
if (param) {
param = dojo.doc.createElement('span');
@ -32,6 +33,13 @@ dojo.declare("fox.PrefFilterTree", lib.CheckBoxTree, {
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') {
var img = dojo.doc.createElement('img');
img.src ='images/filter.png';

View file

@ -44,11 +44,8 @@ function exception_error(location, e, ext_info) {
try {
if (ext_info) {
if (ext_info.responseText) {
ext_info = ext_info.responseText;
}
}
if (ext_info)
ext_info = JSON.stringify(ext_info);
try {
new Ajax.Request("backend.php", {
@ -104,13 +101,15 @@ function exception_error(location, e, ext_info) {
title: "Unhandled exception",
style: "width: 600px",
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({
browserName: navigator.appName,
browserVersion: navigator.appVersion,
browserPlatform: navigator.platform,
browserCookies: navigator.cookieEnabled,
ttrssVersion: __ttrss_version,
initParams: JSON.stringify(init_params),
}).toQueryString();
document.forms['exceptionForm'].submit();
@ -205,6 +204,7 @@ function notify_real(msg, no_hide, n_type) {
return;
} else {
Element.show(n);
new Effect.Highlight(n);
}
/* types:
@ -829,7 +829,14 @@ function quickAddFeed() {
onComplete: function(transport) {
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'];
@ -854,6 +861,8 @@ function quickAddFeed() {
case 4:
feeds = rc['feeds'];
Element.show("fadd_multiple_notify");
var select = dijit.byId("feedDlg_feedContainerSelect");
while (select.getOptions().length > 0)
@ -1148,33 +1157,48 @@ function quickAddFilter() {
href: query});
if (!inPreferences()) {
var selectedText = getSelectionText();
var lh = dojo.connect(dialog, "onLoad", function(){
dojo.disconnect(lh);
var query = "op=rpc&method=getlinktitlebyid&id=" + getActiveArticleId();
if (selectedText != "") {
new Ajax.Request("backend.php", {
parameters: query,
onComplete: function(transport) {
var reply = JSON.parse(transport.responseText);
var feed_id = activeFeedIsCat() ? 'CAT:' + parseInt(getActiveFeedId()) :
getActiveFeedId();
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()) :
getActiveFeedId();
new Ajax.Request("backend.php", {
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...');
for (k in params) {
var v = params[k];
console.log("IP: " + k + " => " + v);
if (k == "label_base_index") _label_base_index = parseInt(v);
console.log("IP: " + k + " => " + JSON.stringify(params[k]));
if (k == "label_base_index") _label_base_index = parseInt(params[k]);
}
init_params = params;
@ -1934,3 +1956,25 @@ function feed_to_label_id(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.SimpleTextarea");
dojo.require("dijit.form.TextBox");
dojo.require("dijit.form.ComboBox");
dojo.require("dijit.form.ValidationTextBox");
dojo.require("dijit.InlineEditBox");
dojo.require("dijit.layout.AccordionContainer");
@ -500,6 +501,10 @@ function init() {
if (!isCdmMode()) {
_widescreen_mode = !_widescreen_mode;
// reset stored sizes because geometry changed
setCookie("ttrss_ci_width", 0);
setCookie("ttrss_ci_height", 0);
switchPanelMode(_widescreen_mode);
}
};
@ -550,25 +555,11 @@ function init_second_stage() {
updateFeedList();
closeArticlePanel();
_widescreen_mode = getInitParam("widescreen");
switchPanelMode(_widescreen_mode);
if (parseInt(getCookie("ttrss_fh_width")) > 0) {
dijit.byId("feeds-holder").domNode.setStyle(
{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();
var tmph = dojo.connect(dijit.byId('feeds-holder'), 'resize',
@ -624,6 +615,9 @@ function init_second_stage() {
hotkeys[1] = tmp;
setInitParam("hotkeys", hotkeys);
_widescreen_mode = getInitParam("widescreen");
switchPanelMode(_widescreen_mode);
console.log("second stage ok");
if (getInitParam("simple_update")) {
@ -706,6 +700,10 @@ function quickMenuGo(opid) {
if (!isCdmMode()) {
_widescreen_mode = !_widescreen_mode;
// reset stored sizes because geometry changed
setCookie("ttrss_ci_width", 0);
setCookie("ttrss_ci_height", 0);
switchPanelMode(_widescreen_mode);
}
break;
@ -989,6 +987,12 @@ function handle_rpc_json(transport, scheduled_call) {
try {
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) {
var error = reply['error'];
@ -1035,16 +1039,21 @@ function handle_rpc_json(transport, scheduled_call) {
if (runtime_info)
parse_runtime_info(runtime_info);
Element.hide(dijit.byId("net-alert").domNode);
if (netalert) Element.hide(netalert);
} else {
//notify_error("Error communicating with server.");
Element.show(dijit.byId("net-alert").domNode);
if (netalert)
Element.show(netalert);
else
notify_error("Communication problem with server.");
}
} catch (e) {
Element.show(dijit.byId("net-alert").domNode);
//notify_error("Error communicating with server.");
if (netalert)
Element.show(netalert);
else
notify_error("Communication problem with server.");
console.log(e);
//exception_error("handle_rpc_json", e, transport);
}
@ -1064,11 +1073,13 @@ function switchPanelMode(wide) {
dijit.byId("content-insert").domNode.setStyle({width: '50%',
height: 'auto',
borderLeftWidth: '1px',
borderLeftColor: '#c0c0c0',
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").addClassName("wide");
@ -1078,10 +1089,12 @@ function switchPanelMode(wide) {
dijit.byId("content-insert").domNode.setStyle({width: 'auto',
height: '50%',
borderLeftWidth: '0px',
borderTopWidth: '1px'});
borderTopWidth: '0px'});
$("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").removeClassName("wide");

View file

@ -87,8 +87,12 @@ function headlines_callback2(transport, offset, background, infscroll_req) {
dijit.byId("headlines-frame").attr('content',
reply['headlines']['content']);
dijit.byId("headlines-toolbar").attr('content',
reply['headlines']['toolbar']);
//dijit.byId("headlines-toolbar").attr('content',
// reply['headlines']['toolbar']);
dojo.html.set($("headlines-toolbar"),
reply['headlines']['toolbar'],
{parseContent: true});
$$("#headlines-frame > div[id*=RROW]").each(function(row) {
if (loaded_article_ids.indexOf(row.id) != -1) {
@ -104,6 +108,10 @@ function headlines_callback2(transport, offset, background, infscroll_req) {
initHeadlinesMenu();
if (_infscroll_disable)
hsp.innerHTML = "<a href='#' onclick='openNextUnreadFeed()'>" +
__("Click to open next unread feed.") + "</a>";
if (_search_query) {
$("feed_title").innerHTML += "<span id='cancel_search'>" +
" (<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 (getInitParam("cdm_auto_catchup") == 1) {
// if (getInitParam("cdm_auto_catchup") == 1) {
c.domNode.appendChild(hsp);
}
// }
console.log("added " + new_elems.size() + " headlines");
@ -172,7 +180,8 @@ function headlines_callback2(transport, offset, background, infscroll_req) {
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
request_counters(true);
} else if (transport.responseText) {
} else {
console.error("Invalid object received: " + transport.responseText);
dijit.byId("headlines-frame").attr('content', "<div class='whiteBox'>" +
__('Could not update headlines (invalid object received - see error console for details)') +
"</div>");
} else {
//notify_error("Error communicating with server.");
Element.show(dijit.byId("net-alert").domNode);
}
_infscroll_request_sent = 0;
@ -314,13 +320,11 @@ function article_callback2(transport, id) {
// return;
// }
} else if (transport.responseText) {
} else {
console.error("Invalid object received: " + transport.responseText);
render_article("<div class='whiteBox'>" +
__('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
@ -961,10 +965,12 @@ function getLoadedArticleIds() {
}
// mode = all,none,unread,invert,marked,published
function selectArticles(mode) {
function selectArticles(mode, query) {
try {
var children = $$("#headlines-frame > div[id*=RROW]");
if (!query) query = "#headlines-frame > div[id*=RROW]";
var children = $$(query);
children.each(function(child) {
var id = child.id.replace("RROW-", "");
@ -1243,7 +1249,7 @@ function postMouseOut(id) {
function unpackVisibleHeadlines() {
try {
if (!isCdmMode()) return;
if (!isCdmMode() || !getInitParam("cdm_expanded")) return;
$$("#headlines-frame > div[id*=RROW]").each(
function(child) {
@ -1306,15 +1312,20 @@ function headlines_scroll_handler(e) {
((e.scrollTop + e.offsetHeight) / e.scrollHeight >= 0.7))) {
if (hsp)
hsp.innerHTML = "<img src='images/indicator_tiny.gif'> " +
__("Loading, please wait...");
hsp.innerHTML = "<span class='loading'><img src='images/indicator_tiny.gif'> " +
__("Loading, please wait...") + "</span>";
loadMoreHeadlines();
return;
}
} 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()) {
@ -1350,6 +1361,20 @@ function headlines_scroll_handler(e) {
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) {
@ -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() {
try {
if (catchup_id_batch.length > 0 && !_infscroll_request_sent) {
@ -1761,7 +1796,8 @@ function cdmClicked(event, id) {
return !event.shiftKey;
}
} else {
} else if (event.target.parents(".cdmHeader").length > 0) {
toggleSelected(id, true);
var elem = $("RROW-" + id);
@ -2107,6 +2143,72 @@ function initHeadlinesMenu() {
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) {
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