Merge pull request #1 from gothfox/master

Update from original
master
YoungMin Park 10 years ago
commit a5bbb2bec1

2
.gitignore vendored

@ -2,13 +2,13 @@
*~
*.DS_Store
#*
lib/floIcon.php
.idea/*
config.php
feed-icons/*
cache/*/*
lock/*
tags
plugins/fever
cache/htmlpurifier/*/*ser
lib/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/*/*ser
web.config

@ -4,12 +4,25 @@ 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)
Licensed under GNU GPL version 2
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyright (c) 2005 Andrew Dolgov (unless explicitly stated otherwise).
Uses Silk icons by Mark James: http://www.famfamfam.com/lab/icons/silk/
## Requirements
* Compatible web browser (http://tt-rss.org/wiki/CompatibleBrowsers)

@ -21,9 +21,6 @@
<div class="rss">
<img class="feedicon" src="images/pub_set.svg" style="width : 64px; height : 64px;"
alt="feed icon"/>
<h1><xsl:value-of select="atom:title"/></h1>
<p class="description">This feed has been exported from

@ -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,11 +200,15 @@ 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 = "date_entered, updated";
$override_order = "score DESC, date_entered, updated";
break;
case "feed_dates":
$override_order = "updated DESC";
@ -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 {
@ -309,8 +314,8 @@ class API extends Handler {
if ($article_id) {
$query = "SELECT id,title,link,content,cached_content,feed_id,comments,int_id,
marked,unread,published,score,
$query = "SELECT id,title,link,content,feed_id,comments,int_id,
marked,unread,published,score,note,lang,
".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
author,(SELECT title FROM ttrss_feeds WHERE id = feed_id) AS feed_title
FROM ttrss_entries,ttrss_user_entries
@ -338,11 +343,13 @@ class API extends Handler {
"comments" => $line["comments"],
"author" => $line["author"],
"updated" => (int) strtotime($line["updated"]),
"content" => $line["cached_content"] != "" ? $line["cached_content"] : $line["content"],
"content" => $line["content"],
"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,14 +430,14 @@ 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;
}
}
array_push($rv, array(
"id" => (int)$line['id'],
"id" => (int)label_to_feed_id($line['id']),
"caption" => $line['caption'],
"fg_color" => $line['fg_color'],
"bg_color" => $line['bg_color'],
@ -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,11 +666,31 @@ class API extends Handler {
$headlines = array();
while ($line = db_fetch_assoc($result)) {
$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);
}
$is_updated = ($line["last_read"] == "" &&
($line["unread"] != "t" && $line["unread"] != "1"));
$tags = explode(",", $line["tag_cache"]);
$labels = json_decode($line["label_cache"], true);
$label_cache = $line["label_cache"];
$labels = false;
if ($label_cache) {
$label_cache = json_decode($label_cache, true);
if ($label_cache) {
if ($label_cache["no-labels"] == 1)
$labels = array();
else
$labels = $label_cache;
}
}
if (!is_array($labels)) $labels = get_article_labels($line["id"]);
//if (!$tags) $tags = get_article_tags($line["id"]);
//if (!$labels) $labels = get_article_labels($line["id"]);
@ -660,28 +708,22 @@ class API extends Handler {
"tags" => $tags,
);
if ($include_attachments)
$headline_row['attachments'] = get_article_enclosures(
$line['id']);
if ($include_attachments)
$headline_row['attachments'] = get_article_enclosures(
$line['id']);
if ($show_excerpt) {
$excerpt = truncate_string(strip_tags($line["content_preview"]), 100);
$headline_row["excerpt"] = $excerpt;
}
if ($show_excerpt)
$headline_row["excerpt"] = $line["content_preview"];
if ($show_content) {
if ($line["cached_content"] != "") {
$line["content_preview"] =& $line["cached_content"];
}
if ($sanitize_content) {
$headline_row["content"] = sanitize(
$line["content_preview"],
$line["content"],
sql_bool_to_bool($line['hide_images']),
false, $line["site_url"]);
false, $line["site_url"], false, $line["id"]);
} else {
$headline_row["content"] = $line["content_preview"];
$headline_row["content"] = $line["content"];
}
}
@ -699,7 +741,10 @@ class API extends Handler {
$headline_row["always_display_attachments"] = sql_bool_to_bool($line["always_display_enclosures"]);
$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));

@ -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");

@ -26,9 +26,12 @@ class Db_Mysql implements IDb {
}
function query($query, $die_on_error = true) {
$result = mysql_query($query, $this->link);
$result = @mysql_query($query, $this->link);
if (!$result) {
user_error("Query $query failed: " . ($this->link ? mysql_error($this->link) : "No connection"),
$error = @mysql_error($this->link);
@mysql_query("ROLLBACK", $this->link);
user_error("Query $query failed: " . ($this->link ? $error : "No connection"),
$die_on_error ? E_USER_ERROR : E_USER_WARNING);
}
return $result;

@ -24,9 +24,12 @@ class Db_Mysqli implements IDb {
}
function query($query, $die_on_error = true) {
$result = mysqli_query($this->link, $query);
$result = @mysqli_query($this->link, $query);
if (!$result) {
user_error("Query $query failed: " . ($this->link ? mysqli_error($this->link) : "No connection"),
$error = @mysqli_error($this->link);
@mysqli_query($this->link, "ROLLBACK");
user_error("Query $query failed: " . ($this->link ? $error : "No connection"),
$die_on_error ? E_USER_ERROR : E_USER_WARNING);
}

@ -35,11 +35,14 @@ class Db_Pgsql implements IDb {
}
function query($query, $die_on_error = true) {
$result = pg_query($query);
$result = @pg_query($this->link, $query);
if (!$result) {
$error = @pg_last_error($this->link);
@pg_query($this->link, "ROLLBACK");
$query = htmlspecialchars($query); // just in case
user_error("Query $query failed: " . ($this->link ? pg_last_error($this->link) : "No connection"),
user_error("Query $query failed: " . ($this->link ? $error : "No connection"),
$die_on_error ? E_USER_ERROR : E_USER_WARNING);
}
return $result;

@ -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>";

@ -3,5 +3,8 @@ class FeedEnclosure {
public $link;
public $type;
public $length;
public $title;
public $height;
public $width;
}
?>

@ -1,5 +1,6 @@
<?php
class FeedItem_Atom extends FeedItem_Common {
function get_id() {
$id = $this->elem->getElementsByTagName("id")->item(0);
@ -30,6 +31,7 @@ class FeedItem_Atom extends FeedItem_Common {
}
}
function get_link() {
$links = $this->elem->getElementsByTagName("link");
@ -38,8 +40,13 @@ class FeedItem_Atom extends FeedItem_Common {
(!$link->hasAttribute("rel")
|| $link->getAttribute("rel") == "alternate"
|| $link->getAttribute("rel") == "standout")) {
$base = $this->xpath->evaluate("string(ancestor-or-self::*[@xml:base][1]/@xml:base)", $link);
if ($base)
return rewrite_relative_url($base, trim($link->getAttribute("href")));
else
return trim($link->getAttribute("href"));
return $link->getAttribute("href");
}
}
}
@ -48,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);
}
}
@ -58,7 +65,13 @@ class FeedItem_Atom extends FeedItem_Common {
if ($content) {
if ($content->hasAttribute('type')) {
if ($content->getAttribute('type') == 'xhtml') {
return $this->doc->saveXML($content->firstChild->nextSibling);
for ($i = 0; $i < $content->childNodes->length; $i++) {
$child = $content->childNodes->item($i);
if ($child->hasChildNodes()) {
return $this->doc->saveXML($child);
}
}
}
}
@ -72,7 +85,13 @@ class FeedItem_Atom extends FeedItem_Common {
if ($content) {
if ($content->hasAttribute('type')) {
if ($content->getAttribute('type') == 'xhtml') {
return $this->doc->saveXML($content->firstChild->nextSibling);
for ($i = 0; $i < $content->childNodes->length; $i++) {
$child = $content->childNodes->item($i);
if ($child->hasChildNodes()) {
return $this->doc->saveXML($child);
}
}
}
}
@ -87,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;
@ -126,6 +145,51 @@ 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);
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);
}

@ -8,6 +8,17 @@ abstract class FeedItem_Common extends FeedItem {
$this->elem = $elem;
$this->xpath = $xpath;
$this->doc = $doc;
try {
$source = $elem->getElementsByTagName("source")->item(0);
// we don't need <source> element
if ($source)
$elem->removeChild($source);
} catch (DOMException $e) {
//
}
}
function get_author() {
@ -33,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;

@ -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,6 +113,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");
array_push($encs, $enc);
}
@ -120,6 +127,51 @@ 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);
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);
}

@ -2,6 +2,7 @@
class FeedParser {
private $doc;
private $error;
private $libxml_errors = array();
private $items;
private $link;
private $title;
@ -12,27 +13,75 @@ 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();
$this->doc = new DOMDocument();
$this->doc->loadXML($data);
mb_substitute_character("none");
$error = libxml_get_last_error();
if ($error && $error->code == 9) {
libxml_clear_errors();
// libxml compiled without iconv?
if ($error && $error->code == 32) {
$data = $this->normalize_encoding($data);
// we might want to try guessing input encoding here too
$data = iconv("UTF-8", "UTF-8//IGNORE", $data);
if ($data) {
libxml_clear_errors();
$this->doc = new DOMDocument();
$this->doc->loadXML($data);
$this->doc = new DOMDocument();
$this->doc->loadXML($data);
$error = libxml_get_last_error();
$error = libxml_get_last_error();
}
}
$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();
@ -48,27 +97,32 @@ 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)")->item(0);
if ($root) {
switch (mb_strtolower($root->tagName)) {
case "rdf:rdf":
$this->type = $this::FEED_RDF;
break;
case "channel":
$this->type = $this::FEED_RSS;
break;
case "feed":
$this->type = $this::FEED_ATOM;
break;
default:
if( !isset($this->error) ){
$this->error = "Unknown/unsupported feed type";
$root = $xpath->query("(//atom03:feed|//atom:feed|//channel|//rdf:rdf|//rdf:RDF)");
if ($root && $root->length > 0) {
$root = $root->item(0);
if ($root) {
switch (mb_strtolower($root->tagName)) {
case "rdf:rdf":
$this->type = $this::FEED_RDF;
break;
case "channel":
$this->type = $this::FEED_RSS;
break;
case "feed":
$this->type = $this::FEED_ATOM;
break;
default:
if( !isset($this->error) ){
$this->error = "Unknown/unsupported feed type";
}
return;
}
return;
}
switch ($this->type) {
@ -151,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";
@ -173,6 +231,10 @@ class FeedParser {
return $this->error;
}
function errors() {
return $this->libxml_errors;
}
function get_link() {
return $this->link;
}
@ -194,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;
@ -203,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;

@ -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,14 +37,24 @@ 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");
// right part
$reply .= "<span class='r'>";
$reply .= "<span id='selected_prompt'></span>";
$reply .= "<span id='feed_title'>";
$error_class = $error ? "error" : "";
$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) {
$last_updated = T_sprintf("Last updated: %s",
@ -58,10 +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) {
$reply .= " (<span class=\"error\" title=\"$error\">Error</span>)";
$error = htmlspecialchars($error);
$reply .= "&nbsp;<img title=\"$error\" src='images/error.png' alt='error' class=\"noborder\">";
}
} else {
@ -70,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.svg\"></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>,
@ -129,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;
}
@ -145,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;
@ -244,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];
@ -252,6 +258,7 @@ class Feeds extends Handler_Protected {
$last_error = $qfh_ret[3];
$last_updated = strpos($qfh_ret[4], '1970-') === FALSE ?
make_local_datetime($qfh_ret[4], false) : __("Never");
$highlight_words = $qfh_ret[5];
$vgroup_last_feed = $vgr_last_feed;
@ -274,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;
@ -281,13 +294,21 @@ 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"]), 250);
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
$line = $p->hook_query_headlines($line, 250, false);
}
if (get_pref('SHOW_CONTENT_PREVIEW')) {
$content_preview = $line["content_preview"];
}
$id = $line["id"];
$feed_id = $line["feed_id"];
$label_cache = $line["label_cache"];
@ -306,7 +327,7 @@ class Feeds extends Handler_Protected {
if (!is_array($labels)) $labels = get_article_labels($id);
$labels_str = "<span id=\"HLLCTR-$id\">";
$labels_str = "<span class=\"HLLCTR-$id\">";
$labels_str .= format_article_labels($labels, $id);
$labels_str .= "</span>";
@ -323,24 +344,24 @@ class Feeds extends Handler_Protected {
if (sql_bool_to_bool($line["marked"])) {
$marked_pic = "<img
src=\"images/mark_set.svg\"
src=\"images/mark_set.png\"
class=\"markedPic\" alt=\"Unstar article\"
onclick='toggleMark($id)'>";
$class .= " marked";
} else {
$marked_pic = "<img
src=\"images/mark_unset.svg\"
src=\"images/mark_unset.png\"
class=\"markedPic\" alt=\"Star article\"
onclick='toggleMark($id)'>";
}
if (sql_bool_to_bool($line["published"])) {
$published_pic = "<img src=\"images/pub_set.svg\"
$published_pic = "<img src=\"images/pub_set.png\"
class=\"pubPic\"
alt=\"Unpublish article\" onclick='togglePub($id)'>";
$class .= " published";
} else {
$published_pic = "<img src=\"images/pub_unset.svg\"
$published_pic = "<img src=\"images/pub_unset.png\"
class=\"pubPic\"
alt=\"Publish article\" onclick='togglePub($id)'>";
}
@ -360,11 +381,6 @@ class Feeds extends Handler_Protected {
$date_entered_fmt = T_sprintf("Imported at %s",
make_local_datetime($line["date_entered"], false));
if (get_pref('SHOW_CONTENT_PREVIEW')) {
$content_preview = truncate_string(strip_tags($line["content_preview"]),
250);
}
$score = $line["score"];
$score_pic = "images/" . get_score_pic($score);
@ -377,9 +393,9 @@ class Feeds extends Handler_Protected {
title=\"$score\">";
if ($score > 500) {
$hlc_suffix = "H";
$hlc_suffix = "high";
} else if ($score < -100) {
$hlc_suffix = "L";
$hlc_suffix = "low";
} else {
$hlc_suffix = "";
}
@ -395,7 +411,7 @@ class Feeds extends Handler_Protected {
if ($has_feed_icon) {
$feed_icon_img = "<img class=\"tinyFeedIcon\" src=\"".ICONS_URL."/$feed_id.ico\" alt=\"\">";
} else {
$feed_icon_img = "<img class=\"tinyFeedIcon\" src=\"images/pub_set.svg\" alt=\"\">";
$feed_icon_img = "<img class=\"tinyFeedIcon\" src=\"images/pub_set.png\" alt=\"\">";
}
$entry_site_url = $line["site_url"];
@ -413,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"];
@ -421,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>";
}
}
@ -434,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'>";
@ -448,16 +464,14 @@ class Feeds extends Handler_Protected {
$reply['content'] .= "</div>";
$reply['content'] .= "<div onclick='return hlClicked(event, $id)'
class=\"hlTitle\"><span class='hlContent$hlc_suffix'>";
$reply['content'] .= "<a id=\"RTITLE-$id\" class=\"title\"
class=\"hlTitle\"><span class='hlContent $hlc_suffix'>";
$reply['content'] .= "<a id=\"RTITLE-$id\" class=\"title $hlc_suffix\"
href=\"" . htmlspecialchars($line["link"]) . "\"
onclick=\"\">" .
truncate_string($line["title"], 200);
if (get_pref('SHOW_CONTENT_PREVIEW')) {
if ($content_preview) {
$reply['content'] .= "<span class=\"contentPreview\"> - $content_preview</span>";
}
$reply['content'] .= "<span class=\"contentPreview\">" . $line["content_preview"] . "</span>";
}
$reply['content'] .= "</a></span>";
@ -466,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>";
@ -484,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>";
@ -502,14 +517,14 @@ class Feeds extends Handler_Protected {
else
$tags = false;
$line["content"] = sanitize($line["content_preview"],
sql_bool_to_bool($line['hide_images']), false, $entry_site_url);
$line["content"] = sanitize($line["content"],
sql_bool_to_bool($line['hide_images']), false, $entry_site_url, $highlight_words, $line["id"]);
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_CDM) as $p) {
$line = $p->hook_render_article_cdm($line);
}
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"];
@ -517,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);
@ -527,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>";
@ -539,10 +554,10 @@ class Feeds extends Handler_Protected {
$expanded_class = $expand_cdm ? "expanded" : "expandable";
$reply['content'] .= "<div class=\"cdm $expanded_class $class\"
id=\"RROW-$id\" $mouseover_attrs>";
$reply['content'] .= "<div class=\"cdm $hlc_suffix $expanded_class $class\"
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\"
@ -554,10 +569,17 @@ class Feeds extends Handler_Protected {
$reply['content'] .= "</div>";
if ($highlight_words && count($highlight_words > 0)) {
foreach ($highlight_words as $word) {
$line["title"] = preg_replace("/(\Q$word\E)/i",
"<span class=\"highlight\">$1</span>", $line["title"]);
}
}
$reply['content'] .= "<span id=\"RTITLE-$id\"
onclick=\"return cdmClicked(event, $id);\"
class=\"titleWrap$hlc_suffix\">
<a class=\"title\"
class=\"titleWrap $hlc_suffix\">
<a class=\"title $hlc_suffix\"
target=\"_blank\" href=\"".
htmlspecialchars($line["link"])."\">".
$line["title"] .
@ -574,11 +596,11 @@ class Feeds extends Handler_Protected {
else
$excerpt_hidden = "style=\"display : none\"";
$reply['content'] .= "<span $excerpt_hidden
id=\"CEXC-$id\" class=\"cdmExcerpt\"> - $content_preview</span>";
$reply['content'] .= "<span $excerpt_hidden id=\"CEXC-$id\" class=\"cdmExcerpt\">" . $content_preview . "</span>";
$reply['content'] .= "</span>";
if (!get_pref('VFEED_GROUP_BY_FEED')) {
if (!$vfeed_group_enabled) {
if (@$line["feed_title"]) {
$rgba = @$rgba_cache[$feed_id];
@ -615,7 +637,9 @@ class Feeds extends Handler_Protected {
}
$reply['content'] .= "</div>";
$reply['content'] .= "<div class=\"cdmContentInner\">";
if (!$line['lang']) $line['lang'] = 'en';
$reply['content'] .= "<div class=\"cdmContentInner\" lang=\"".$line['lang']."\">";
if ($line["orig_feed_id"]) {
@ -638,7 +662,7 @@ class Feeds extends Handler_Protected {
$reply['content'] .= "&nbsp;";
$reply['content'] .= "<a target='_blank' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
$reply['content'] .= "<img title='".__('Feed URL')."'class='tinyFeedIcon' src='images/pub_unset.svg'></a>";
$reply['content'] .= "<img title='".__('Feed URL')."'class='tinyFeedIcon' src='images/pub_unset.png'></a>";
$reply['content'] .= "</div>";
}
@ -685,10 +709,13 @@ class Feeds extends Handler_Protected {
} else {
$comments_url = htmlspecialchars($line["link"]);
}
$entry_comments = "<a target='_blank' href=\"$comments_url\">$num_comments comments</a>";
$entry_comments = "<a class=\"postComments\"
target='_blank' href=\"$comments_url\">$num_comments ".
_ngettext("comment", "comments", $num_comments)."</a>";
} else {
if ($line["comments"] && $line["link"] != $line["comments"]) {
$entry_comments = "<a target='_blank' href=\"".htmlspecialchars($line["comments"])."\">comments</a>";
$entry_comments = "<a class=\"postComments\" target='_blank' href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
}
}
@ -706,7 +733,7 @@ class Feeds extends Handler_Protected {
$reply['content'] .= "</div>";
$reply['content'] .= "</div>";
$reply['content'] .= "</div><hr/>";
$reply['content'] .= "</div>";
$reply['content'] .= "</div>";
@ -784,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"]);
@ -863,7 +888,7 @@ class Feeds extends Handler_Protected {
$override_order = "ttrss_entries.title";
break;
case "date_reverse":
$override_order = "date_entered, updated";
$override_order = "score DESC, date_entered, updated";
break;
case "feed_dates":
$override_order = "updated DESC";
@ -878,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];
@ -959,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\">";
@ -1054,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'>
@ -1126,9 +1153,9 @@ 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>
<a class=\"visibleLink\" target=\"_blank\" href=\"http://tt-rss.org/wiki/SearchSyntax\">".__("Search syntax")."</a>
</div>";
}

@ -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) {
$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,17 +63,17 @@ 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=-2&key=" .
get_feed_access_key(-2, false, $owner_uid);
"/public.php?op=rss&id=$feed&key=" .
get_feed_access_key($feed, false, $owner_uid);
if (!$feed_site_url) $feed_site_url = get_self_url_prefix();
@ -85,16 +92,23 @@ 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"]), 100, '...');
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
$line = $p->hook_query_headlines($line);
}
$tpl->setVariable('ARTICLE_ID', htmlspecialchars($line['link']), true);
$tpl->setVariable('ARTICLE_ID',
htmlspecialchars($orig_guid ? $line['link'] :
get_self_url_prefix() .
"/public.php?url=" . urlencode($line['link'])), true);
$tpl->setVariable('ARTICLE_LINK', htmlspecialchars($line['link']), true);
$tpl->setVariable('ARTICLE_TITLE', htmlspecialchars($line['title']), true);
$tpl->setVariable('ARTICLE_EXCERPT',
truncate_string(strip_tags($line["content_preview"]), 100, '...'), true);
$tpl->setVariable('ARTICLE_EXCERPT', $line["content_preview"], true);
$content = sanitize($line["content_preview"], 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>" .
@ -111,6 +125,9 @@ 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'] ? $line['feed_title'] : $feed_title), true);
$tags = get_article_tags($line["id"], $owner_uid);
foreach ($tags as $tag) {
@ -164,13 +181,17 @@ class Handler_Public extends Handler {
$feed['articles'] = array();
while ($line = $this->dbh->fetch_assoc($result)) {
$line["content_preview"] = truncate_string(strip_tags($line["content_preview"]), 100, '...');
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
$line = $p->hook_query_headlines($line, 100);
}
$article = array();
$article['id'] = $line['link'];
$article['link'] = $line['link'];
$article['title'] = $line['title'];
$article['excerpt'] = truncate_string(strip_tags($line["content_preview"]), 100, '...');
$article['content'] = sanitize($line["content_preview"], false, $owner_uid);
$article['excerpt'] = $line["content_preview"];
$article['content'] = sanitize($line["content"], false, $owner_uid);
$article['updated'] = date('c', strtotime($line["updated"]));
if ($line['note']) $article['note'] = $line['note'];
@ -256,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'");
@ -274,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
@ -303,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)";
}
}
@ -342,7 +370,7 @@ class Handler_Public extends Handler {
function rss() {
$feed = $this->dbh->escape_string($_REQUEST["id"]);
$key = $this->dbh->escape_string($_REQUEST["key"]);
$is_cat = $_REQUEST["is_cat"] != false;
$is_cat = sql_bool_to_bool($_REQUEST["is_cat"]);
$limit = (int)$this->dbh->escape_string($_REQUEST["limit"]);
$offset = (int)$this->dbh->escape_string($_REQUEST["offset"]);
@ -350,8 +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["orig_guid"]);
if (!$format) $format = 'atom';
@ -371,20 +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);
$offset, $search, $search_mode, $view_mode, $format, $order, $orig_guid, $start_ts);
} else {
header('HTTP/1.1 403 Forbidden');
}
}
function globalUpdateFeeds() {
include "rssfuncs.php";
// Update all feeds needing a update.
update_daemon_common(0, true, false);
housekeeping_common(false);
function updateTask() {
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_UPDATE_TASK, "hook_update_task", false);
}
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_UPDATE_TASK, "hook_update_task", $op);
function housekeepingTask() {
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", false);
}
function sharepopup() {
@ -393,11 +427,14 @@ class Handler_Public extends Handler {
}
header('Content-Type: text/html; charset=utf-8');
print "<html><head><title>Tiny Tiny RSS</title>";
stylesheet_tag("css/utility.css");
javascript_tag("lib/prototype.js");
javascript_tag("lib/scriptaculous/scriptaculous.js?load=effects,dragdrop,controls");
print "<html><head><title>Tiny Tiny RSS</title>
<link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
<link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">";
echo stylesheet_tag("css/utility.css");
echo stylesheet_tag("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'>";
@ -543,6 +580,7 @@ class Handler_Public extends Handler {
}
} else {
$_SESSION["login_error_msg"] = __("Incorrect username or password");
user_error("Failed login attempt for $login from {$_SERVER['REMOTE_ADDR']}", E_USER_WARNING);
}
if ($_REQUEST['return']) {
@ -553,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();
@ -568,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\"
@ -652,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)));
@ -747,11 +713,15 @@ class Handler_Public extends Handler {
function forgotpass() {
startup_gettext();
@$hash = $_REQUEST["hash"];
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");
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'>";
@ -762,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'>";
@ -804,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);
require_once 'classes/ttrssmailer.php';
require_once "lib/MiniTemplator.class.php";
$tpl = new MiniTemplator;
$tpl->readTemplateFromFile("templates/resetpass_link_template.txt");
$tpl->setVariable('LOGIN', $login);
$tpl->setVariable('RESETPASS_LINK', $resetpass_link);
print "<p>";
$tpl->addBlock('message');
print "<p>"."Completed."."</p>";
$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")."\">
@ -853,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; }

@ -97,7 +97,7 @@ class Opml extends Handler_Protected {
$html_url_qpart = "";
}
$out .= "<outline text=\"$title\" xmlUrl=\"$url\" $html_url_qpart/>\n";
$out .= "<outline type=\"rss\" text=\"$title\" xmlUrl=\"$url\" $html_url_qpart/>\n";
}
if ($cat_title) $out .= "</outline>\n";
@ -190,6 +190,7 @@ class Opml extends Handler_Protected {
}
$tmp_line["cat_filter"] = sql_bool_to_bool($tmp_line["cat_filter"]);
$tmp_line["inverse"] = sql_bool_to_bool($tmp_line["inverse"]);
unset($tmp_line["feed_id"]);
unset($tmp_line["cat_id"]);
@ -256,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));
@ -363,9 +364,10 @@ class Opml extends Handler_Protected {
$cat_filter = bool_to_sql_bool($rule["cat_filter"]);
$reg_exp = $this->dbh->escape_string($rule["reg_exp"]);
$filter_type = (int)$rule["filter_type"];
$inverse = bool_to_sql_bool($rule["inverse"]);
$this->dbh->query("INSERT INTO ttrss_filters2_rules (feed_id,cat_id,filter_id,filter_type,reg_exp,cat_filter)
VALUES ($feed_id, $cat_id, $filter_id, $filter_type, '$reg_exp', $cat_filter)");
$this->dbh->query("INSERT INTO ttrss_filters2_rules (feed_id,cat_id,filter_id,filter_type,reg_exp,cat_filter,inverse)
VALUES ($feed_id, $cat_id, $filter_id, $filter_type, '$reg_exp', $cat_filter,$inverse)");
}
foreach ($filter["actions"] as $action) {

@ -37,6 +37,12 @@ class PluginHost {
const HOOK_PREFS_EDIT_FEED = 20;
const HOOK_PREFS_SAVE_FEED = 21;
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;
@ -73,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;
}
@ -97,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]);
}

@ -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"]);
@ -572,8 +573,9 @@ class Pref_Feeds extends Handler_Protected {
$last_error = $this->dbh->fetch_result($result, 0, "last_error");
if ($last_error) {
print "&nbsp;<span title=\"".htmlspecialchars($last_error)."\"
class=\"feed_error\">(error)</span>";
print "&nbsp;<img src=\"images/error.png\" alt=\"(error)\"
style=\"vertical-align : middle\"
title=\"".htmlspecialchars($last_error)."\">";
}
@ -736,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>";
@ -792,31 +794,10 @@ class Pref_Feeds extends Handler_Protected {
print "<div class=\"dlgSec\">".__("Feed")."</div>";
print "<div class=\"dlgSecCont\">";
/* Title */
print "<input dojoType=\"dijit.form.ValidationTextBox\"
disabled=\"1\" style=\"font-size : 16px; width : 20em;\" required=\"1\"
name=\"title\" value=\"\">";
$this->batch_edit_cbox("title");
/* Feed URL */
print "<br/>";
print __('URL:') . " ";
print "<input dojoType=\"dijit.form.ValidationTextBox\" disabled=\"1\"
required=\"1\" regExp='^(http|https)://.*' style=\"width : 20em\"
name=\"feed_url\" value=\"\">";
$this->batch_edit_cbox("feed_url");
/* Category */
if (get_pref('ENABLE_FEED_CATS')) {
print "<br/>";
print __('Place in category:') . " ";
print_feed_cat_select("cat_id", false,
@ -862,7 +843,7 @@ class Pref_Feeds extends Handler_Protected {
$this->batch_edit_cbox("auth_login");
print "<br/><input dojoType=\"dijit.form.TextBox\" type=\"password\" name=\"auth_pass\"
print "<hr/> <input dojoType=\"dijit.form.TextBox\" type=\"password\" name=\"auth_pass\"
placeHolder=\"".__("Password")."\" disabled=\"1\"
value=\"\">";
@ -982,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',
@ -1279,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\"
@ -1492,15 +1478,6 @@ class Pref_Feeds extends Handler_Protected {
print "</p>";
print_warning(__("You can disable all articles shared by unique URLs here."));
print "<p>";
print "<button dojoType=\"dijit.form.Button\" onclick=\"return clearArticleAccessKeys()\">".
__('Unshare all articles')."</button> ";
print "</p>";
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION,
"hook_prefs_tab_section", "prefFeedsPublishedGenerated");
@ -1602,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\"
@ -1668,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\"
@ -1920,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

@ -88,7 +88,6 @@ class Pref_Filters extends Handler_Protected {
$result = $qfh_ret[0];
$articles = array();
$found = 0;
print __("Articles matching this filter:");
@ -97,12 +96,13 @@ 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"]), 100, '...');
$entry_timestamp = strtotime($line["updated"]);
$entry_tags = get_article_tags($line["id"], $_SESSION["uid"]);
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
$line = $p->hook_query_headlines($line, 100);
}
$content_preview = truncate_string(
strip_tags($line["content_preview"]), 100, '...');
$content_preview = $line["content_preview"];
if ($line["feed_title"])
$feed_title = $line["feed_title"];
@ -147,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();
@ -170,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;
@ -223,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);
@ -429,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() {
@ -467,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'
@ -585,14 +610,15 @@ class Pref_Filters extends Handler_Protected {
$enabled = checkbox_to_sql_bool($_REQUEST["enabled"]);
$match_any_rule = checkbox_to_sql_bool($_REQUEST["match_any_rule"]);
$title = $this->dbh->escape_string($_REQUEST["title"]);
$inverse = checkbox_to_sql_bool($_REQUEST["inverse"]);
$this->dbh->query("BEGIN");
/* create base filter */
$result = $this->dbh->query("INSERT INTO ttrss_filters2
(owner_uid, match_any_rule, enabled, title) VALUES
(".$_SESSION["uid"].",$match_any_rule,$enabled, '$title')");
(owner_uid, match_any_rule, enabled, title, inverse) VALUES
(".$_SESSION["uid"].",$match_any_rule,$enabled, '$title', $inverse)");
$result = $this->dbh->query("SELECT MAX(id) AS id FROM ttrss_filters2
WHERE owner_uid = ".$_SESSION["uid"]);
@ -870,6 +896,11 @@ class Pref_Filters extends Handler_Protected {
print "<div class=\"dlgButtons\">";
print "<div style=\"float : left\">
<a class=\"visibleLink\" target=\"_blank\" href=\"http://tt-rss.org/wiki/ContentFilters\">".__("Wiki: Filters")."</a>
</div>";
print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterNewRuleDlg').execute()\">".
($rule ? __("Save rule") : __('Add rule'))."</button> ";

@ -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" */
@ -765,7 +766,9 @@ class Pref_Prefs extends Handler_Protected {
dojoType=\"dijit.form.CheckBox\" $checked
type=\"checkbox\"></td>";
print "<td>$name</td>";
$plugin_icon = $checked ? "plugin.png" : "plugin_disabled.png";
print "<td><label><img src='images/$plugin_icon' alt=''> $name</label></td>";
print "<td>" . htmlspecialchars($about[1]);
if (@$about[4]) {
print " &mdash; <a target=\"_blank\" class=\"visibleLink\"
@ -818,11 +821,13 @@ class Pref_Prefs extends Handler_Protected {
print "<tr class='$rowclass'>";
$plugin_icon = $checked ? "plugin.png" : "plugin_disabled.png";
print "<td align='center'><input id='FPCHK-$name' name='plugins[]' value='$name' onclick='toggleSelectRow2(this);'
dojoType=\"dijit.form.CheckBox\" $checked $disabled
type=\"checkbox\"></td>";
print "<td><label for='FPCHK-$name'>$name</label></td>";
print "<td><label for='FPCHK-$name'><img src='images/$plugin_icon' alt=''> $name</label></td>";
print "<td><label for='FPCHK-$name'>" . htmlspecialchars($about[1]) . "</label>";
if (@$about[4]) {
print " &mdash; <a target=\"_blank\" class=\"visibleLink\"
@ -883,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"));
}
}

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

@ -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"]);
@ -467,14 +466,6 @@ class RPC extends Handler_Protected {
print_feed_cat_select("cat_id", $id, '');
}
// Silent
function clearArticleKeys() {
$this->dbh->query("UPDATE ttrss_user_entries SET uuid = '' WHERE
owner_uid = " . $_SESSION["uid"]);
return;
}
function setpanelmode() {
$wide = (int) $_REQUEST["wide"];
@ -484,7 +475,8 @@ class RPC extends Handler_Protected {
print json_encode(array("wide" => $wide));
}
function updaterandomfeed() {
static function updaterandomfeed_real($dbh) {
// Test if the feed need a update (update interval exceded).
if (DB_TYPE == "pgsql") {
$update_limit_qpart = "AND ((
@ -515,16 +507,24 @@ class RPC extends Handler_Protected {
$random_qpart = sql_random_function();
// we could be invoked from public.php with no active session
if ($_SESSION["uid"]) {
$owner_check_qpart = "AND ttrss_feeds.owner_uid = '".$_SESSION["uid"]."'";
} else {
$owner_check_qpart = "";
}
// We search for feed needing update.
$result = $this->dbh->query("SELECT ttrss_feeds.feed_url,ttrss_feeds.id
$result = $dbh->query("SELECT ttrss_feeds.feed_url,ttrss_feeds.id
FROM
ttrss_feeds, ttrss_users, ttrss_user_prefs
WHERE
ttrss_feeds.owner_uid = ttrss_users.id
AND ttrss_users.id = ttrss_user_prefs.owner_uid
AND ttrss_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL'
AND ttrss_feeds.owner_uid = ".$_SESSION["uid"]."
$update_limit_qpart $updstart_thresh_qpart
$owner_check_qpart
$update_limit_qpart
$updstart_thresh_qpart
ORDER BY $random_qpart LIMIT 30");
$feed_id = -1;
@ -535,7 +535,7 @@ class RPC extends Handler_Protected {
$tstart = time();
while ($line = $this->dbh->fetch_assoc($result)) {
while ($line = $dbh->fetch_assoc($result)) {
$feed_id = $line["id"];
if (time() - $tstart < ini_get("max_execution_time") * 0.7) {
@ -559,6 +559,10 @@ class RPC extends Handler_Protected {
}
function updaterandomfeed() {
RPC::updaterandomfeed_real($this->dbh);
}
private function markArticlesById($ids, $cmode) {
$tmp_ids = array();
@ -615,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);
}
}

@ -18,9 +18,9 @@
// *** Basic settings (important!) ***
// ***********************************
define('SELF_URL_PATH', 'http://yourserver/tt-rss/');
define('SELF_URL_PATH', 'http://example.org/tt-rss/');
// Full URL of your tt-rss installation. This should be set to the
// location of tt-rss directory, e.g. http://yourserver/tt-rss/
// location of tt-rss directory, e.g. http://example.org/tt-rss/
// You need to set this option correctly otherwise several features
// including PUSH, bookmarklets and browser integration will not work properly.
@ -50,10 +50,11 @@
// *****************************
define('PHP_EXECUTABLE', '/usr/bin/php');
// Path to PHP *COMMAND LINE* executable, used for various command-line tt-rss programs and
// update daemon. Do not try to use CGI binary here, it won't work. If you see HTTP headers
// being displayed while running tt-rss scripts, then most probably you are using the CGI
// binary. If you are unsure what to put in here, ask your hosting provider.
// Path to PHP *COMMAND LINE* executable, used for various command-line tt-rss
// programs and update daemon. Do not try to use CGI binary here, it won't work.
// If you see HTTP headers being displayed while running tt-rss scripts,
// then most probably you are using the CGI binary. If you are unsure what to
// put in here, ask your hosting provider.
define('LOCK_DIRECTORY', 'lock');
// Directory for lockfiles, must be writable to the user you run
@ -103,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.
@ -173,7 +170,8 @@
// outgoing mail. Only used with SMTP_SERVER.
define('SMTP_SECURE', '');
// used to select a secure SMTP conneciton. can be tls, ssl or enpty
// Used to select a secure SMTP connection. Allowed values: ssl, tls,
// or empty.
// ***************************************
// *** Other settings (less important) ***
@ -182,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.
@ -209,4 +213,3 @@
// if necessary (after migrating all new options from this file).
// vim:ft=php
?>

@ -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;
}
@ -110,9 +129,20 @@ div.cdm.expandable div.cdmHeader span.titleWrap {
max-width : 500px;
}
div.cdm.expandable.active div.cdmHeader span.titleWrap {
white-space : normal;
}
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 {
@ -121,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 {
@ -128,9 +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 {
@ -159,7 +201,7 @@ div.cdm.active div.cdmContent {
span.cdmExcerpt {
font-size : 11px;
color : #555;
color : #999;
font-weight : normal;
cursor : pointer;
}
@ -174,7 +216,7 @@ div.cdmFeedTitle {
border-width : 0px 0px 1px 0px;
border-style : solid;
padding : 5px 3px 5px 5px;
background : url("images/toolbar.png") bottom left;
background : url("../images/toolbar.png") bottom left;
background-repeat : repeat-x;
}
@ -214,6 +256,9 @@ div.cdm .hlFeed a {
div.cdmContentInner p {
max-width : 650px;
-webkit-hyphens: auto;
-moz-hyphens: auto;
hyphens: auto;
}
div.cdmContentInner iframe {
@ -230,38 +275,126 @@ div.cdmHeader span.author {
div#floatingTitle {
position : absolute;
z-index : 5;
top : 30px;
right : 20px;
border : 1px solid #ccc;
top : 0px;
right : 0px;
left : 0px;
border-color : #ddd;
border-width : 0px 0px 1px 0px;
border-style : solid;
background : white;
border-radius : 3px;
box-shadow : 0px 0px 3px 0px rgba(0,0,0,0.1);
color : #555;
font-size : 10px;
padding : 3px;
box-shadow : 0px 1px 1px -1px rgba(0,0,0,0.1);
}
div#floatingTitle > * {
display : table-cell;
white-space : nowrap;
vertical-align : middle;
padding : 9px 5px;
}
div#floatingTitle span.titleWrap {
max-width : 200px;
overflow : hidden;
text-overflow : ellipsis;
div#floatingTitle img {
margin-right : 4px;
margin-left : 4px;
}
div#floatingTitle img {
padding-right : 3px;
div#floatingTitle span.author {
color : #555;
font-size : 11px;
font-weight : normal;
}
div#floatingTitle a.title {
font-size : 16px;
color : #999;
-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 {
color : black;
}
div#floatingTitle img.anchor {
margin-left : 0px;
}
div#floatingTitle div.hlFeed {
padding-right : 10px;
color : #555;
font-weight : normal;
font-style : italic;
font-size : 11px;
white-space : nowrap;
}
div#floatingTitle div.hlFeed a {
border-radius : 4px;
display : inline-block;
padding : 1px 4px 1px 4px;
}
div#floatingTitle span.updated {
padding-right : 10px;
white-space : nowrap;
color : #555;
font-size : 11px;
}
div#floatingTitle div.hlFeed a {
color : #555;
}
div#floatingTitle .dijit,
div#floatingTitle span.updated,
div#floatingTitle div.scoreWrap,
div#floatingTitle div.hlFeed,
div#floatingTitle span.author,
div#floatingTitle img.tinyFeedIcon {
div#floatingTitle span.titleWrap {
width : 100%;
white-space : normal;
}
div#floatingTitle .dijit,
div#floatingTitle img.hlScorePic {
display : none;
}
.cdm.high .cdmHeader a.title.high, .cdm.high .cdmHeader .cdmExcerpt,
.cdm.high .cdmHeader span.author {
color : #00aa00;
}
.cdm.Unread.high .cdmHeader a.title.high, .cdm.Unread.high .cdmHeader .cdmExcerpt,
.cdm.Unread.high .cdmHeader span.author {
color : #00dd00;
}
.cdm .cdmHeader a.title.low, .cdm.low .cdmHeader .cdmExcerpt,
.cdm.Unread .cdmHeader a.title.low, .cdm.Unread.low .cdmHeader .cdmExcerpt,
.cdm.low .cdmHeader span.author {
color : #909090;
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;
}

@ -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;
}

@ -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;
}
@ -98,6 +107,14 @@ table.prefPluginsList td label, table.prefUserList td {
cursor : pointer;
}
table.prefPluginsList label {
white-space : nowrap;
}
table.prefPluginsList label img {
vertical-align : middle;
}
table.prefErrorLog tr.errrow td {
font-size : 10px;
}
@ -112,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%;
}

File diff suppressed because it is too large Load Diff

@ -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);
}
@ -86,7 +83,7 @@ div.error {
}
div.warning img, div.notice img, div.error img {
margin-right : 4px;
margin : 4px;
vertical-align : middle;
}
@ -120,7 +117,6 @@ div.rss h1 {
border-color : gray;
border-style : dotted;
color : gray;
margin-right : 90px;
}
div.rss h2 {
@ -160,7 +156,7 @@ div.rss hr {
body#sharepopup {
background-color : white;
background-image : url("images/toolbar.png");
background-image : url("../images/toolbar.png");
background-repeat : repeat-x;
background-position : bottom;
margin : 10px;

@ -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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 B

After

Width:  |  Height:  |  Size: 701 B

@ -1,87 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16px"
height="16px"
id="svg2985"
version="1.1"
inkscape:version="0.48.4 r9939"
inkscape:export-filename="Y:\public_html\testbox\tt-irc\images\alert.png"
inkscape:export-xdpi="102.17"
inkscape:export-ydpi="102.17"
sodipodi:docname="alert.svg">
<defs
id="defs2987" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.197802"
inkscape:cx="0.97227717"
inkscape:cy="13.508447"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1600"
inkscape:window-height="1137"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1" />
<metadata
id="metadata2990">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
sodipodi:type="star"
style="opacity:1;fill:#d40000;fill-opacity:1"
id="path2993"
sodipodi:sides="3"
sodipodi:cx="4.7752476"
sodipodi:cy="2.3499999"
sodipodi:r1="8.1370182"
sodipodi:r2="4.0685091"
sodipodi:arg1="0.52359878"
sodipodi:arg2="1.5707963"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 11.822112,6.418509 -7.0468643,0 -7.0468646,0 3.5234322,-6.10276355 3.5234323,-6.10276375 3.5234322,6.10276356 z"
inkscape:transform-center-y="-2.0342545"
transform="translate(3.2247524,7.6842546)" />
<text
xml:space="preserve"
style="font-size:11px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Times New Roman;-inkscape-font-specification:Sans Bold"
x="6.1657715"
y="12.508285"
id="text3767"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3769"
x="6.1657715"
y="12.508285">!</tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 B

After

Width:  |  Height:  |  Size: 555 B

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16px"
height="16px"
id="svg2985"
version="1.1"
inkscape:version="0.48.2 r9819"
sodipodi:docname="close_notify.svg">
<defs
id="defs2987" />
<sodipodi:namedview
id="base"
pagecolor="#88b0f0"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="22.197802"
inkscape:cx="0.97227717"
inkscape:cy="8"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1920"
inkscape:window-height="1138"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1" />
<metadata
id="metadata2990">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
sodipodi:type="arc"
style="opacity:1;fill:none;fill-opacity:1;stroke:#454545;stroke-width:0.73948608000000005;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
id="path2997"
sodipodi:cx="6.6673269"
sodipodi:cy="7.1702971"
sodipodi:rx="5.270792"
sodipodi:ry="5.270792"
d="m 11.938119,7.1702971 a 5.270792,5.270792 0 1 1 -10.5415841,0 5.270792,5.270792 0 1 1 10.5415841,0 z"
transform="matrix(1.3522905,0,0,1.3522905,-1.0161629,-1.6963247)" />
<path
style="fill:none;stroke:#454545;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;opacity:1"
d="M 4.4322613,4.4322611 11.567739,11.567739"
id="path3771"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#454545;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;opacity:1"
d="M 11.567739,4.432261 4.432261,11.567739"
id="path3771-1"
inkscape:connector-curvature="0" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 B

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 B

After

Width:  |  Height:  |  Size: 633 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 B

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

@ -1,73 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16.000000px"
height="16.000000px"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.42"
sodipodi:docbase="/home/fox/public_html/testbox/tt-rss/images"
sodipodi:docname="mark_set.svg"
inkscape:export-filename="/home/fox/public_html/testbox/tt-rss/images/mark_set.png"
inkscape:export-xdpi="90.000000"
inkscape:export-ydpi="90.000000">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0000000"
inkscape:pageshadow="2"
inkscape:zoom="31.678384"
inkscape:cx="6.9004349"
inkscape:cy="7.4155540"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:window-width="1600"
inkscape:window-height="1131"
inkscape:window-x="0"
inkscape:window-y="25"
showguides="true"
inkscape:guide-bbox="true" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
sodipodi:type="star"
style="opacity:1.0000000;fill:#a8cdfd;fill-opacity:1.0000000;stroke:#4f9dfd;stroke-width:0.99999938;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-opacity:1.0000000"
id="path1306"
sodipodi:sides="5"
sodipodi:cx="7.3551731"
sodipodi:cy="1.6684607"
sodipodi:r1="6.3745561"
sodipodi:r2="3.1872780"
sodipodi:arg1="0.78539816"
sodipodi:arg2="1.4137167"
inkscape:flatsided="false"
inkscape:rounded="0.0000000"
inkscape:randomized="0.0000000"
d="M 11.862665,6.1759525 L 7.8537732,4.8164981 L 4.4611852,7.3482318 L 4.5152876,3.1154547 L 1.0590984,0.67126048 L 5.1014272,-0.58528520 L 6.3579728,-4.6276140 L 8.8021671,-1.1714248 L 13.034944,-1.2255272 L 10.503210,2.1670609 L 11.862665,6.1759525 z "
transform="matrix(-0.707107,-0.707107,0.707107,-0.707107,12.02111,14.98939)" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16.000000px"
height="16.000000px"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.48.0 r9654"
sodipodi:docname="mark_unset.svg"
inkscape:export-filename="/home/fox/public_html/testbox/tt-rss/images/mark_unset.png"
inkscape:export-xdpi="90.000000"
inkscape:export-ydpi="90.000000"
version="1.1">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0000000"
inkscape:pageshadow="2"
inkscape:zoom="7.919596"
inkscape:cx="-13.509597"
inkscape:cy="-5.9527534"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:window-width="1600"
inkscape:window-height="1131"
inkscape:window-x="0"
inkscape:window-y="25"
showguides="true"
inkscape:guide-bbox="true"
showgrid="false"
inkscape:window-maximized="0" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
sodipodi:type="star"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#88b0f0;stroke-width:0.99999938000000022;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1306"
sodipodi:sides="5"
sodipodi:cx="7.3551731"
sodipodi:cy="1.6684607"
sodipodi:r1="6.3745561"
sodipodi:r2="3.1872780"
sodipodi:arg1="0.78539816"
sodipodi:arg2="1.4137167"
inkscape:flatsided="false"
inkscape:rounded="0.0000000"
inkscape:randomized="0.0000000"
d="M 11.862665,6.1759525 L 7.8537732,4.8164981 L 4.4611852,7.3482318 L 4.5152876,3.1154547 L 1.0590984,0.67126048 L 5.1014272,-0.58528520 L 6.3579728,-4.6276140 L 8.8021671,-1.1714248 L 13.034944,-1.2255272 L 10.503210,2.1670609 L 11.862665,6.1759525 z "
transform="matrix(-0.707107,-0.707107,0.707107,-0.707107,12.02111,14.98939)" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 B

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

@ -1,184 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="16"
height="16"
id="RSSicon"
viewBox="0 0 32 32"
inkscape:version="0.48.0 r9654"
sodipodi:docname="mark_set.svg">
<metadata
id="metadata34">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1600"
inkscape:window-height="1137"
id="namedview32"
showgrid="false"
inkscape:zoom="23.953242"
inkscape:cx="6.5252922"
inkscape:cy="10.694533"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="RSSicon" />
<defs
id="defs3">
<linearGradient
x1="30.059999"
y1="30.059999"
x2="225.94"
y2="225.94"
id="RSSg"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(0,-224)">
<stop
offset="0.0"
stop-color="#E3702D"
id="stop6" />
<stop
offset="0.1071"
stop-color="#EA7D31"
id="stop8" />
<stop
offset="0.3503"
stop-color="#F69537"
id="stop10" />
<stop
offset="0.5"
stop-color="#FB9E3A"
id="stop12" />
<stop
offset="0.7016"
stop-color="#EA7C31"
id="stop14" />
<stop
offset="0.8866"
stop-color="#DE642B"
id="stop16" />
<stop
offset="1.0"
stop-color="#D95B29"
id="stop18" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#RSSg"
id="linearGradient3029"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(0,-224)"
x1="30.059999"
y1="30.059999"
x2="225.94"
y2="225.94" />
<filter
id="filter3031"
inkscape:label="Desaturate"
x="0"
y="0"
width="1"
height="1"
inkscape:menu="Color"
inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero"
color-interpolation-filters="sRGB">
<feColorMatrix
id="feColorMatrix3033"
type="saturate"
values="0" />
</filter>
<filter
id="filter3035"
inkscape:label="Desaturate"
x="0"
y="0"
width="1"
height="1"
inkscape:menu="Color"
inkscape:menu-tooltip="Render in shades of gray by reducing saturation to zero"
color-interpolation-filters="sRGB">
<feColorMatrix
id="feColorMatrix3037"
type="saturate"
values="0" />
</filter>
</defs>
<g
id="g3021"
transform="matrix(0.10305878,0,0,0.10305878,2.808475,25.893644)"
style="opacity:1">
<rect
style="fill:#cc5d15;"
id="rect20"
y="-224"
x="0"
ry="55"
rx="55"
height="256"
width="256" />
<rect
style="fill:#f49c52;"
id="rect22"
y="-219"
x="5"
ry="50"
rx="50"
height="246"
width="246" />
<rect
style="fill:url(#linearGradient3029);"
id="rect24"
y="-214"
x="10"
ry="47"
rx="47"
height="236"
width="236" />
<circle
transform="translate(0,-224)"
style="fill:#ffffff;"
sodipodi:ry="24"
sodipodi:rx="24"
sodipodi:cy="189"
sodipodi:cx="68"
id="circle26"
r="24"
cy="189"
cx="68"
d="m 92,189 c 0,13.25483 -10.745166,24 -24,24 -13.254834,0 -24,-10.74517 -24,-24 0,-13.25483 10.745166,-24 24,-24 13.254834,0 24,10.74517 24,24 z" />
<path
style="fill:#ffffff;"
inkscape:connector-curvature="0"
id="path28"
d="M 160,-11 H 126 A 82,82 0 0 0 44,-93 v -34 a 116,116 0 0 1 116,116 z" />
<path
style="fill:#ffffff;"
inkscape:connector-curvature="0"
id="path30"
d="M 184,-11 A 140,140 0 0 0 44,-151 v -35 a 175,175 0 0 1 175,175 z" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

@ -1,149 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="16"
height="16"
id="RSSicon"
viewBox="0 0 32 32"
inkscape:version="0.48.4 r9939"
sodipodi:docname="pub_unset.svg">
<metadata
id="metadata34">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1600"
inkscape:window-height="1137"
id="namedview32"
showgrid="false"
inkscape:zoom="23.953242"
inkscape:cx="6.0451902"
inkscape:cy="5.8935127"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="RSSicon" />
<defs
id="defs3">
<linearGradient
x1="30.059999"
y1="30.059999"
x2="225.94"
y2="225.94"
id="RSSg"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(0,-224)">
<stop
offset="0.0"
stop-color="#E3702D"
id="stop6" />
<stop
offset="0.1071"
stop-color="#EA7D31"
id="stop8" />
<stop
offset="0.3503"
stop-color="#F69537"
id="stop10" />
<stop
offset="0.5"
stop-color="#FB9E3A"
id="stop12" />
<stop
offset="0.7016"
stop-color="#EA7C31"
id="stop14" />
<stop
offset="0.8866"
stop-color="#DE642B"
id="stop16" />
<stop
offset="1.0"
stop-color="#D95B29"
id="stop18" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#RSSg"
id="linearGradient3029"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(0,-224)"
x1="30.059999"
y1="30.059999"
x2="225.94"
y2="225.94" />
</defs>
<rect
width="27.385"
height="27.385"
rx="5.8834963"
ry="5.8834963"
x="2.3074989"
y="2.3075001"
id="rect20"
style="fill:#909090;fill-opacity:1" />
<rect
width="26.315275"
height="26.315275"
rx="5.3486328"
ry="5.3486328"
x="2.8423624"
y="2.8423634"
id="rect22"
style="fill:#a0a0a0;fill-opacity:0.94117647" />
<rect
width="25.245548"
height="25.245548"
rx="5.0277152"
ry="5.0277152"
x="3.3772256"
y="3.3772268"
id="rect24"
style="fill:#c0c0c0;fill-opacity:1" />
<circle
d="m 92,189 c 0,13.25483 -10.745166,24 -24,24 -13.254834,0 -24,-10.74517 -24,-24 0,-13.25483 10.745166,-24 24,-24 13.254834,0 24,10.74517 24,24 z"
cx="68"
cy="189"
r="24"
id="circle26"
sodipodi:cx="68"
sodipodi:cy="189"
sodipodi:rx="24"
sodipodi:ry="24"
style="fill:#ffffff"
transform="matrix(0.10697266,0,0,0.10697266,2.307499,2.3075002)" />
<path
d="M 19.423125,25.092677 H 15.786054 A 8.7717581,8.7717581 0 0 0 7.014296,16.320919 v -3.637071 a 12.408829,12.408829 0 0 1 12.408829,12.408829 z"
id="path28"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
<path
d="M 21.990468,25.092677 A 14.976172,14.976172 0 0 0 7.014296,10.116504 V 6.3724612 A 18.720216,18.720216 0 0 1 25.734512,25.092677 z"
id="path30"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
</svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 B

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 B

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 B

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 B

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 B

After

Width:  |  Height:  |  Size: 201 B

@ -1,80 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32px"
height="32px"
id="svg7311"
version="1.1"
inkscape:version="0.48.0 r9654"
sodipodi:docname="sign_excl.svg">
<defs
id="defs7313" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.197802"
inkscape:cx="-11.862611"
inkscape:cy="16"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1600"
inkscape:window-height="1137"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1" />
<metadata
id="metadata7316">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
sodipodi:type="arc"
style="fill:#eb6363;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
id="path6540-7"
sodipodi:cx="3.409934"
sodipodi:cy="4.3416462"
sodipodi:rx="14.271205"
sodipodi:ry="14.271205"
d="m 17.681139,4.3416462 a 14.271205,14.271205 0 1 1 -28.54241,0 14.271205,14.271205 0 1 1 28.54241,0 z"
transform="translate(12.590066,11.658354)"
inkscape:export-xdpi="73.07"
inkscape:export-ydpi="73.07" />
<text
xml:space="preserve"
style="font-size:40px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Times New Roman;-inkscape-font-specification:Times New Roman"
x="11.482422"
y="24.308594"
id="text6542-9"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan6544-5"
x="11.482422"
y="24.308594"
style="font-size:24px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill:#ffffff;fill-opacity:1;font-family:Georgia;-inkscape-font-specification:Georgia Bold">!</tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

@ -1,83 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32px"
height="32px"
id="svg2985"
version="1.1"
inkscape:version="0.48.0 r9654"
sodipodi:docname="sign_info.svg"
inkscape:export-filename="Y:\public_html\testbox\tt-rss\images\sign_info.png"
inkscape:export-xdpi="67.5"
inkscape:export-ydpi="67.5">
<defs
id="defs2987" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.7994505"
inkscape:cx="6.8311272"
inkscape:cy="-22.339561"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1600"
inkscape:window-height="1137"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1" />
<metadata
id="metadata2990">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
sodipodi:type="arc"
style="fill:#88b0f0;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
id="path6540"
sodipodi:cx="3.409934"
sodipodi:cy="4.3416462"
sodipodi:rx="14.271205"
sodipodi:ry="14.271205"
d="m 17.681139,4.3416462 a 14.271205,14.271205 0 1 1 -28.54241,0 14.271205,14.271205 0 1 1 28.54241,0 z"
transform="translate(12.590066,11.658354)"
inkscape:export-xdpi="73.07"
inkscape:export-ydpi="73.07" />
<text
xml:space="preserve"
style="font-size:40px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Times New Roman;-inkscape-font-specification:Times New Roman"
x="11.763672"
y="25.070313"
id="text6542"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan6544"
x="11.763672"
y="25.070313"
style="font-size:24px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill:#ffffff;fill-opacity:1;font-family:Georgia;-inkscape-font-specification:Georgia Bold">i</tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 B

After

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

@ -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);
}
}

@ -288,17 +288,24 @@ function hsl2rgb($arr) {
$size = @getimagesize($imageFile);
if (strtolower($size['mime']) == 'image/vnd.microsoft.icon' && class_exists("floIcon")) {
// to enable .ico support place floIcon.php into lib/
if (strtolower($size['mime']) == 'image/vnd.microsoft.icon') {
$ico = new floIcon();
@$ico->readICO($imageFile);
if (class_exists("floIcon")) {
if(count($ico->images)==0)
return null;
else
$img = @$ico->images[count($ico->images)-1]->getImageResource();
$ico = new floIcon();
@$ico->readICO($imageFile);
} else {
if(count($ico->images)==0)
return false;
else
$img = @$ico->images[count($ico->images)-1]->getImageResource();
} else {
return false;
}
} else if ($size[0] > 0 && $size[1] > 0) {
$img = @imagecreatefromstring(file_get_contents($imageFile));
}

@ -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();

@ -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\">";
@ -80,7 +79,7 @@
$class = ($feedctr % 2) ? "even" : "odd";
if ($line['articles_archived'] > 0) {
$archived = sprintf(ngettext("%d archived article", "%d archived articles", $line['articles_archived']), $line['articles_archived']);
$archived = sprintf(_ngettext("%d archived article", "%d archived articles", $line['articles_archived']), $line['articles_archived']);
$archived = "&nbsp;<span class='subscribers'>($archived)</span>";
} else {
$archived = '';
@ -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

@ -42,7 +42,8 @@
ORDER BY caption");
while ($line = db_fetch_assoc($result)) {
$rk = array($line["label_id"], $line["caption"], $line["fg_color"],
$rk = array(label_to_feed_id($line["label_id"]),
$line["caption"], $line["fg_color"],
$line["bg_color"]);
array_push($rv, $rk);
}
@ -108,7 +109,7 @@
if (!$label_id) return;
$result = db_query(
db_query(
"DELETE FROM ttrss_user_labels2
WHERE
label_id = '$label_id' AND

@ -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() {
@ -246,9 +253,6 @@ function bwLimitChange(elem) {
<div class='footer'>
<a href="http://tt-rss.org/">Tiny Tiny RSS</a>
<?php if (!defined('HIDE_VERSION')) { ?>
v<?php echo VERSION ?>
<?php } ?>
&copy; 2005&ndash;<?php echo date('Y') ?> <a href="http://fakecake.org/">Andrew Dolgov</a>
</div>

@ -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,14 +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.
@ -190,14 +223,15 @@
} // 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'];
_debug_suppress(!$debug_enabled);
_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
@ -209,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'))
@ -250,34 +283,37 @@
$pluginhost->load($user_plugins, PluginHost::KIND_USER, $owner_uid);
$pluginhost->load_data();
$rss = false;
$rss_hash = false;
if ($rss && is_object($rss) && get_class($rss) == "FeedParser") {
_debug("using previously initialized parser object");
} else {
$rss_hash = false;
$force_refetch = isset($_REQUEST["force_refetch"]);
$force_refetch = isset($_REQUEST["force_refetch"]);
if (file_exists($cache_filename) &&
is_readable($cache_filename) &&
!$auth_login && !$auth_pass &&
filemtime($cache_filename) > time() - 30) {
foreach ($pluginhost->get_hooks(PluginHost::HOOK_FETCH_FEED) as $plugin) {
$feed_data = $plugin->hook_fetch_feed($feed_data, $fetch_url, $owner_uid, $feed, $last_article_timestamp, $auth_login, $auth_pass);
}
_debug("using local cache.", $debug_enabled);
// try cache
if (!$feed_data &&
file_exists($cache_filename) &&
is_readable($cache_filename) &&
!$auth_login && !$auth_pass &&
filemtime($cache_filename) > time() - 30) {
@$feed_data = file_get_contents($cache_filename);
_debug("using local cache [$cache_filename].", $debug_enabled);
if ($feed_data) {
$rss_hash = sha1($feed_data);
}
@$feed_data = file_get_contents($cache_filename);
} else {
_debug("local cache will not be used for this feed", $debug_enabled);
}
if (!$rss) {
if ($feed_data) {
$rss_hash = sha1($feed_data);
}
foreach ($pluginhost->get_hooks(PluginHost::HOOK_FETCH_FEED) as $plugin) {
$feed_data = $plugin->hook_fetch_feed($feed_data, $fetch_url, $owner_uid, $feed);
} 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);
@ -316,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) {
@ -354,22 +400,19 @@
$rss->init();
}
if (DETECT_ARTICLE_LANGUAGE) {
require_once "lib/languagedetect/LanguageDetect.php";
$lang = new Text_LanguageDetect();
$lang->setNameMode(2);
}
// print_r($rss);
$feed = db_escape_string($feed);
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);
@ -438,7 +481,7 @@
if (!$registered_title || $registered_title == "[Unknown]") {
$feed_title = db_escape_string($rss->get_title());
$feed_title = db_escape_string(mb_substr($rss->get_title(), 0, 199));
if ($feed_title) {
_debug("registering title: $feed_title", $debug_enabled);
@ -488,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';
@ -498,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'");
@ -517,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";
@ -536,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);
@ -565,6 +615,21 @@
print "\n";
}
$entry_language = "";
if (DETECT_ARTICLE_LANGUAGE) {
$entry_language = $lang->detect($entry_title . " " . $entry_content, 1);
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();
$entry_author = $item->get_author();
@ -600,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
@ -626,32 +682,67 @@
"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)
);
$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'");
// if we allow duplicate posts, we have to continue to
// create the user entries for this feed
if (!get_pref("ALLOW_DUPLICATE_POSTS", $owner_uid, false)) {
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
@ -671,13 +762,13 @@
updated,
content,
content_hash,
cached_content,
no_orig_date,
date_updated,
date_entered,
comments,
num_comments,
plugin_data,
lang,
author)
VALUES
('$entry_title',
@ -685,41 +776,27 @@
'$entry_link',
'$entry_timestamp_fmt',
'$entry_content',
'$content_hash',
'',
$no_orig_date,
'$entry_current_hash',
false,
NOW(),
'$date_feed_processed',
'$entry_comments',
'$num_comments',
'$entry_plugin_data',
'$entry_language',
'$entry_author')");
$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;
@ -729,14 +806,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;
@ -850,7 +919,7 @@
$p = new Publisher(PUBSUBHUBBUB_HUB);
$pubsub_result = $p->publish_update($rss_link);
/* $pubsub_result = */ $p->publish_update($rss_link);
}
$result = db_query(
@ -870,53 +939,20 @@
_debug("RID: $entry_ref_id, IID: $entry_int_id", $debug_enabled);
$post_needs_update = false;
$update_insignificant = false;
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'");
}
}
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 ($mark_unread_on_update) {
db_query("UPDATE ttrss_user_entries
SET last_read = null, unread = true WHERE ref_id = '$ref_id'");
}
}
@ -938,7 +974,7 @@
if (is_array($encs)) {
foreach ($encs as $e) {
$e_item = array(
$e->link, $e->type, $e->length);
$e->link, $e->type, $e->length, $e->title, $e->width, $e->height);
array_push($enclosures, $e_item);
}
}
@ -950,18 +986,24 @@
db_query("BEGIN");
// debugging
// db_query("DELETE FROM ttrss_enclosures WHERE post_id = '$entry_ref_id'");
foreach ($enclosures as $enc) {
$enc_url = db_escape_string($enc[0]);
$enc_type = db_escape_string($enc[1]);
$enc_dur = db_escape_string($enc[2]);
$enc_title = db_escape_string($enc[3]);
$enc_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_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)");
}
}
@ -1062,11 +1104,6 @@
_debug("article processed", $debug_enabled);
}
if (!$last_updated) {
_debug("new feed, catching it up...", $debug_enabled);
catchup_feed($feed, false, $owner_uid);
}
_debug("purging feed...", $debug_enabled);
purge_feed($feed, 0, $debug_enabled);
@ -1080,21 +1117,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>
@ -1118,21 +1161,20 @@
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);
}
}
if (file_exists($local_filename)) {
/* if (file_exists($local_filename)) {
$entry->setAttribute('src', SELF_URL_PATH . '/image.php?url=' .
base64_encode($src));
}
} */
}
}
$node = $doc->getElementsByTagName('body')->item(0);
return $doc->saveXML($node);
//$node = $doc->getElementsByTagName('body')->item(0);
//return $doc->saveXML($node);
}
function expire_error_log($debug) {
@ -1370,5 +1412,8 @@
$rc = cleanup_tags( 14, 50000);
_debug("Cleaned $rc cached tags.");
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_HOUSE_KEEPING, "hook_house_keeping", "");
}
?>

@ -95,7 +95,7 @@
}
}
if (SELF_URL_PATH == "http://yourserver/tt-rss/") {
if (SELF_URL_PATH == "http://example.org/tt-rss/") {
$urlpath = preg_replace("/\w+\.php$/", "", make_self_url_path());
array_push($errors,
@ -138,10 +138,6 @@
array_push($errors, "PHP support for ctype functions are required by HTMLPurifier.");
}
if (!function_exists("iconv")) {
array_push($errors, "PHP support for iconv is required to handle multiple charsets.");
}
/* if (ini_get("safe_mode")) {
array_push($errors, "PHP safe mode setting is not supported.");
} */

@ -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'); ?>

@ -62,11 +62,17 @@
return false;
}
if ($_SESSION["ref_schema_version"] != session_get_schema_version(true))
if ($_SESSION["ref_schema_version"] != session_get_schema_version(true)) {
$_SESSION["login_error_msg"] =
__("Session failed to validate (schema version changed)");
return false;
}
if (sha1($_SERVER['HTTP_USER_AGENT']) != $_SESSION["user_agent"])
if (sha1($_SERVER['HTTP_USER_AGENT']) != $_SESSION["user_agent"]) {
$_SESSION["login_error_msg"] =
__("Session failed to validate (user agent changed)");
return false;
}
if ($_SESSION["uid"]) {
$result = Db::get()->query(
@ -74,11 +80,19 @@
// user not found
if (Db::get()->num_rows($result) == 0) {
$_SESSION["login_error_msg"] =
__("Session failed to validate (user not found)");
return false;
} else {
$pwd_hash = Db::get()->fetch_result($result, 0, "pwd_hash");
if ($pwd_hash != $_SESSION["pwd_hash"]) {
$_SESSION["login_error_msg"] =
__("Session failed to validate (password changed)");
return false;
}
}

@ -1,13 +1,13 @@
<?php
define('VERSION_STATIC', '1.8');
define('VERSION_STATIC', '1.14');
function get_version() {
date_default_timezone_set('UTC');
$root_dir = dirname(dirname(__FILE__));
if (is_dir("$root_dir/.git") && file_exists("$root_dir/.git/ORIG_HEAD")) {
if (is_dir("$root_dir/.git") && file_exists("$root_dir/.git/refs/heads/master")) {
$suffix = substr(trim(file_get_contents("$root_dir/.git/ORIG_HEAD")), 0, 7);
$suffix = substr(trim(file_get_contents("$root_dir/.git/refs/heads/master")), 0, 7);
return VERSION_STATIC . ".$suffix";
} else {

@ -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,19 +90,19 @@
<?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);
} ?>
<script type="text/javascript">
require({cache:{}});
<?php
require 'lib/jshrink/Minifier.php';
require_once 'lib/jshrink/Minifier.php';
print get_minified_js(array("tt-rss",
"functions", "feedlist", "viewfeed", "FeedTree", "PluginHost"));
@ -134,7 +138,7 @@
</div>
</div>
<div id="notify" class="notify" style="display : none"></div>
<div id="notify" class="notify"></div>
<div id="cmdline" style="display : none"></div>
<div id="headlines-tmp" style="display : none"></div>
@ -153,11 +157,15 @@
<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"
onclick="collapse_feedlist()"
title="<?php echo __('Collapse feedlist') ?>" style="display : inline">
title="<?php echo __('Collapse feedlist') ?>" style="display : none">
&lt;&lt;</button>
<select name="view_mode" title="<?php echo __('Show articles') ?>"
@ -210,7 +218,7 @@
<button id="net-alert" dojoType="dijit.form.Button" style="display : none" disabled="true"
title="<?php echo __("Communication problem with server.") ?>">
<img
src="images/alert.png" />
src="images/error.png" />
</button>
<button id="newVersionIcon" dojoType="dijit.form.Button" style="display : none">
@ -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"

@ -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 = "";
@ -80,13 +87,13 @@
}
function print_error($msg) {
print "<div class='error'><span><img src='../images/sign_excl.svg'></span>
print "<div class='error'><span><img src='../images/alert.png'></span>
<span>$msg</span></div>";
}
function print_notice($msg) {
print "<div class=\"notice\">
<span><img src=\"../images/sign_info.svg\"></span><span>$msg</span></div>";
<span><img src=\"../images/information.png\"></span><span>$msg</span></div>";
}
function db_connect($host, $user, $pass, $db, $type, $port = false) {
@ -199,7 +206,7 @@
}
function make_self_url_path() {
$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
$url_path = ((!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") ? 'http://' : 'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
return $url_path;
}
@ -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>

@ -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) {

@ -10,6 +10,7 @@ var PluginHost = {
HOOK_ARTICLE_COLLAPSED: 7,
HOOK_PARAMS_LOADED: 8,
HOOK_RUNTIME_INFO_LOADED: 9,
HOOK_FLOATING_TITLE: 10,
hooks: [],
register: function (name, callback) {
if (typeof(this.hooks[name]) == 'undefined')

@ -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,21 @@ 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';
img.className = 'markedPic';
tnode._filterIconNode = img;
dojo.place(tnode._filterIconNode, tnode.labelNode, 'before');
}
return tnode;
},

@ -22,7 +22,7 @@ dojo.declare("fox.PrefLabelTree", lib.CheckBoxTree, {
if (type == 'label') {
var span = dojo.doc.createElement('span');
span.innerHTML = '&alpha;';
span.className = 'labelColorIndicator2';
span.className = 'labelColorIndicator';
span.id = 'LICID-' + bare_id;
span.setStyle({
@ -38,6 +38,6 @@ dojo.declare("fox.PrefLabelTree", lib.CheckBoxTree, {
},
getIconClass: function (item, opened) {
return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "invisible";
},
},
});

@ -2,6 +2,7 @@ var _infscroll_disable = 0;
var _infscroll_request_sent = 0;
var _search_query = false;
var _viewfeed_last = 0;
var _viewfeed_timeout = false;
var counters_last_request = 0;
@ -34,10 +35,7 @@ function loadMoreHeadlines() {
} else if (_search_query) {
offset = num_all;
} else if (view_mode == "adaptive") {
if (num_unread > 0)
offset = unread_in_buffer;
else
offset = num_all;
offset = num_unread > 0 ? unread_in_buffer : num_all;
} else {
offset = num_all;
}
@ -52,7 +50,7 @@ function loadMoreHeadlines() {
}
function viewfeed(feed, method, is_cat, offset, background, infscroll_req) {
function viewfeed(feed, method, is_cat, offset, background, infscroll_req, can_wait) {
try {
if (is_cat == undefined)
is_cat = false;
@ -94,7 +92,7 @@ function viewfeed(feed, method, is_cat, offset, background, infscroll_req) {
var toolbar_query = Form.serialize("main_toolbar_form");
var query = "?op=feeds&method=view&feed=" + feed + "&" +
var query = "?op=feeds&method=view&feed=" + param_escape(feed) + "&" +
toolbar_query;
if (method) {
@ -132,15 +130,24 @@ function viewfeed(feed, method, is_cat, offset, background, infscroll_req) {
console.log(query);
if (can_wait && _viewfeed_timeout) {
setFeedExpandoIcon(getActiveFeedId(), activeFeedIsCat(), 'images/blank_icon.gif');
clearTimeout(_viewfeed_timeout);
}
setActiveFeedId(feed, is_cat);
new Ajax.Request("backend.php", {
parameters: query,
onComplete: function(transport) {
setFeedExpandoIcon(feed, is_cat, 'images/blank_icon.gif');
headlines_callback2(transport, offset, background, infscroll_req);
PluginHost.run(PluginHost.HOOK_FEED_LOADED, [feed, is_cat]);
} });
timeout_ms = can_wait ? 250 : 0;
_viewfeed_timeout = setTimeout(function() {
new Ajax.Request("backend.php", {
parameters: query,
onComplete: function(transport) {
setFeedExpandoIcon(feed, is_cat, 'images/blank_icon.gif');
headlines_callback2(transport, offset, background, infscroll_req);
PluginHost.run(PluginHost.HOOK_FEED_LOADED, [feed, is_cat]);
} });
}, timeout_ms); // Wait 250ms
} catch (e) {
exception_error("viewfeed", e);

@ -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();
@ -183,11 +182,6 @@ function param_unescape(arg) {
return unescape(arg);
}
function hide_notify() {
Element.hide('notify');
}
function notify_real(msg, no_hide, n_type) {
var n = $("notify");
@ -199,12 +193,11 @@ function notify_real(msg, no_hide, n_type) {
}
if (msg == "") {
if (Element.visible(n)) {
notify_hide_timerid = window.setTimeout("hide_notify()", 0);
if (n.hasClassName("visible")) {
notify_hide_timerid = window.setTimeout(function() {
n.removeClassName("visible") }, 0);
}
return;
} else {
Element.show(n);
}
/* types:
@ -218,21 +211,21 @@ function notify_real(msg, no_hide, n_type) {
msg = "<span class=\"msg\"> " + __(msg) + "</span>";
if (n_type == 1) {
n.className = "notify";
} else if (n_type == 2) {
n.className = "notify progress";
if (n_type == 2) {
n.className = "notify notify_progress visible";
msg = "<span><img src='images/indicator_white.gif'></span>" + msg;
no_hide = true;
} else if (n_type == 3) {
n.className = "notify error";
msg = "<span><img src='images/sign_excl.svg'></span>" + msg;
n.className = "notify notify_error visible";
msg = "<span><img src='images/alert.png'></span>" + msg;
} else if (n_type == 4) {
n.className = "notify info";
msg = "<span><img src='images/sign_info.svg'></span>" + msg;
n.className = "notify notify_info visible";
msg = "<span><img src='images/information.png'></span>" + msg;
} else {
n.className = "notify visible";
}
msg += " <span><img src=\"images/close_notify.svg\" class=\"close\" title=\"" +
msg += " <span><img src=\"images/cross.png\" class=\"close\" title=\"" +
__("Click to close") + "\" onclick=\"notify('')\"></span>";
// msg = "<img src='images/live_com_loading.gif'> " + msg;
@ -240,7 +233,8 @@ function notify_real(msg, no_hide, n_type) {
n.innerHTML = msg;
if (!no_hide) {
notify_hide_timerid = window.setTimeout("hide_notify()", 5*1000);
notify_hide_timerid = window.setTimeout(function() {
n.removeClassName("visible") }, 5*1000);
}
}
@ -829,7 +823,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 +855,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 +1151,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 +1288,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 +1950,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();
}

@ -1529,25 +1529,6 @@ function clearFeedAccessKeys() {
return false;
}
function clearArticleAccessKeys() {
var ok = confirm(__("This will invalidate all previously shared article URLs. Continue?"));
if (ok) {
notify_progress("Clearing URLs...");
var query = "?op=rpc&method=clearArticleKeys";
new Ajax.Request("backend.php", {
parameters: query,
onComplete: function(transport) {
notify_info("Shared URLs cleared.");
} });
}
return false;
}
function resetFilterOrder() {
try {
notify_progress("Loading, please wait...");

@ -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");
@ -272,13 +273,13 @@ function init() {
var rv = dijit.byId("feedTree").getNextFeed(
getActiveFeedId(), activeFeedIsCat());
if (rv) viewfeed(rv[0], '', rv[1]);
if (rv) viewfeed(rv[0], '', rv[1], null, null, null, true);
};
hotkey_actions["prev_feed"] = function() {
var rv = dijit.byId("feedTree").getPreviousFeed(
getActiveFeedId(), activeFeedIsCat());
if (rv) viewfeed(rv[0], '', rv[1]);
if (rv) viewfeed(rv[0], '', rv[1], null, null, null, true);
};
hotkey_actions["next_article"] = function() {
moveToPost('next');
@ -301,21 +302,27 @@ function init() {
hotkey_actions["collapse_article"] = function() {
var id = getActiveArticleId();
var elem = $("CICD-"+id);
if(elem.visible()) {
cdmCollapseArticle(null, id);
}
else {
cdmExpandArticle(id);
if (elem) {
if (elem.visible()) {
cdmCollapseArticle(null, id);
}
else {
cdmExpandArticle(id);
}
}
};
hotkey_actions["toggle_expand"] = function() {
var id = getActiveArticleId();
var elem = $("CICD-"+id);
if(elem.visible()) {
cdmCollapseArticle(null, id, false);
}
else {
cdmExpandArticle(id);
if (elem) {
if (elem.visible()) {
cdmCollapseArticle(null, id, false);
}
else {
cdmExpandArticle(id);
}
}
};
hotkey_actions["search_dialog"] = function() {
@ -339,6 +346,9 @@ function init() {
hotkey_actions["dismiss_selected"] = function() {
dismissSelectedArticles();
};
hotkey_actions["dismiss_read"] = function() {
dismissReadArticles();
};
hotkey_actions["open_in_new_window"] = function() {
if (getActiveArticleId()) {
openArticleInNewWindow(getActiveArticleId());
@ -497,6 +507,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);
}
};
@ -547,36 +561,26 @@ 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',
function (args) {
setCookie("ttrss_fh_width", args.w, getInitParam("cookie_lifetime"));
if (args && args.w >= 0) {
setCookie("ttrss_fh_width", args.w, getInitParam("cookie_lifetime"));
}
});
var tmph = dojo.connect(dijit.byId('content-insert'), 'resize',
function (args) {
setCookie("ttrss_ci_width", args.w, getInitParam("cookie_lifetime"));
setCookie("ttrss_ci_height", args.h, getInitParam("cookie_lifetime"));
if (args && args.w >= 0 && args.h >= 0) {
setCookie("ttrss_ci_width", args.w, getInitParam("cookie_lifetime"));
setCookie("ttrss_ci_height", args.h, getInitParam("cookie_lifetime"));
}
});
});
@ -617,6 +621,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")) {
@ -699,6 +706,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;
@ -844,11 +855,16 @@ function hotkey_handler(e) {
var keycode = false;
var shift_key = false;
var ctrl_key = false;
var alt_key = false;
var meta_key = false;
var cmdline = $('cmdline');
shift_key = e.shiftKey;
ctrl_key = e.ctrlKey;
alt_key = e.altKey;
meta_key = e.metaKey;
if (window.event) {
keycode = window.event.keyCode;
@ -890,6 +906,8 @@ function hotkey_handler(e) {
// ensure ^*char notation
if (shift_key) hotkey = "*" + hotkey;
if (ctrl_key) hotkey = "^" + hotkey;
if (alt_key) hotkey = "+" + hotkey;
if (meta_key) hotkey = "%" + hotkey;
hotkey = hotkey_prefix ? hotkey_prefix + " " + hotkey : hotkey;
hotkey_prefix = false;
@ -975,6 +993,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'];
@ -1021,16 +1045,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);
}
@ -1050,11 +1079,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");
@ -1064,10 +1095,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");

@ -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
@ -835,6 +839,8 @@ function selectionToggleUnread(set_state, callback, no_error, ids) {
}
}
updateFloatingTitle(true);
if (rows.length > 0) {
var cmode = "";
@ -959,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-", "");
@ -1241,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) {
@ -1273,15 +1281,17 @@ function headlines_scroll_handler(e) {
unpackVisibleHeadlines();
// set topmost child in the buffer as active
if (getInitParam("cdm_auto_catchup") == 1 &&
(!isCdmMode() || getInitParam("cdm_expanded"))) {
if (isCdmMode() && getInitParam("cdm_auto_catchup") == 1 &&
getSelectedArticleIds2().length <= 1 &&
getInitParam("cdm_expanded")) {
var rows = $$("#headlines-frame > div[id*=RROW]");
for (var i = 0; i < rows.length; i++) {
var child = rows[i];
if ($("headlines-frame").scrollTop < child.offsetTop &&
child.offsetTop - $("headlines-frame").scrollTop < 100) {
if ($("headlines-frame").scrollTop <= child.offsetTop &&
child.offsetTop - $("headlines-frame").scrollTop < 100 &&
child.id.replace("RROW-", "") != _active_article_id) {
if (_active_article_id) {
var row = $("RROW-" + _active_article_id);
@ -1302,18 +1312,23 @@ 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 (getInitParam("cdm_expanded") && isCdmMode()) {
if (isCdmMode()) {
updateFloatingTitle();
}
@ -1346,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) {
@ -1353,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) {
@ -1379,6 +1418,8 @@ function catchupBatchedArticles() {
catchup_id_batch.remove(id);
});
updateFloatingTitle(true);
} });
}
@ -1487,6 +1528,12 @@ function cdmCollapseArticle(event, id, unmark) {
if (event) Event.stop(event);
PluginHost.run(PluginHost.HOOK_ARTICLE_COLLAPSED, id);
if (row.offsetTop < $("headlines-frame").scrollTop)
scrollToRowId(row.id);
Element.hide("floatingTitle");
$("floatingTitle").setAttribute("rowid", false);
}
} catch (e) {
@ -1591,9 +1638,9 @@ function show_labels_in_headlines(transport) {
if (data) {
data['info-for-headlines'].each(function(elem) {
var ctr = $("HLLCTR-" + elem.id);
if (ctr) ctr.innerHTML = elem.labels;
$$(".HLLCTR-" + elem.id).each(function(ctr) {
ctr.innerHTML = elem.labels;
});
});
}
} catch (e) {
@ -1611,6 +1658,12 @@ function dismissArticle(id) {
new Effect.Fade(elem, {duration : 0.5});
// Remove the content, too
var elem_content = $("CICD-" + id);
if (elem_content) {
Element.remove(elem_content);
}
if (id == getActiveArticleId()) {
setActiveArticleId(0);
}
@ -1634,6 +1687,12 @@ function dismissSelectedArticles() {
ids[i] != getActiveArticleId()) {
new Effect.Fade(elem, {duration : 0.5});
sel.push(ids[i]);
// Remove the content, too
var elem_content = $("CICD-" + ids[i]);
if (elem_content) {
Element.remove(elem_content);
}
} else {
tmp.push(ids[i]);
}
@ -1661,13 +1720,19 @@ function dismissReadArticles() {
!elem.hasClassName("Selected")) {
new Effect.Fade(elem, {duration : 0.5});
// Remove the content, too
var elem_content = $("CICD-" + ids[i]);
if (elem_content) {
Element.remove(elem_content);
}
} else {
tmp.push(ids[i]);
}
}
} catch (e) {
exception_error("dismissSelectedArticles", e);
exception_error("dismissReadArticles", e);
}
}
@ -1716,6 +1781,7 @@ function cdmClicked(event, id) {
if (article_is_unread) {
decrementFeedCounter(getActiveFeedId(), activeFeedIsCat());
updateFloatingTitle(true);
}
var query = "?op=rpc&method=catchupSelected" +
@ -1730,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);
@ -1890,58 +1957,39 @@ function closeArticlePanel() {
dijit.byId("content-insert"));
}
function initHeadlinesMenu() {
function initFloatingMenu() {
try {
if (dijit.byId("headlinesMenu"))
dijit.byId("headlinesMenu").destroyRecursive();
var ids = [];
if (!isCdmMode()) {
nodes = $$("#headlines-frame > div[id*=RROW]");
} else {
nodes = $$("#headlines-frame span[id*=RTITLE]");
}
if (dijit.byId("floatingMenu"))
dijit.byId("floatingMenu").destroyRecursive();
nodes.each(function(node) {
ids.push(node.id);
});
var menu = new dijit.Menu({
id: "headlinesMenu",
targetNodeIds: ids,
});
var menu = new dijit.Menu({
id: "floatingMenu",
targetNodeIds: ["floatingTitle"]
});
var tmph = dojo.connect(menu, '_openMyself', function (event) {
var callerNode = event.target, match = null, tries = 0;
var id = $("floatingTitle").getAttribute("rowid").replace("RROW-", "");
while (match == null && callerNode && tries <= 3) {
match = callerNode.id.match("^[A-Z]+[-]([0-9]+)$");
callerNode = callerNode.parentNode;
++tries;
}
headlinesMenuCommon(menu, id);
if (match) this.callerRowId = parseInt(match[1]);
});
menu.startup();
} catch (e) {
exception_error("initFloatingMenu", e);
}
}
/* if (!isCdmMode())
menu.addChild(new dijit.MenuItem({
label: __("View article"),
onClick: function(event) {
view(this.getParent().callerRowId);
}})); */
function headlinesMenuCommon(menu, base_id) {
try {
menu.addChild(new dijit.MenuItem({
label: __("Open original article"),
onClick: function(event) {
openArticleInNewWindow(this.getParent().callerRowId);
openArticleInNewWindow(base_id ? base_id : this.getParent().callerRowId);
}}));
menu.addChild(new dijit.MenuItem({
label: __("Display article URL"),
onClick: function(event) {
displayArticleUrl(this.getParent().callerRowId);
displayArticleUrl(base_id ? base_id : this.getParent().callerRowId);
}}));
menu.addChild(new dijit.MenuSeparator());
@ -1951,7 +1999,7 @@ function initHeadlinesMenu() {
onClick: function(event) {
var ids = getSelectedArticleIds2();
// cast to string
var id = this.getParent().callerRowId + "";
var id = (base_id ? base_id : this.getParent().callerRowId) + "";
ids = ids.size() != 0 && ids.indexOf(id) != -1 ? ids : [id];
selectionToggleUnread(undefined, false, true, ids);
@ -1962,7 +2010,7 @@ function initHeadlinesMenu() {
onClick: function(event) {
var ids = getSelectedArticleIds2();
// cast to string
var id = this.getParent().callerRowId + "";
var id = (base_id ? base_id : this.getParent().callerRowId) + "";
ids = ids.size() != 0 && ids.indexOf(id) != -1 ? ids : [id];
selectionToggleMarked(undefined, false, true, ids);
@ -1973,7 +2021,7 @@ function initHeadlinesMenu() {
onClick: function(event) {
var ids = getSelectedArticleIds2();
// cast to string
var id = this.getParent().callerRowId + "";
var id = (base_id ? base_id : this.getParent().callerRowId) + "";
ids = ids.size() != 0 && ids.indexOf(id) != -1 ? ids : [id];
selectionTogglePublished(undefined, false, true, ids);
@ -1984,13 +2032,13 @@ function initHeadlinesMenu() {
menu.addChild(new dijit.MenuItem({
label: __("Mark above as read"),
onClick: function(event) {
catchupRelativeToArticle(0, this.getParent().callerRowId);
catchupRelativeToArticle(0, base_id ? base_id : this.getParent().callerRowId);
}}));
menu.addChild(new dijit.MenuItem({
label: __("Mark below as read"),
onClick: function(event) {
catchupRelativeToArticle(1, this.getParent().callerRowId);
catchupRelativeToArticle(1, base_id ? base_id : this.getParent().callerRowId);
}}));
@ -2016,7 +2064,7 @@ function initHeadlinesMenu() {
onClick: function(event) {
var ids = getSelectedArticleIds2();
// cast to string
var id = this.getParent().ownerMenu.callerRowId + "";
var id = (base_id ? base_id : this.getParent().ownerMenu.callerRowId) + "";
ids = ids.size() != 0 && ids.indexOf(id) != -1 ? ids : [id];
@ -2029,7 +2077,7 @@ function initHeadlinesMenu() {
onClick: function(event) {
var ids = getSelectedArticleIds2();
// cast to string
var id = this.getParent().ownerMenu.callerRowId + "";
var id = (base_id ? base_id : this.getParent().ownerMenu.callerRowId) + "";
ids = ids.size() != 0 && ids.indexOf(id) != -1 ? ids : [id];
@ -2050,8 +2098,117 @@ function initHeadlinesMenu() {
}
} catch (e) {
exception_error("headlinesMenuCommon", e);
}
}
function initHeadlinesMenu() {
try {
if (dijit.byId("headlinesMenu"))
dijit.byId("headlinesMenu").destroyRecursive();
var ids = [];
if (!isCdmMode()) {
nodes = $$("#headlines-frame > div[id*=RROW]");
} else {
nodes = $$("#headlines-frame span[id*=RTITLE]");
}
nodes.each(function(node) {
ids.push(node.id);
});
var menu = new dijit.Menu({
id: "headlinesMenu",
targetNodeIds: ids,
});
var tmph = dojo.connect(menu, '_openMyself', function (event) {
var callerNode = event.target, match = null, tries = 0;
while (match == null && callerNode && tries <= 3) {
match = callerNode.id.match("^[A-Z]+[-]([0-9]+)$");
callerNode = callerNode.parentNode;
++tries;
}
if (match) this.callerRowId = parseInt(match[1]);
});
headlinesMenuCommon(menu, false);
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);
}
@ -2195,33 +2352,67 @@ function openSelectedAttachment(elem) {
}
}
function updateFloatingTitle() {
function scrollToRowId(id) {
try {
var row = $(id);
if (row)
$("headlines-frame").scrollTop = row.offsetTop;
} catch (e) {
exception_error("scrollToRowId", e);
}
}
function updateFloatingTitle(unread_only) {
try {
if (!isCdmMode()) return;
var hf = $("headlines-frame");
var elems = $$("#headlines-frame > div[id*=RROW]");
for (var i = 0; i < elems.length; i++) {
var child = elems[i];
if (child.offsetTop + child.offsetHeight > hf.scrollTop) {
if (child && child.offsetTop + child.offsetHeight > hf.scrollTop) {
var header = child.getElementsByClassName("cdmHeader")[0];
if (child.id != $("floatingTitle").getAttribute("rowid")) {
$("floatingTitle").setAttribute("rowid", child.id);
$("floatingTitle").innerHTML = header.innerHTML;
if (unread_only || child.id != $("floatingTitle").getAttribute("rowid")) {
if (child.id != $("floatingTitle").getAttribute("rowid")) {
$("floatingTitle").setAttribute("rowid", child.id);
$("floatingTitle").innerHTML = header.innerHTML;
$("floatingTitle").firstChild.innerHTML = "<img class='anchor markedPic' src='images/page_white_go.png' onclick=\"scrollToRowId('"+child.id+"')\">" + $("floatingTitle").firstChild.innerHTML;
initFloatingMenu();
var cb = $$("#floatingTitle .dijitCheckBox")[0];
if (cb)
cb.parentNode.removeChild(cb);
}
if (child.hasClassName("Unread"))
$("floatingTitle").addClassName("Unread");
else
$("floatingTitle").removeClassName("Unread");
PluginHost.run(PluginHost.HOOK_FLOATING_TITLE, child);
}
if (child.offsetTop < hf.scrollTop - header.offsetHeight - 100 &&
child.offsetTop + child.offsetHeight - hf.scrollTop > 100)
if (child.offsetTop < hf.scrollTop - header.offsetHeight &&
child.offsetTop + child.offsetHeight - hf.scrollTop > header.offsetHeight)
Element.show("floatingTitle");
else
Element.hide("floatingTitle");
break;
return;
}
}
} catch (e) {
exception_error("updateFloatingTitle", e);
}

@ -0,0 +1,20 @@
<?php
// Compatibility shim for hooking up jimIcon to Tiny Tiny RSS.
require_once "jimIcon.php";
class floIconIcon {
function getImageResource() {
return $this->img;
}
}
class floIcon {
function readICO($file) {
$jim = new jimIcon();
$icon = new floIconIcon();
$icon->img = $jim->fromiconstring(file_get_contents($file));
$this->images = array($icon);
}
}
?>

@ -0,0 +1,270 @@
<?php
// Simple .ICO parsing. The ICO format is insanely complex and this may
// fail to correctly handle some technically valid files, but it works
// on the majority I've found.
//
// jimIcon was written in 2013 by Jim Paris <jim@jtan.com> and is
// released under the terms of the CC0:
//
// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to
// the public domain worldwide. This software is distributed without
// any arranty.
//
// You may have received a copy of the CC0 Public Domain Dedication
// along with this software. If not, see
// http://creativecommons.org/publicdomain/zero/1.0/
class jimIcon {
// Get an image color from a string
function get_color($str, $img) {
$b = ord($str[0]);
$g = ord($str[1]);
$r = ord($str[2]);
if (strlen($str) > 3) {
$a = 127 - (ord($str[3]) / 2);
if ($a != 0 && $a != 127)
$this->had_alpha = 1;
} else {
$a = 0;
}
if ($a != 127)
$this->all_transaprent = 0;
return imagecolorallocatealpha($img, $r, $g, $b, $a);
}
// Given a string with the contents of an .ICO,
// return a GD image of the icon, or false on error.
function fromiconstring($ico) {
$this->error = "(unknown error)";
$this->had_alpha = 0;
// Read header
if (strlen($ico) < 6) {
$this->error = "too short";
return false;
}
$h = unpack("vzero/vtype/vnum", $ico);
// Must be ICO format with at least one image
if ($h["zero"] != 0 || $h["type"] != 1 || $h["num"] == 0) {
// See if we can just parse it with GD directly
// if it's not ICO format; maybe it was a mislabeled
// PNG or something.
$i = @imagecreatefromstring($ico);
if ($i) {
imagesavealpha($i, true);
return $i;
}
$this->error = "not ICO or other image";
return false;
}
// Read directory entries to find the biggest image
$most_pixels = 0;
for ($i = 0; $i < $h["num"]; $i++) {
$entry = substr($ico, 6 + 16 * $i, 16);
if (!$entry || strlen($entry) < 16)
continue;
$e = unpack("Cwidth/" .
"Cheight/" .
"Ccolors/" .
"Czero/" .
"vplanes/" .
"vbpp/" .
"Vsize/" .
"Voffset/",
$entry);
if ($e["width"] == 0)
$e["width"] = 256;
if ($e["height"] == 0)
$e["height"] = 256;
if ($e["zero"] != 0) {
$this->error = "nonzero reserved field";
return false;
}
$pixels = $e["width"] * $e["height"];
if ($pixels > $most_pixels) {
$most_pixels = $pixels;
$most = $e;
}
}
if ($most_pixels == 0) {
$this->error = "no pixels";
return false;
}
$e = $most;
// Extract image data
$data = substr($ico, $e["offset"], $e["size"]);
if (!$data || strlen($data) != $e["size"]) {
$this->error = "bad image data";
return false;
}
// See if we can parse it (might be PNG format here)
$i = @imagecreatefromstring($data);
if ($i) {
imagesavealpha($img, true);
return $i;
}
// Must be a BMP. Parse it ourselves.
$img = imagecreatetruecolor($e["width"], $e["height"]);
imagesavealpha($img, true);
$bg = imagecolorallocatealpha($img, 255, 0, 0, 127);
imagefill($img, 0, 0, $bg);
// Skip over the BITMAPCOREHEADER or BITMAPINFOHEADER;
// we'll just assume the palette and pixel data follow
// in the most obvious format as described by the icon
// directory entry.
$bitmapinfo = unpack("Vsize", $data);
if ($bitmapinfo["size"] == 40) {
$info = unpack("Vsize/" .
"Vwidth/" .
"Vheight/" .
"vplanes/" .
"vbpp/" .
"Vcompress/" .
"Vsize/" .
"Vxres/" .
"Vyres/" .
"Vpalcolors/" .
"Vimpcolors/", $data);
if ($e["bpp"] == 0) {
$e["bpp"] = $info["bpp"];
}
}
$data = substr($data, $bitmapinfo["size"]);
$height = $e["height"];
$width = $e["width"];
$bpp = $e["bpp"];
// For indexed images, we only support 1, 4, or 8 BPP
switch ($bpp) {
case 1:
case 4:
case 8:
$indexed = 1;
break;
case 24:
case 32:
$indexed = 0;
break;
default:
$this->error = "bad BPP $bpp";
return false;
}
$offset = 0;
if ($indexed) {
$palette = array();
$this->all_transparent = 1;
for ($i = 0; $i < (1 << $bpp); $i++) {
$entry = substr($data, $i * 4, 4);
$palette[$i] = $this->get_color($entry, $img);
}
$offset = $i * 4;
// Hack for some icons: if everything was transparent,
// discard alpha channel.
if ($this->all_transparent) {
for ($i = 0; $i < (1 << $bpp); $i++) {
$palette[$i] &= 0xffffff;
}
}
}
// Assume image data follows in bottom-up order.
// First the "XOR" image
if ((strlen($data) - $offset) < ($bpp * $height * $width / 8)) {
$this->error = "short data";
return false;
}
$XOR = array();
for ($y = $height - 1; $y >= 0; $y--) {
$x = 0;
while ($x < $width) {
if (!$indexed) {
$bytes = $bpp / 8;
$entry = substr($data, $offset, $bytes);
$pixel = $this->get_color($entry, $img);
$XOR[$y][$x] = $pixel;
$x++;
$offset += $bytes;
} elseif ($bpp == 1) {
$p = ord($data[$offset]);
for ($b = 0x80; $b > 0; $b >>= 1) {
if ($p & $b) {
$pixel = $palette[1];
} else {
$pixel = $palette[0];
}
$XOR[$y][$x] = $pixel;
$x++;
}
$offset++;
} elseif ($bpp == 4) {
$p = ord($data[$offset]);
$pixel1 = $palette[$p >> 4];
$pixel2 = $palette[$p & 0x0f];
$XOR[$y][$x] = $pixel1;
$XOR[$y][$x+1] = $pixel2;
$x += 2;
$offset++;
} elseif ($bpp == 8) {
$pixel = $palette[ord($data[$offset])];
$XOR[$y][$x] = $pixel;
$x += 1;
$offset++;
} else {
$this->error = "bad BPP";
return false;
}
}
// End of row padding
while ($offset & 3)
$offset++;
}
// Now the "AND" image, which is 1 bit per pixel. Ignore
// if some of our image data already had alpha values,
// or if there isn't enough data left.
if ($this->had_alpha ||
((strlen($data) - $offset) < ($height * $width / 8))) {
// Just return what we've got
for ($y = 0; $y < $height; $y++) {
for ($x = 0; $x < $width; $x++) {
imagesetpixel($img, $x, $y,
$XOR[$y][$x]);
}
}
return $img;
}
// Mask what we have with the "AND" image
for ($y = $height - 1; $y >= 0; $y--) {
$x = 0;
while ($x < $width) {
for ($b = 0x80;
$b > 0 && $x < $width; $b >>= 1) {
if (!(ord($data[$offset]) & $b)) {
imagesetpixel($img, $x, $y,
$XOR[$y][$x]);
}
$x++;
}
$offset++;
}
// End of row padding
while ($offset & 3)
$offset++;
}
return $img;
}
}
?>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,57 @@
<?php
class Text_LanguageDetect_Exception extends Exception
{
/**
* Database file could not be found
*/
const DB_NOT_FOUND = 10;
/**
* Database file found, but not readable
*/
const DB_NOT_READABLE = 11;
/**
* Database file is empty
*/
const DB_EMPTY = 12;
/**
* Database contents is not a PHP array
*/
const DB_NOT_ARRAY = 13;
/**
* Magic quotes are activated
*/
const MAGIC_QUOTES = 14;
/**
* Parameter of invalid type passed to method
*/
const PARAM_TYPE = 20;
/**
* Character in parameter is invalid
*/
const INVALID_CHAR = 21;
/**
* Language is not in the database
*/
const UNKNOWN_LANGUAGE = 30;
/**
* Error during block detection
*/
const BLOCK_DETECTION = 40;
/**
* Error while clustering languages
*/
const NO_HIGHEST_KEY = 50;
}

@ -0,0 +1,341 @@
<?php
/**
* Part of Text_LanguageDetect
*
* PHP version 5
*
* @category Text
* @package Text_LanguageDetect
* @author Christian Weiske <cweiske@php.net>
* @copyright 2011 Christian Weiske <cweiske@php.net>
* @license http://www.debian.org/misc/bsd.license BSD
* @version SVN: $Id$
* @link http://pear.php.net/package/Text_LanguageDetect/
*/
/**
* Provides a mapping between the languages from lang.dat and the
* ISO 639-1 and ISO-639-2 codes.
*
* Note that this class contains only languages that exist in lang.dat.
*
* @category Text
* @package Text_LanguageDetect
* @author Christian Weiske <cweiske@php.net>
* @copyright 2011 Christian Weiske <cweiske@php.net>
* @license http://www.debian.org/misc/bsd.license BSD
* @link http://www.loc.gov/standards/iso639-2/php/code_list.php
*/
class Text_LanguageDetect_ISO639
{
/**
* Maps all language names from the language database to the
* ISO 639-1 2-letter language code.
*
* NULL indicates that there is no 2-letter code.
*
* @var array
*/
public static $nameToCode2 = array(
'albanian' => 'sq',
'arabic' => 'ar',
'azeri' => 'az',
'bengali' => 'bn',
'bulgarian' => 'bg',
'cebuano' => null,
'croatian' => 'hr',
'czech' => 'cs',
'danish' => 'da',
'dutch' => 'nl',
'english' => 'en',
'estonian' => 'et',
'farsi' => 'fa',
'finnish' => 'fi',
'french' => 'fr',
'german' => 'de',
'hausa' => 'ha',
'hawaiian' => null,
'hindi' => 'hi',
'hungarian' => 'hu',
'icelandic' => 'is',
'indonesian' => 'id',
'italian' => 'it',
'kazakh' => 'kk',
'kyrgyz' => 'ky',
'latin' => 'la',
'latvian' => 'lv',
'lithuanian' => 'lt',
'macedonian' => 'mk',
'mongolian' => 'mn',
'nepali' => 'ne',
'norwegian' => 'no',
'pashto' => 'ps',
'pidgin' => null,
'polish' => 'pl',
'portuguese' => 'pt',
'romanian' => 'ro',
'russian' => 'ru',
'serbian' => 'sr',
'slovak' => 'sk',
'slovene' => 'sl',
'somali' => 'so',
'spanish' => 'es',
'swahili' => 'sw',
'swedish' => 'sv',
'tagalog' => 'tl',
'turkish' => 'tr',
'ukrainian' => 'uk',
'urdu' => 'ur',
'uzbek' => 'uz',
'vietnamese' => 'vi',
'welsh' => 'cy',
);
/**
* Maps all language names from the language database to the
* ISO 639-2 3-letter language code.
*
* @var array
*/
public static $nameToCode3 = array(
'albanian' => 'sqi',
'arabic' => 'ara',
'azeri' => 'aze',
'bengali' => 'ben',
'bulgarian' => 'bul',
'cebuano' => 'ceb',
'croatian' => 'hrv',
'czech' => 'ces',
'danish' => 'dan',
'dutch' => 'nld',
'english' => 'eng',
'estonian' => 'est',
'farsi' => 'fas',
'finnish' => 'fin',
'french' => 'fra',
'german' => 'deu',
'hausa' => 'hau',
'hawaiian' => 'haw',
'hindi' => 'hin',
'hungarian' => 'hun',
'icelandic' => 'isl',
'indonesian' => 'ind',
'italian' => 'ita',
'kazakh' => 'kaz',
'kyrgyz' => 'kir',
'latin' => 'lat',
'latvian' => 'lav',
'lithuanian' => 'lit',
'macedonian' => 'mkd',
'mongolian' => 'mon',
'nepali' => 'nep',
'norwegian' => 'nor',
'pashto' => 'pus',
'pidgin' => 'crp',
'polish' => 'pol',
'portuguese' => 'por',
'romanian' => 'ron',
'russian' => 'rus',
'serbian' => 'srp',
'slovak' => 'slk',
'slovene' => 'slv',
'somali' => 'som',
'spanish' => 'spa',
'swahili' => 'swa',
'swedish' => 'swe',
'tagalog' => 'tgl',
'turkish' => 'tur',
'ukrainian' => 'ukr',
'urdu' => 'urd',
'uzbek' => 'uzb',
'vietnamese' => 'vie',
'welsh' => 'cym',
);
/**
* Maps ISO 639-1 2-letter language codes to the language names
* in the language database
*
* Not all languages have a 2 letter code, so some are missing
*
* @var array
*/
public static $code2ToName = array(
'ar' => 'arabic',
'az' => 'azeri',
'bg' => 'bulgarian',
'bn' => 'bengali',
'cs' => 'czech',
'cy' => 'welsh',
'da' => 'danish',
'de' => 'german',
'en' => 'english',
'es' => 'spanish',
'et' => 'estonian',
'fa' => 'farsi',
'fi' => 'finnish',
'fr' => 'french',
'ha' => 'hausa',
'hi' => 'hindi',
'hr' => 'croatian',
'hu' => 'hungarian',
'id' => 'indonesian',
'is' => 'icelandic',
'it' => 'italian',
'kk' => 'kazakh',
'ky' => 'kyrgyz',
'la' => 'latin',
'lt' => 'lithuanian',
'lv' => 'latvian',
'mk' => 'macedonian',
'mn' => 'mongolian',
'ne' => 'nepali',
'nl' => 'dutch',
'no' => 'norwegian',
'pl' => 'polish',
'ps' => 'pashto',
'pt' => 'portuguese',
'ro' => 'romanian',
'ru' => 'russian',
'sk' => 'slovak',
'sl' => 'slovene',
'so' => 'somali',
'sq' => 'albanian',
'sr' => 'serbian',
'sv' => 'swedish',
'sw' => 'swahili',
'tl' => 'tagalog',
'tr' => 'turkish',
'uk' => 'ukrainian',
'ur' => 'urdu',
'uz' => 'uzbek',
'vi' => 'vietnamese',
);
/**
* Maps ISO 639-2 3-letter language codes to the language names
* in the language database.
*
* @var array
*/
public static $code3ToName = array(
'ara' => 'arabic',
'aze' => 'azeri',
'ben' => 'bengali',
'bul' => 'bulgarian',
'ceb' => 'cebuano',
'ces' => 'czech',
'crp' => 'pidgin',
'cym' => 'welsh',
'dan' => 'danish',
'deu' => 'german',
'eng' => 'english',
'est' => 'estonian',
'fas' => 'farsi',
'fin' => 'finnish',
'fra' => 'french',
'hau' => 'hausa',
'haw' => 'hawaiian',
'hin' => 'hindi',
'hrv' => 'croatian',
'hun' => 'hungarian',
'ind' => 'indonesian',
'isl' => 'icelandic',
'ita' => 'italian',
'kaz' => 'kazakh',
'kir' => 'kyrgyz',
'lat' => 'latin',
'lav' => 'latvian',
'lit' => 'lithuanian',
'mkd' => 'macedonian',
'mon' => 'mongolian',
'nep' => 'nepali',
'nld' => 'dutch',
'nor' => 'norwegian',
'pol' => 'polish',
'por' => 'portuguese',
'pus' => 'pashto',
'rom' => 'romanian',
'rus' => 'russian',
'slk' => 'slovak',
'slv' => 'slovene',
'som' => 'somali',
'spa' => 'spanish',
'sqi' => 'albanian',
'srp' => 'serbian',
'swa' => 'swahili',
'swe' => 'swedish',
'tgl' => 'tagalog',
'tur' => 'turkish',
'ukr' => 'ukrainian',
'urd' => 'urdu',
'uzb' => 'uzbek',
'vie' => 'vietnamese',
);
/**
* Returns the 2-letter ISO 639-1 code for the given language name.
*
* @param string $lang English language name like "swedish"
*
* @return string Two-letter language code (e.g. "sv") or NULL if not found
*/
public static function nameToCode2($lang)
{
$lang = strtolower($lang);
if (!isset(self::$nameToCode2[$lang])) {
return null;
}
return self::$nameToCode2[$lang];
}
/**
* Returns the 3-letter ISO 639-2 code for the given language name.
*
* @param string $lang English language name like "swedish"
*
* @return string Three-letter language code (e.g. "swe") or NULL if not found
*/
public static function nameToCode3($lang)
{
$lang = strtolower($lang);
if (!isset(self::$nameToCode3[$lang])) {
return null;
}
return self::$nameToCode3[$lang];
}
/**
* Returns the language name for the given 2-letter ISO 639-1 code.
*
* @param string $code Two-letter language code (e.g. "sv")
*
* @return string English language name like "swedish"
*/
public static function code2ToName($code)
{
$lang = strtolower($code);
if (!isset(self::$code2ToName[$code])) {
return null;
}
return self::$code2ToName[$code];
}
/**
* Returns the language name for the given 3-letter ISO 639-2 code.
*
* @param string $code Three-letter language code (e.g. "swe")
*
* @return string English language name like "swedish"
*/
public static function code3ToName($code)
{
$lang = strtolower($code);
if (!isset(self::$code3ToName[$code])) {
return null;
}
return self::$code3ToName[$code];
}
}
?>

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

Loading…
Cancel
Save