Automated build for v0.01
This commit is contained in:
888
classes/api.php
Executable file
888
classes/api.php
Executable file
@ -0,0 +1,888 @@
|
||||
<?php
|
||||
class API extends Handler {
|
||||
|
||||
const API_LEVEL = 14;
|
||||
|
||||
const STATUS_OK = 0;
|
||||
const STATUS_ERR = 1;
|
||||
|
||||
private $seq;
|
||||
|
||||
static function param_to_bool($p) {
|
||||
return $p && ($p !== "f" && $p !== "false");
|
||||
}
|
||||
|
||||
function before($method) {
|
||||
if (parent::before($method)) {
|
||||
header("Content-Type: text/json");
|
||||
|
||||
if (!$_SESSION["uid"] && $method != "login" && $method != "isloggedin") {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => 'NOT_LOGGED_IN'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($_SESSION["uid"] && $method != "logout" && !get_pref('ENABLE_API_ACCESS')) {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => 'API_DISABLED'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->seq = (int) clean($_REQUEST['seq']);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function wrap($status, $reply) {
|
||||
print json_encode(array("seq" => $this->seq,
|
||||
"status" => $status,
|
||||
"content" => $reply));
|
||||
}
|
||||
|
||||
function getVersion() {
|
||||
$rv = array("version" => VERSION);
|
||||
$this->wrap(self::STATUS_OK, $rv);
|
||||
}
|
||||
|
||||
function getApiLevel() {
|
||||
$rv = array("level" => self::API_LEVEL);
|
||||
$this->wrap(self::STATUS_OK, $rv);
|
||||
}
|
||||
|
||||
function login() {
|
||||
@session_destroy();
|
||||
@session_start();
|
||||
|
||||
$login = clean($_REQUEST["user"]);
|
||||
$password = clean($_REQUEST["password"]);
|
||||
$password_base64 = base64_decode(clean($_REQUEST["password"]));
|
||||
|
||||
if (SINGLE_USER_MODE) $login = "admin";
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE login = ?");
|
||||
$sth->execute([$login]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$uid = $row["id"];
|
||||
} else {
|
||||
$uid = 0;
|
||||
}
|
||||
|
||||
if (!$uid) {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => "LOGIN_ERROR"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (get_pref("ENABLE_API_ACCESS", $uid)) {
|
||||
if (authenticate_user($login, $password)) { // try login with normal password
|
||||
$this->wrap(self::STATUS_OK, array("session_id" => session_id(),
|
||||
"api_level" => self::API_LEVEL));
|
||||
} else if (authenticate_user($login, $password_base64)) { // else try with base64_decoded password
|
||||
$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 {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => "API_DISABLED"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function logout() {
|
||||
logout_user();
|
||||
$this->wrap(self::STATUS_OK, array("status" => "OK"));
|
||||
}
|
||||
|
||||
function isLoggedIn() {
|
||||
$this->wrap(self::STATUS_OK, array("status" => $_SESSION["uid"] != ''));
|
||||
}
|
||||
|
||||
function getUnread() {
|
||||
$feed_id = clean($_REQUEST["feed_id"]);
|
||||
$is_cat = clean($_REQUEST["is_cat"]);
|
||||
|
||||
if ($feed_id) {
|
||||
$this->wrap(self::STATUS_OK, array("unread" => getFeedUnread($feed_id, $is_cat)));
|
||||
} else {
|
||||
$this->wrap(self::STATUS_OK, array("unread" => Feeds::getGlobalUnread()));
|
||||
}
|
||||
}
|
||||
|
||||
/* Method added for ttrss-reader for Android */
|
||||
function getCounters() {
|
||||
$this->wrap(self::STATUS_OK, Counters::getAllCounters());
|
||||
}
|
||||
|
||||
function getFeeds() {
|
||||
$cat_id = clean($_REQUEST["cat_id"]);
|
||||
$unread_only = API::param_to_bool(clean($_REQUEST["unread_only"]));
|
||||
$limit = (int) clean($_REQUEST["limit"]);
|
||||
$offset = (int) clean($_REQUEST["offset"]);
|
||||
$include_nested = API::param_to_bool(clean($_REQUEST["include_nested"]));
|
||||
|
||||
$feeds = $this->api_get_feeds($cat_id, $unread_only, $limit, $offset, $include_nested);
|
||||
|
||||
$this->wrap(self::STATUS_OK, $feeds);
|
||||
}
|
||||
|
||||
function getCategories() {
|
||||
$unread_only = API::param_to_bool(clean($_REQUEST["unread_only"]));
|
||||
$enable_nested = API::param_to_bool(clean($_REQUEST["enable_nested"]));
|
||||
$include_empty = API::param_to_bool(clean($_REQUEST['include_empty']));
|
||||
|
||||
// TODO do not return empty categories, return Uncategorized and standard virtual cats
|
||||
|
||||
if ($enable_nested)
|
||||
$nested_qpart = "parent_cat IS NULL";
|
||||
else
|
||||
$nested_qpart = "true";
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT
|
||||
id, title, order_id, (SELECT COUNT(id) FROM
|
||||
ttrss_feeds WHERE
|
||||
ttrss_feed_categories.id IS NOT NULL AND cat_id = ttrss_feed_categories.id) AS num_feeds,
|
||||
(SELECT COUNT(id) FROM
|
||||
ttrss_feed_categories AS c2 WHERE
|
||||
c2.parent_cat = ttrss_feed_categories.id) AS num_cats
|
||||
FROM ttrss_feed_categories
|
||||
WHERE $nested_qpart AND owner_uid = ?");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
|
||||
$cats = array();
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
if ($include_empty || $line["num_feeds"] > 0 || $line["num_cats"] > 0) {
|
||||
$unread = getFeedUnread($line["id"], true);
|
||||
|
||||
if ($enable_nested)
|
||||
$unread += Feeds::getCategoryChildrenUnread($line["id"]);
|
||||
|
||||
if ($unread || !$unread_only) {
|
||||
array_push($cats, array("id" => $line["id"],
|
||||
"title" => $line["title"],
|
||||
"unread" => $unread,
|
||||
"order_id" => (int) $line["order_id"],
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array(-2,-1,0) as $cat_id) {
|
||||
if ($include_empty || !$this->isCategoryEmpty($cat_id)) {
|
||||
$unread = getFeedUnread($cat_id, true);
|
||||
|
||||
if ($unread || !$unread_only) {
|
||||
array_push($cats, array("id" => $cat_id,
|
||||
"title" => Feeds::getCategoryTitle($cat_id),
|
||||
"unread" => $unread));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->wrap(self::STATUS_OK, $cats);
|
||||
}
|
||||
|
||||
function getHeadlines() {
|
||||
$feed_id = clean($_REQUEST["feed_id"]);
|
||||
if ($feed_id !== "") {
|
||||
|
||||
if (is_numeric($feed_id)) $feed_id = (int) $feed_id;
|
||||
|
||||
$limit = (int)clean($_REQUEST["limit"]);
|
||||
|
||||
if (!$limit || $limit >= 200) $limit = 200;
|
||||
|
||||
$offset = (int)clean($_REQUEST["skip"]);
|
||||
$filter = clean($_REQUEST["filter"]);
|
||||
$is_cat = API::param_to_bool(clean($_REQUEST["is_cat"]));
|
||||
$show_excerpt = API::param_to_bool(clean($_REQUEST["show_excerpt"]));
|
||||
$show_content = API::param_to_bool(clean($_REQUEST["show_content"]));
|
||||
/* all_articles, unread, adaptive, marked, updated */
|
||||
$view_mode = clean($_REQUEST["view_mode"]);
|
||||
$include_attachments = API::param_to_bool(clean($_REQUEST["include_attachments"]));
|
||||
$since_id = (int)clean($_REQUEST["since_id"]);
|
||||
$include_nested = API::param_to_bool(clean($_REQUEST["include_nested"]));
|
||||
$sanitize_content = !isset($_REQUEST["sanitize"]) ||
|
||||
API::param_to_bool($_REQUEST["sanitize"]);
|
||||
$force_update = API::param_to_bool(clean($_REQUEST["force_update"]));
|
||||
$has_sandbox = API::param_to_bool(clean($_REQUEST["has_sandbox"]));
|
||||
$excerpt_length = (int)clean($_REQUEST["excerpt_length"]);
|
||||
$check_first_id = (int)clean($_REQUEST["check_first_id"]);
|
||||
$include_header = API::param_to_bool(clean($_REQUEST["include_header"]));
|
||||
|
||||
$_SESSION['hasSandbox'] = $has_sandbox;
|
||||
|
||||
$skip_first_id_check = false;
|
||||
|
||||
$override_order = false;
|
||||
switch (clean($_REQUEST["order_by"])) {
|
||||
case "title":
|
||||
$override_order = "ttrss_entries.title, date_entered, updated";
|
||||
break;
|
||||
case "date_reverse":
|
||||
$override_order = "score DESC, date_entered, updated";
|
||||
$skip_first_id_check = true;
|
||||
break;
|
||||
case "feed_dates":
|
||||
$override_order = "updated DESC";
|
||||
break;
|
||||
}
|
||||
|
||||
/* do not rely on params below */
|
||||
|
||||
$search = clean($_REQUEST["search"]);
|
||||
|
||||
list($headlines, $headlines_header) = $this->api_get_headlines($feed_id, $limit, $offset,
|
||||
$filter, $is_cat, $show_excerpt, $show_content, $view_mode, $override_order,
|
||||
$include_attachments, $since_id, $search,
|
||||
$include_nested, $sanitize_content, $force_update, $excerpt_length, $check_first_id, $skip_first_id_check);
|
||||
|
||||
if ($include_header) {
|
||||
$this->wrap(self::STATUS_OK, array($headlines_header, $headlines));
|
||||
} else {
|
||||
$this->wrap(self::STATUS_OK, $headlines);
|
||||
}
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => 'INCORRECT_USAGE'));
|
||||
}
|
||||
}
|
||||
|
||||
function updateArticle() {
|
||||
$article_ids = explode(",", clean($_REQUEST["article_ids"]));
|
||||
$mode = (int) clean($_REQUEST["mode"]);
|
||||
$data = clean($_REQUEST["data"]);
|
||||
$field_raw = (int)clean($_REQUEST["field"]);
|
||||
|
||||
$field = "";
|
||||
$set_to = "";
|
||||
|
||||
switch ($field_raw) {
|
||||
case 0:
|
||||
$field = "marked";
|
||||
$additional_fields = ",last_marked = NOW()";
|
||||
break;
|
||||
case 1:
|
||||
$field = "published";
|
||||
$additional_fields = ",last_published = NOW()";
|
||||
break;
|
||||
case 2:
|
||||
$field = "unread";
|
||||
$additional_fields = ",last_read = NOW()";
|
||||
break;
|
||||
case 3:
|
||||
$field = "note";
|
||||
};
|
||||
|
||||
switch ($mode) {
|
||||
case 1:
|
||||
$set_to = "true";
|
||||
break;
|
||||
case 0:
|
||||
$set_to = "false";
|
||||
break;
|
||||
case 2:
|
||||
$set_to = "NOT $field";
|
||||
break;
|
||||
}
|
||||
|
||||
if ($field == "note") $set_to = $this->pdo->quote($data);
|
||||
|
||||
if ($field && $set_to && count($article_ids) > 0) {
|
||||
|
||||
$article_qmarks = arr_qmarks($article_ids);
|
||||
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
$field = $set_to $additional_fields
|
||||
WHERE ref_id IN ($article_qmarks) AND owner_uid = ?");
|
||||
$sth->execute(array_merge($article_ids, [$_SESSION['uid']]));
|
||||
|
||||
$num_updated = $sth->rowCount();
|
||||
|
||||
if ($num_updated > 0 && $field == "unread") {
|
||||
$sth = $this->pdo->prepare("SELECT DISTINCT feed_id FROM ttrss_user_entries
|
||||
WHERE ref_id IN ($article_qmarks)");
|
||||
$sth->execute($article_ids);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
CCache::update($line["feed_id"], $_SESSION["uid"]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->wrap(self::STATUS_OK, array("status" => "OK",
|
||||
"updated" => $num_updated));
|
||||
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => 'INCORRECT_USAGE'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getArticle() {
|
||||
|
||||
$article_ids = explode(",", clean($_REQUEST["article_id"]));
|
||||
$sanitize_content = !isset($_REQUEST["sanitize"]) ||
|
||||
API::param_to_bool($_REQUEST["sanitize"]);
|
||||
|
||||
if ($article_ids) {
|
||||
|
||||
$article_qmarks = arr_qmarks($article_ids);
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id,guid,title,link,content,feed_id,comments,int_id,
|
||||
marked,unread,published,score,note,lang,
|
||||
".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
|
||||
author,(SELECT title FROM ttrss_feeds WHERE id = feed_id) AS feed_title,
|
||||
(SELECT site_url FROM ttrss_feeds WHERE id = feed_id) AS site_url,
|
||||
(SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images
|
||||
FROM ttrss_entries,ttrss_user_entries
|
||||
WHERE id IN ($article_qmarks) AND ref_id = id AND owner_uid = ?");
|
||||
|
||||
$sth->execute(array_merge($article_ids, [$_SESSION['uid']]));
|
||||
|
||||
$articles = array();
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
|
||||
$attachments = Article::get_article_enclosures($line['id']);
|
||||
|
||||
$article = array(
|
||||
"id" => $line["id"],
|
||||
"guid" => $line["guid"],
|
||||
"title" => $line["title"],
|
||||
"link" => $line["link"],
|
||||
"labels" => Article::get_article_labels($line['id']),
|
||||
"unread" => API::param_to_bool($line["unread"]),
|
||||
"marked" => API::param_to_bool($line["marked"]),
|
||||
"published" => API::param_to_bool($line["published"]),
|
||||
"comments" => $line["comments"],
|
||||
"author" => $line["author"],
|
||||
"updated" => (int) strtotime($line["updated"]),
|
||||
"feed_id" => $line["feed_id"],
|
||||
"attachments" => $attachments,
|
||||
"score" => (int)$line["score"],
|
||||
"feed_title" => $line["feed_title"],
|
||||
"note" => $line["note"],
|
||||
"lang" => $line["lang"]
|
||||
);
|
||||
|
||||
if ($sanitize_content) {
|
||||
$article["content"] = sanitize(
|
||||
$line["content"],
|
||||
API::param_to_bool($line['hide_images']),
|
||||
false, $line["site_url"], false, $line["id"]);
|
||||
} else {
|
||||
$article["content"] = $line["content"];
|
||||
}
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_API) as $p) {
|
||||
$article = $p->hook_render_article_api(array("article" => $article));
|
||||
}
|
||||
|
||||
$article['content'] = rewrite_cached_urls($article['content']);
|
||||
|
||||
array_push($articles, $article);
|
||||
|
||||
}
|
||||
|
||||
$this->wrap(self::STATUS_OK, $articles);
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => 'INCORRECT_USAGE'));
|
||||
}
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
$config = array(
|
||||
"icons_dir" => ICONS_DIR,
|
||||
"icons_url" => ICONS_URL);
|
||||
|
||||
$config["daemon_is_running"] = file_is_locked("update_daemon.lock");
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT COUNT(*) AS cf FROM
|
||||
ttrss_feeds WHERE owner_uid = ?");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
$row = $sth->fetch();
|
||||
|
||||
$config["num_feeds"] = $row["cf"];
|
||||
|
||||
$this->wrap(self::STATUS_OK, $config);
|
||||
}
|
||||
|
||||
function updateFeed() {
|
||||
$feed_id = (int) clean($_REQUEST["feed_id"]);
|
||||
|
||||
if (!ini_get("open_basedir")) {
|
||||
RSSUtils::update_rss_feed($feed_id);
|
||||
}
|
||||
|
||||
$this->wrap(self::STATUS_OK, array("status" => "OK"));
|
||||
}
|
||||
|
||||
function catchupFeed() {
|
||||
$feed_id = clean($_REQUEST["feed_id"]);
|
||||
$is_cat = clean($_REQUEST["is_cat"]);
|
||||
|
||||
Feeds::catchup_feed($feed_id, $is_cat);
|
||||
|
||||
$this->wrap(self::STATUS_OK, array("status" => "OK"));
|
||||
}
|
||||
|
||||
function getPref() {
|
||||
$pref_name = clean($_REQUEST["pref_name"]);
|
||||
|
||||
$this->wrap(self::STATUS_OK, array("value" => get_pref($pref_name)));
|
||||
}
|
||||
|
||||
function getLabels() {
|
||||
$article_id = (int)clean($_REQUEST['article_id']);
|
||||
|
||||
$rv = array();
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id, caption, fg_color, bg_color
|
||||
FROM ttrss_labels2
|
||||
WHERE owner_uid = ? ORDER BY caption");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
|
||||
if ($article_id)
|
||||
$article_labels = Article::get_article_labels($article_id);
|
||||
else
|
||||
$article_labels = array();
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
|
||||
$checked = false;
|
||||
foreach ($article_labels as $al) {
|
||||
if (Labels::feed_to_label_id($al[0]) == $line['id']) {
|
||||
$checked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
array_push($rv, array(
|
||||
"id" => (int)Labels::label_to_feed_id($line['id']),
|
||||
"caption" => $line['caption'],
|
||||
"fg_color" => $line['fg_color'],
|
||||
"bg_color" => $line['bg_color'],
|
||||
"checked" => $checked));
|
||||
}
|
||||
|
||||
$this->wrap(self::STATUS_OK, $rv);
|
||||
}
|
||||
|
||||
function setArticleLabel() {
|
||||
|
||||
$article_ids = explode(",", clean($_REQUEST["article_ids"]));
|
||||
$label_id = (int) clean($_REQUEST['label_id']);
|
||||
$assign = API::param_to_bool(clean($_REQUEST['assign']));
|
||||
|
||||
$label = Labels::find_caption(Labels::feed_to_label_id($label_id), $_SESSION["uid"]);
|
||||
|
||||
$num_updated = 0;
|
||||
|
||||
if ($label) {
|
||||
|
||||
foreach ($article_ids as $id) {
|
||||
|
||||
if ($assign)
|
||||
Labels::add_article($id, $label, $_SESSION["uid"]);
|
||||
else
|
||||
Labels::remove_article($id, $label, $_SESSION["uid"]);
|
||||
|
||||
++$num_updated;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$this->wrap(self::STATUS_OK, array("status" => "OK",
|
||||
"updated" => $num_updated));
|
||||
|
||||
}
|
||||
|
||||
function index($method) {
|
||||
$plugin = PluginHost::getInstance()->get_api_method(strtolower($method));
|
||||
|
||||
if ($plugin && method_exists($plugin, $method)) {
|
||||
$reply = $plugin->$method();
|
||||
|
||||
$this->wrap($reply[0], $reply[1]);
|
||||
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => 'UNKNOWN_METHOD', "method" => $method));
|
||||
}
|
||||
}
|
||||
|
||||
function shareToPublished() {
|
||||
$title = strip_tags(clean($_REQUEST["title"]));
|
||||
$url = strip_tags(clean($_REQUEST["url"]));
|
||||
$content = strip_tags(clean($_REQUEST["content"]));
|
||||
|
||||
if (Article::create_published_article($title, $url, $content, "", $_SESSION["uid"])) {
|
||||
$this->wrap(self::STATUS_OK, array("status" => 'OK'));
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => 'Publishing failed'));
|
||||
}
|
||||
}
|
||||
|
||||
static function api_get_feeds($cat_id, $unread_only, $limit, $offset, $include_nested = false) {
|
||||
|
||||
$feeds = array();
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$limit = (int) $limit;
|
||||
$offset = (int) $offset;
|
||||
$cat_id = (int) $cat_id;
|
||||
|
||||
/* Labels */
|
||||
|
||||
if ($cat_id == -4 || $cat_id == -2) {
|
||||
$counters = Counters::getLabelCounters(true);
|
||||
|
||||
foreach (array_values($counters) as $cv) {
|
||||
|
||||
$unread = $cv["counter"];
|
||||
|
||||
if ($unread || !$unread_only) {
|
||||
|
||||
$row = array(
|
||||
"id" => (int) $cv["id"],
|
||||
"title" => $cv["description"],
|
||||
"unread" => $cv["counter"],
|
||||
"cat_id" => -2,
|
||||
);
|
||||
|
||||
array_push($feeds, $row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Virtual feeds */
|
||||
|
||||
if ($cat_id == -4 || $cat_id == -1) {
|
||||
foreach (array(-1, -2, -3, -4, -6, 0) as $i) {
|
||||
$unread = getFeedUnread($i);
|
||||
|
||||
if ($unread || !$unread_only) {
|
||||
$title = Feeds::getFeedTitle($i);
|
||||
|
||||
$row = array(
|
||||
"id" => $i,
|
||||
"title" => $title,
|
||||
"unread" => $unread,
|
||||
"cat_id" => -1,
|
||||
);
|
||||
array_push($feeds, $row);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/* Child cats */
|
||||
|
||||
if ($include_nested && $cat_id) {
|
||||
$sth = $pdo->prepare("SELECT
|
||||
id, title, order_id FROM ttrss_feed_categories
|
||||
WHERE parent_cat = ? AND owner_uid = ? ORDER BY id, title");
|
||||
|
||||
$sth->execute([$cat_id, $_SESSION['uid']]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
$unread = getFeedUnread($line["id"], true) +
|
||||
Feeds::getCategoryChildrenUnread($line["id"]);
|
||||
|
||||
if ($unread || !$unread_only) {
|
||||
$row = array(
|
||||
"id" => (int) $line["id"],
|
||||
"title" => $line["title"],
|
||||
"unread" => $unread,
|
||||
"is_cat" => true,
|
||||
"order_id" => (int) $line["order_id"]
|
||||
);
|
||||
array_push($feeds, $row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Real feeds */
|
||||
|
||||
if ($limit) {
|
||||
$limit_qpart = "LIMIT $limit OFFSET $offset";
|
||||
} else {
|
||||
$limit_qpart = "";
|
||||
}
|
||||
|
||||
if ($cat_id == -4 || $cat_id == -3) {
|
||||
$sth = $pdo->prepare("SELECT
|
||||
id, feed_url, cat_id, title, order_id, ".
|
||||
SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated
|
||||
FROM ttrss_feeds WHERE owner_uid = ?
|
||||
ORDER BY cat_id, title " . $limit_qpart);
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
|
||||
} else {
|
||||
|
||||
$sth = $pdo->prepare("SELECT
|
||||
id, feed_url, cat_id, title, order_id, ".
|
||||
SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated
|
||||
FROM ttrss_feeds WHERE
|
||||
(cat_id = :cat OR (:cat = 0 AND cat_id IS NULL))
|
||||
AND owner_uid = :uid
|
||||
ORDER BY cat_id, title " . $limit_qpart);
|
||||
$sth->execute([":uid" => $_SESSION['uid'], ":cat" => $cat_id]);
|
||||
}
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
|
||||
$unread = getFeedUnread($line["id"]);
|
||||
|
||||
$has_icon = Feeds::feedHasIcon($line['id']);
|
||||
|
||||
if ($unread || !$unread_only) {
|
||||
|
||||
$row = array(
|
||||
"feed_url" => $line["feed_url"],
|
||||
"title" => $line["title"],
|
||||
"id" => (int)$line["id"],
|
||||
"unread" => (int)$unread,
|
||||
"has_icon" => $has_icon,
|
||||
"cat_id" => (int)$line["cat_id"],
|
||||
"last_updated" => (int) strtotime($line["last_updated"]),
|
||||
"order_id" => (int) $line["order_id"],
|
||||
);
|
||||
|
||||
array_push($feeds, $row);
|
||||
}
|
||||
}
|
||||
|
||||
return $feeds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
static function api_get_headlines($feed_id, $limit, $offset,
|
||||
$filter, $is_cat, $show_excerpt, $show_content, $view_mode, $order,
|
||||
$include_attachments, $since_id,
|
||||
$search = "", $include_nested = false, $sanitize_content = true,
|
||||
$force_update = false, $excerpt_length = 100, $check_first_id = false, $skip_first_id_check = false) {
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
if ($force_update && $feed_id > 0 && is_numeric($feed_id)) {
|
||||
// Update the feed if required with some basic flood control
|
||||
|
||||
$sth = $pdo->prepare(
|
||||
"SELECT cache_images,".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated
|
||||
FROM ttrss_feeds WHERE id = ?");
|
||||
$sth->execute([$feed_id]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$last_updated = strtotime($row["last_updated"]);
|
||||
$cache_images = API::param_to_bool($row["cache_images"]);
|
||||
|
||||
if (!$cache_images && time() - $last_updated > 120) {
|
||||
RSSUtils::update_rss_feed($feed_id, true);
|
||||
} else {
|
||||
$sth = $pdo->prepare("UPDATE ttrss_feeds SET last_updated = '1970-01-01', last_update_started = '1970-01-01'
|
||||
WHERE id = ?");
|
||||
$sth->execute([$feed_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$params = array(
|
||||
"feed" => $feed_id,
|
||||
"limit" => $limit,
|
||||
"view_mode" => $view_mode,
|
||||
"cat_view" => $is_cat,
|
||||
"search" => $search,
|
||||
"override_order" => $order,
|
||||
"offset" => $offset,
|
||||
"since_id" => $since_id,
|
||||
"include_children" => $include_nested,
|
||||
"check_first_id" => $check_first_id,
|
||||
"skip_first_id_check" => $skip_first_id_check
|
||||
);
|
||||
|
||||
$qfh_ret = Feeds::queryFeedHeadlines($params);
|
||||
|
||||
$result = $qfh_ret[0];
|
||||
$feed_title = $qfh_ret[1];
|
||||
$first_id = $qfh_ret[6];
|
||||
|
||||
$headlines = array();
|
||||
|
||||
$headlines_header = array(
|
||||
'id' => $feed_id,
|
||||
'first_id' => $first_id,
|
||||
'is_cat' => $is_cat);
|
||||
|
||||
if (!is_numeric($result)) {
|
||||
while ($line = $result->fetch()) {
|
||||
$line["content_preview"] = truncate_string(strip_tags($line["content"]), $excerpt_length);
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
|
||||
$line = $p->hook_query_headlines($line, $excerpt_length, true);
|
||||
}
|
||||
|
||||
$is_updated = ($line["last_read"] == "" &&
|
||||
($line["unread"] != "t" && $line["unread"] != "1"));
|
||||
|
||||
$tags = explode(",", $line["tag_cache"]);
|
||||
|
||||
$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 = Article::get_article_labels($line["id"]);
|
||||
|
||||
$headline_row = array(
|
||||
"id" => (int)$line["id"],
|
||||
"guid" => $line["guid"],
|
||||
"unread" => API::param_to_bool($line["unread"]),
|
||||
"marked" => API::param_to_bool($line["marked"]),
|
||||
"published" => API::param_to_bool($line["published"]),
|
||||
"updated" => (int)strtotime($line["updated"]),
|
||||
"is_updated" => $is_updated,
|
||||
"title" => $line["title"],
|
||||
"link" => $line["link"],
|
||||
"feed_id" => $line["feed_id"] ? $line['feed_id'] : 0,
|
||||
"tags" => $tags,
|
||||
);
|
||||
|
||||
if ($include_attachments)
|
||||
$headline_row['attachments'] = Article::get_article_enclosures(
|
||||
$line['id']);
|
||||
|
||||
if ($show_excerpt)
|
||||
$headline_row["excerpt"] = $line["content_preview"];
|
||||
|
||||
if ($show_content) {
|
||||
|
||||
if ($sanitize_content) {
|
||||
$headline_row["content"] = sanitize(
|
||||
$line["content"],
|
||||
API::param_to_bool($line['hide_images']),
|
||||
false, $line["site_url"], false, $line["id"]);
|
||||
} else {
|
||||
$headline_row["content"] = $line["content"];
|
||||
}
|
||||
}
|
||||
|
||||
// unify label output to ease parsing
|
||||
if ($labels["no-labels"] == 1) $labels = array();
|
||||
|
||||
$headline_row["labels"] = $labels;
|
||||
|
||||
$headline_row["feed_title"] = $line["feed_title"] ? $line["feed_title"] :
|
||||
$feed_title;
|
||||
|
||||
$headline_row["comments_count"] = (int)$line["num_comments"];
|
||||
$headline_row["comments_link"] = $line["comments"];
|
||||
|
||||
$headline_row["always_display_attachments"] = API::param_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));
|
||||
}
|
||||
|
||||
$headline_row['content'] = rewrite_cached_urls($headline_row['content']);
|
||||
|
||||
array_push($headlines, $headline_row);
|
||||
}
|
||||
} else if (is_numeric($result) && $result == -1) {
|
||||
$headlines_header['first_id_changed'] = true;
|
||||
}
|
||||
|
||||
return array($headlines, $headlines_header);
|
||||
}
|
||||
|
||||
function unsubscribeFeed() {
|
||||
$feed_id = (int) clean($_REQUEST["feed_id"]);
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE
|
||||
id = ? AND owner_uid = ?");
|
||||
$sth->execute([$feed_id, $_SESSION['uid']]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
Pref_Feeds::remove_feed($feed_id, $_SESSION["uid"]);
|
||||
$this->wrap(self::STATUS_OK, array("status" => "OK"));
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => "FEED_NOT_FOUND"));
|
||||
}
|
||||
}
|
||||
|
||||
function subscribeToFeed() {
|
||||
$feed_url = clean($_REQUEST["feed_url"]);
|
||||
$category_id = (int) clean($_REQUEST["category_id"]);
|
||||
$login = clean($_REQUEST["login"]);
|
||||
$password = clean($_REQUEST["password"]);
|
||||
|
||||
if ($feed_url) {
|
||||
$rc = Feeds::subscribe_to_feed($feed_url, $category_id, $login, $password);
|
||||
|
||||
$this->wrap(self::STATUS_OK, array("status" => $rc));
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" => 'INCORRECT_USAGE'));
|
||||
}
|
||||
}
|
||||
|
||||
function getFeedTree() {
|
||||
$include_empty = API::param_to_bool(clean($_REQUEST['include_empty']));
|
||||
|
||||
$pf = new Pref_Feeds($_REQUEST);
|
||||
|
||||
$_REQUEST['mode'] = 2;
|
||||
$_REQUEST['force_show_empty'] = $include_empty;
|
||||
|
||||
if ($pf){
|
||||
$data = $pf->makefeedtree();
|
||||
$this->wrap(self::STATUS_OK, array("categories" => $data));
|
||||
} else {
|
||||
$this->wrap(self::STATUS_ERR, array("error" =>
|
||||
'UNABLE_TO_INSTANTIATE_OBJECT'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// only works for labels or uncategorized for the time being
|
||||
private function isCategoryEmpty($id) {
|
||||
|
||||
if ($id == -2) {
|
||||
$sth = $this->pdo->prepare("SELECT COUNT(id) AS count FROM ttrss_labels2
|
||||
WHERE owner_uid = ?");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
$row = $sth->fetch();
|
||||
|
||||
return $row["count"] == 0;
|
||||
|
||||
} else if ($id == 0) {
|
||||
$sth = $this->pdo->prepare("SELECT COUNT(id) AS count FROM ttrss_feeds
|
||||
WHERE cat_id IS NULL AND owner_uid = ?");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
$row = $sth->fetch();
|
||||
|
||||
return $row["count"] == 0;
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
788
classes/article.php
Executable file
788
classes/article.php
Executable file
@ -0,0 +1,788 @@
|
||||
<?php
|
||||
class Article extends Handler_Protected {
|
||||
|
||||
function csrf_ignore($method) {
|
||||
$csrf_ignored = array("redirect", "editarticletags");
|
||||
|
||||
return array_search($method, $csrf_ignored) !== false;
|
||||
}
|
||||
|
||||
function redirect() {
|
||||
$id = clean($_REQUEST['id']);
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT link FROM ttrss_entries, ttrss_user_entries
|
||||
WHERE id = ? AND id = ref_id AND owner_uid = ?
|
||||
LIMIT 1");
|
||||
$sth->execute([$id, $_SESSION['uid']]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$article_url = $row['link'];
|
||||
$article_url = str_replace("\n", "", $article_url);
|
||||
|
||||
header("Location: $article_url");
|
||||
return;
|
||||
|
||||
} else {
|
||||
print_error(__("Article not found."));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
function view() {
|
||||
$id = clean($_REQUEST["id"]);
|
||||
$cids = explode(",", clean($_REQUEST["cids"]));
|
||||
$mode = clean($_REQUEST["mode"]);
|
||||
|
||||
// in prefetch mode we only output requested cids, main article
|
||||
// just gets marked as read (it already exists in client cache)
|
||||
|
||||
$articles = array();
|
||||
|
||||
if ($mode == "") {
|
||||
array_push($articles, $this->format_article($id, false));
|
||||
} else if ($mode == "zoom") {
|
||||
array_push($articles, $this->format_article($id, true, true));
|
||||
} else if ($mode == "raw") {
|
||||
if (isset($_REQUEST['html'])) {
|
||||
header("Content-Type: text/html");
|
||||
print '<link rel="stylesheet" type="text/css" href="css/default.css"/>';
|
||||
}
|
||||
|
||||
$article = $this->format_article($id, false, isset($_REQUEST["zoom"]));
|
||||
print $article['content'];
|
||||
return;
|
||||
}
|
||||
|
||||
$this->catchupArticleById($id, 0);
|
||||
|
||||
if (!$_SESSION["bw_limit"]) {
|
||||
foreach ($cids as $cid) {
|
||||
if ($cid) {
|
||||
array_push($articles, $this->format_article($cid, false, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print json_encode($articles);
|
||||
} */
|
||||
|
||||
/*
|
||||
private function catchupArticleById($id, $cmode) {
|
||||
|
||||
if ($cmode == 0) {
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
unread = false,last_read = NOW()
|
||||
WHERE ref_id = ? AND owner_uid = ?");
|
||||
} else if ($cmode == 1) {
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
unread = true
|
||||
WHERE ref_id = ? AND owner_uid = ?");
|
||||
} else {
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
unread = NOT unread,last_read = NOW()
|
||||
WHERE ref_id = ? AND owner_uid = ?");
|
||||
}
|
||||
|
||||
$sth->execute([$id, $_SESSION['uid']]);
|
||||
|
||||
$feed_id = $this->getArticleFeed($id);
|
||||
CCache::update($feed_id, $_SESSION["uid"]);
|
||||
}
|
||||
*/
|
||||
|
||||
static function create_published_article($title, $url, $content, $labels_str,
|
||||
$owner_uid) {
|
||||
|
||||
$guid = 'SHA1:' . sha1("ttshared:" . $url . $owner_uid); // include owner_uid to prevent global GUID clash
|
||||
|
||||
if (!$content) {
|
||||
$pluginhost = new PluginHost();
|
||||
$pluginhost->load_all(PluginHost::KIND_ALL, $owner_uid);
|
||||
$pluginhost->load_data();
|
||||
|
||||
$af_readability = $pluginhost->get_plugin("Af_Readability");
|
||||
|
||||
if ($af_readability) {
|
||||
$enable_share_anything = $pluginhost->get($af_readability, "enable_share_anything");
|
||||
|
||||
if ($enable_share_anything) {
|
||||
$extracted_content = $af_readability->extract_content($url);
|
||||
|
||||
if ($extracted_content) $content = $extracted_content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$content_hash = sha1($content);
|
||||
|
||||
if ($labels_str != "") {
|
||||
$labels = explode(",", $labels_str);
|
||||
} else {
|
||||
$labels = array();
|
||||
}
|
||||
|
||||
$rc = false;
|
||||
|
||||
if (!$title) $title = $url;
|
||||
if (!$title && !$url) return false;
|
||||
|
||||
if (filter_var($url, FILTER_VALIDATE_URL) === FALSE) return false;
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// only check for our user data here, others might have shared this with different content etc
|
||||
$sth = $pdo->prepare("SELECT id FROM ttrss_entries, ttrss_user_entries WHERE
|
||||
guid = ? AND ref_id = id AND owner_uid = ? LIMIT 1");
|
||||
$sth->execute([$guid, $owner_uid]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$ref_id = $row['id'];
|
||||
|
||||
$sth = $pdo->prepare("SELECT int_id FROM ttrss_user_entries WHERE
|
||||
ref_id = ? AND owner_uid = ? LIMIT 1");
|
||||
$sth->execute([$ref_id, $owner_uid]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$int_id = $row['int_id'];
|
||||
|
||||
$sth = $pdo->prepare("UPDATE ttrss_entries SET
|
||||
content = ?, content_hash = ? WHERE id = ?");
|
||||
$sth->execute([$content, $content_hash, $ref_id]);
|
||||
|
||||
$sth = $pdo->prepare("UPDATE ttrss_user_entries SET published = true,
|
||||
last_published = NOW() WHERE
|
||||
int_id = ? AND owner_uid = ?");
|
||||
$sth->execute([$int_id, $owner_uid]);
|
||||
|
||||
} else {
|
||||
|
||||
$sth = $pdo->prepare("INSERT INTO ttrss_user_entries
|
||||
(ref_id, uuid, feed_id, orig_feed_id, owner_uid, published, tag_cache, label_cache,
|
||||
last_read, note, unread, last_published)
|
||||
VALUES
|
||||
(?, '', NULL, NULL, ?, true, '', '', NOW(), '', false, NOW())");
|
||||
$sth->execute([$ref_id, $owner_uid]);
|
||||
}
|
||||
|
||||
if (count($labels) != 0) {
|
||||
foreach ($labels as $label) {
|
||||
Labels::add_article($ref_id, trim($label), $owner_uid);
|
||||
}
|
||||
}
|
||||
|
||||
$rc = true;
|
||||
|
||||
} else {
|
||||
$sth = $pdo->prepare("INSERT INTO ttrss_entries
|
||||
(title, guid, link, updated, content, content_hash, date_entered, date_updated)
|
||||
VALUES
|
||||
(?, ?, ?, NOW(), ?, ?, NOW(), NOW())");
|
||||
$sth->execute([$title, $guid, $url, $content, $content_hash]);
|
||||
|
||||
$sth = $pdo->prepare("SELECT id FROM ttrss_entries WHERE guid = ?");
|
||||
$sth->execute([$guid]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$ref_id = $row["id"];
|
||||
|
||||
$sth = $pdo->prepare("INSERT INTO ttrss_user_entries
|
||||
(ref_id, uuid, feed_id, orig_feed_id, owner_uid, published, tag_cache, label_cache,
|
||||
last_read, note, unread, last_published)
|
||||
VALUES
|
||||
(?, '', NULL, NULL, ?, true, '', '', NOW(), '', false, NOW())");
|
||||
$sth->execute([$ref_id, $owner_uid]);
|
||||
|
||||
if (count($labels) != 0) {
|
||||
foreach ($labels as $label) {
|
||||
Labels::add_article($ref_id, trim($label), $owner_uid);
|
||||
}
|
||||
}
|
||||
|
||||
$rc = true;
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
return $rc;
|
||||
}
|
||||
|
||||
function editArticleTags() {
|
||||
|
||||
$param = clean($_REQUEST['param']);
|
||||
|
||||
$tags = Article::get_article_tags($param);
|
||||
|
||||
$tags_str = join(", ", $tags);
|
||||
|
||||
print_hidden("id", "$param");
|
||||
print_hidden("op", "article");
|
||||
print_hidden("method", "setArticleTags");
|
||||
|
||||
print "<header class='horizontal'>" . __("Tags for this article (separated by commas):")."</header>";
|
||||
|
||||
print "<section>";
|
||||
print "<textarea dojoType='dijit.form.SimpleTextarea' rows='4'
|
||||
style='height : 100px; font-size : 12px; width : 98%' id='tags_str'
|
||||
name='tags_str'>$tags_str</textarea>
|
||||
<div class='autocomplete' id='tags_choices'
|
||||
style='display:none'></div>";
|
||||
print "</section>";
|
||||
|
||||
print "<footer>";
|
||||
print "<button dojoType='dijit.form.Button'
|
||||
type='submit' class='alt-primary' onclick=\"dijit.byId('editTagsDlg').execute()\">".__('Save')."</button> ";
|
||||
print "<button dojoType='dijit.form.Button'
|
||||
onclick=\"dijit.byId('editTagsDlg').hide()\">".__('Cancel')."</button>";
|
||||
print "</footer>";
|
||||
|
||||
}
|
||||
|
||||
function setScore() {
|
||||
$ids = explode(",", clean($_REQUEST['id']));
|
||||
$score = (int)clean($_REQUEST['score']);
|
||||
|
||||
$ids_qmarks = arr_qmarks($ids);
|
||||
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
score = ? WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
|
||||
|
||||
$sth->execute(array_merge([$score], $ids, [$_SESSION['uid']]));
|
||||
|
||||
print json_encode(["id" => $ids, "score" => (int)$score]);
|
||||
}
|
||||
|
||||
function getScore() {
|
||||
$id = clean($_REQUEST['id']);
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT score FROM ttrss_user_entries WHERE ref_id = ? AND owner_uid = ?");
|
||||
$sth->execute([$id, $_SESSION['uid']]);
|
||||
$row = $sth->fetch();
|
||||
|
||||
$score = $row['score'];
|
||||
|
||||
print json_encode(["id" => $id, "score" => (int)$score]);
|
||||
}
|
||||
|
||||
|
||||
function setArticleTags() {
|
||||
|
||||
$id = clean($_REQUEST["id"]);
|
||||
|
||||
$tags_str = clean($_REQUEST["tags_str"]);
|
||||
$tags = array_unique(trim_array(explode(",", $tags_str)));
|
||||
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT int_id FROM ttrss_user_entries WHERE
|
||||
ref_id = ? AND owner_uid = ? LIMIT 1");
|
||||
$sth->execute([$id, $_SESSION['uid']]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
|
||||
$tags_to_cache = array();
|
||||
|
||||
$int_id = $row['int_id'];
|
||||
|
||||
$sth = $this->pdo->prepare("DELETE FROM ttrss_tags WHERE
|
||||
post_int_id = ? AND owner_uid = ?");
|
||||
$sth->execute([$int_id, $_SESSION['uid']]);
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
$tag = sanitize_tag($tag);
|
||||
|
||||
if (!tag_is_valid($tag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match("/^[0-9]*$/", $tag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// print "<!-- $id : $int_id : $tag -->";
|
||||
|
||||
if ($tag != '') {
|
||||
$sth = $this->pdo->prepare("INSERT INTO ttrss_tags
|
||||
(post_int_id, owner_uid, tag_name)
|
||||
VALUES (?, ?, ?)");
|
||||
|
||||
$sth->execute([$int_id, $_SESSION['uid'], $tag]);
|
||||
}
|
||||
|
||||
array_push($tags_to_cache, $tag);
|
||||
}
|
||||
|
||||
/* update tag cache */
|
||||
|
||||
sort($tags_to_cache);
|
||||
$tags_str = join(",", $tags_to_cache);
|
||||
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries
|
||||
SET tag_cache = ? WHERE ref_id = ? AND owner_uid = ?");
|
||||
$sth->execute([$tags_str, $id, $_SESSION['uid']]);
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
|
||||
$tags = Article::get_article_tags($id);
|
||||
$tags_str = $this->format_tags_string($tags, $id);
|
||||
$tags_str_full = join(", ", $tags);
|
||||
|
||||
if (!$tags_str_full) $tags_str_full = __("no tags");
|
||||
|
||||
print json_encode(array("id" => (int)$id,
|
||||
"content" => $tags_str, "content_full" => $tags_str_full));
|
||||
}
|
||||
|
||||
|
||||
function completeTags() {
|
||||
$search = clean($_REQUEST["search"]);
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT DISTINCT tag_name FROM ttrss_tags
|
||||
WHERE owner_uid = ? AND
|
||||
tag_name LIKE ? ORDER BY tag_name
|
||||
LIMIT 10");
|
||||
|
||||
$sth->execute([$_SESSION['uid'], "$search%"]);
|
||||
|
||||
print "<ul>";
|
||||
while ($line = $sth->fetch()) {
|
||||
print "<li>" . $line["tag_name"] . "</li>";
|
||||
}
|
||||
print "</ul>";
|
||||
}
|
||||
|
||||
function assigntolabel() {
|
||||
return $this->labelops(true);
|
||||
}
|
||||
|
||||
function removefromlabel() {
|
||||
return $this->labelops(false);
|
||||
}
|
||||
|
||||
private function labelops($assign) {
|
||||
$reply = array();
|
||||
|
||||
$ids = explode(",", clean($_REQUEST["ids"]));
|
||||
$label_id = clean($_REQUEST["lid"]);
|
||||
|
||||
$label = Labels::find_caption($label_id, $_SESSION["uid"]);
|
||||
|
||||
$reply["info-for-headlines"] = array();
|
||||
|
||||
if ($label) {
|
||||
|
||||
foreach ($ids as $id) {
|
||||
|
||||
if ($assign)
|
||||
Labels::add_article($id, $label, $_SESSION["uid"]);
|
||||
else
|
||||
Labels::remove_article($id, $label, $_SESSION["uid"]);
|
||||
|
||||
$labels = $this->get_article_labels($id, $_SESSION["uid"]);
|
||||
|
||||
array_push($reply["info-for-headlines"],
|
||||
array("id" => $id, "labels" => $this->format_article_labels($labels)));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$reply["message"] = "UPDATE_COUNTERS";
|
||||
|
||||
print json_encode($reply);
|
||||
}
|
||||
|
||||
function getArticleFeed($id) {
|
||||
$sth = $this->pdo->prepare("SELECT feed_id FROM ttrss_user_entries
|
||||
WHERE ref_id = ? AND owner_uid = ?");
|
||||
$sth->execute([$id, $_SESSION['uid']]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
return $row["feed_id"];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static function format_article_enclosures($id, $always_display_enclosures,
|
||||
$article_content, $hide_images = false) {
|
||||
|
||||
$result = Article::get_article_enclosures($id);
|
||||
$rv = '';
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ENCLOSURES) as $plugin) {
|
||||
$retval = $plugin->hook_format_enclosures($rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
|
||||
if (is_array($retval)) {
|
||||
$rv = $retval[0];
|
||||
$result = $retval[1];
|
||||
} else {
|
||||
$rv = $retval;
|
||||
}
|
||||
}
|
||||
unset($retval); // Unset to prevent breaking render if there are no HOOK_RENDER_ENCLOSURE hooks below.
|
||||
|
||||
if ($rv === '' && !empty($result)) {
|
||||
$entries_html = array();
|
||||
$entries = array();
|
||||
$entries_inline = array();
|
||||
|
||||
foreach ($result as $line) {
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ENCLOSURE_ENTRY) as $plugin) {
|
||||
$line = $plugin->hook_enclosure_entry($line);
|
||||
}
|
||||
|
||||
$url = $line["content_url"];
|
||||
$ctype = $line["content_type"];
|
||||
$title = $line["title"];
|
||||
$width = $line["width"];
|
||||
$height = $line["height"];
|
||||
|
||||
if (!$ctype) $ctype = __("unknown type");
|
||||
|
||||
//$filename = substr($url, strrpos($url, "/")+1);
|
||||
$filename = basename($url);
|
||||
|
||||
$player = format_inline_player($url, $ctype);
|
||||
|
||||
if ($player) array_push($entries_inline, $player);
|
||||
|
||||
# $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\" rel=\"noopener noreferrer\">" .
|
||||
# $filename . " (" . $ctype . ")" . "</a>";
|
||||
|
||||
$entry = "<div onclick=\"popupOpenUrl('".htmlspecialchars($url)."')\"
|
||||
dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
|
||||
|
||||
array_push($entries_html, $entry);
|
||||
|
||||
$entry = array();
|
||||
|
||||
$entry["type"] = $ctype;
|
||||
$entry["filename"] = $filename;
|
||||
$entry["url"] = $url;
|
||||
$entry["title"] = $title;
|
||||
$entry["width"] = $width;
|
||||
$entry["height"] = $height;
|
||||
|
||||
array_push($entries, $entry);
|
||||
}
|
||||
|
||||
if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
|
||||
if ($always_display_enclosures ||
|
||||
!preg_match("/<img/i", $article_content)) {
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ENCLOSURE) as $plugin)
|
||||
$retval = $plugin->hook_render_enclosure($entry, $hide_images);
|
||||
|
||||
|
||||
if ($retval) {
|
||||
$rv .= $retval;
|
||||
} else {
|
||||
|
||||
if (preg_match("/image/", $entry["type"])) {
|
||||
|
||||
if (!$hide_images) {
|
||||
$encsize = '';
|
||||
if ($entry['height'] > 0)
|
||||
$encsize .= ' height="' . intval($entry['height']) . '"';
|
||||
if ($entry['width'] > 0)
|
||||
$encsize .= ' width="' . intval($entry['width']) . '"';
|
||||
$rv .= "<p><img
|
||||
alt=\"".htmlspecialchars($entry["filename"])."\"
|
||||
src=\"" .htmlspecialchars($entry["url"]) . "\"
|
||||
" . $encsize . " /></p>";
|
||||
} else {
|
||||
$rv .= "<p><a target=\"_blank\" rel=\"noopener noreferrer\"
|
||||
href=\"".htmlspecialchars($entry["url"])."\"
|
||||
>" .htmlspecialchars($entry["url"]) . "</a></p>";
|
||||
}
|
||||
|
||||
if ($entry['title']) {
|
||||
$rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($entries_inline) > 0) {
|
||||
//$rv .= "<hr clear='both'/>";
|
||||
foreach ($entries_inline as $entry) { $rv .= $entry; };
|
||||
$rv .= "<br clear='both'/>";
|
||||
}
|
||||
|
||||
$rv .= "<div class=\"attachments\" dojoType=\"dijit.form.DropDownButton\">".
|
||||
"<span>" . __('Attachments')."</span>";
|
||||
|
||||
$rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if ($entry["title"])
|
||||
$title = " — " . truncate_string($entry["title"], 30);
|
||||
else
|
||||
$title = "";
|
||||
|
||||
if ($entry["filename"])
|
||||
$filename = truncate_middle(htmlspecialchars($entry["filename"]), 60);
|
||||
else
|
||||
$filename = "";
|
||||
|
||||
$rv .= "<div onclick='popupOpenUrl(\"".htmlspecialchars($entry["url"])."\")'
|
||||
dojoType=\"dijit.MenuItem\">".$filename . $title."</div>";
|
||||
|
||||
};
|
||||
|
||||
$rv .= "</div>";
|
||||
$rv .= "</div>";
|
||||
}
|
||||
|
||||
return $rv;
|
||||
}
|
||||
|
||||
static function get_article_tags($id, $owner_uid = 0, $tag_cache = false) {
|
||||
|
||||
$a_id = $id;
|
||||
|
||||
if (!$owner_uid) $owner_uid = $_SESSION["uid"];
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT DISTINCT tag_name,
|
||||
owner_uid as owner FROM ttrss_tags
|
||||
WHERE post_int_id = (SELECT int_id FROM ttrss_user_entries WHERE
|
||||
ref_id = ? AND owner_uid = ? LIMIT 1) ORDER BY tag_name");
|
||||
|
||||
$tags = array();
|
||||
|
||||
/* check cache first */
|
||||
|
||||
if ($tag_cache === false) {
|
||||
$csth = $pdo->prepare("SELECT tag_cache FROM ttrss_user_entries
|
||||
WHERE ref_id = ? AND owner_uid = ?");
|
||||
$csth->execute([$id, $owner_uid]);
|
||||
|
||||
if ($row = $csth->fetch()) $tag_cache = $row["tag_cache"];
|
||||
}
|
||||
|
||||
if ($tag_cache) {
|
||||
$tags = explode(",", $tag_cache);
|
||||
} else {
|
||||
|
||||
/* do it the hard way */
|
||||
|
||||
$sth->execute([$a_id, $owner_uid]);
|
||||
|
||||
while ($tmp_line = $sth->fetch()) {
|
||||
array_push($tags, $tmp_line["tag_name"]);
|
||||
}
|
||||
|
||||
/* update the cache */
|
||||
|
||||
$tags_str = join(",", $tags);
|
||||
|
||||
$sth = $pdo->prepare("UPDATE ttrss_user_entries
|
||||
SET tag_cache = ? WHERE ref_id = ?
|
||||
AND owner_uid = ?");
|
||||
$sth->execute([$tags_str, $id, $owner_uid]);
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
static function format_tags_string($tags) {
|
||||
if (!is_array($tags) || count($tags) == 0) {
|
||||
return __("no tags");
|
||||
} else {
|
||||
$maxtags = min(5, count($tags));
|
||||
$tags_str = "";
|
||||
|
||||
for ($i = 0; $i < $maxtags; $i++) {
|
||||
$tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"Feeds.open({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
|
||||
}
|
||||
|
||||
$tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
|
||||
|
||||
if (count($tags) > $maxtags)
|
||||
$tags_str .= ", …";
|
||||
|
||||
return $tags_str;
|
||||
}
|
||||
}
|
||||
|
||||
static function format_article_labels($labels) {
|
||||
|
||||
if (!is_array($labels)) return '';
|
||||
|
||||
$labels_str = "";
|
||||
|
||||
foreach ($labels as $l) {
|
||||
$labels_str .= sprintf("<div class='label'
|
||||
style='color : %s; background-color : %s'>%s</div>",
|
||||
$l[2], $l[3], $l[1]);
|
||||
}
|
||||
|
||||
return $labels_str;
|
||||
|
||||
}
|
||||
|
||||
static function format_article_note($id, $note, $allow_edit = true) {
|
||||
|
||||
if ($allow_edit) {
|
||||
$onclick = "onclick='Plugins.Note.edit($id)'";
|
||||
$note_class = 'editable';
|
||||
} else {
|
||||
$onclick = '';
|
||||
$note_class = '';
|
||||
}
|
||||
|
||||
return "<div class='article-note $note_class'>
|
||||
<i class='material-icons'>note</i>
|
||||
<div $onclick class='body'>$note</div>
|
||||
</div>";
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
static function get_article_enclosures($id) {
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT * FROM ttrss_enclosures
|
||||
WHERE post_id = ? AND content_url != ''");
|
||||
$sth->execute([$id]);
|
||||
|
||||
$rv = array();
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
|
||||
if (file_exists(CACHE_DIR . '/images/' . sha1($line["content_url"]))) {
|
||||
$line["content_url"] = get_self_url_prefix() . '/public.php?op=cached_url&hash=' . sha1($line["content_url"]);
|
||||
}
|
||||
|
||||
array_push($rv, $line);
|
||||
}
|
||||
|
||||
return $rv;
|
||||
}
|
||||
|
||||
static function purge_orphans() {
|
||||
|
||||
// purge orphaned posts in main content table
|
||||
|
||||
if (DB_TYPE == "mysql")
|
||||
$limit_qpart = "LIMIT 5000";
|
||||
else
|
||||
$limit_qpart = "";
|
||||
|
||||
$pdo = Db::pdo();
|
||||
$res = $pdo->query("DELETE FROM ttrss_entries WHERE
|
||||
NOT EXISTS (SELECT ref_id FROM ttrss_user_entries WHERE ref_id = id) $limit_qpart");
|
||||
|
||||
if (Debug::enabled()) {
|
||||
$rows = $res->rowCount();
|
||||
Debug::log("Purged $rows orphaned posts.");
|
||||
}
|
||||
}
|
||||
|
||||
static function catchupArticlesById($ids, $cmode, $owner_uid = false) {
|
||||
|
||||
if (!$owner_uid) $owner_uid = $_SESSION["uid"];
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$ids_qmarks = arr_qmarks($ids);
|
||||
|
||||
if ($cmode == 1) {
|
||||
$sth = $pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
unread = true
|
||||
WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
|
||||
} else if ($cmode == 2) {
|
||||
$sth = $pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
unread = NOT unread,last_read = NOW()
|
||||
WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
|
||||
} else {
|
||||
$sth = $pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
unread = false,last_read = NOW()
|
||||
WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
|
||||
}
|
||||
|
||||
$sth->execute(array_merge($ids, [$owner_uid]));
|
||||
|
||||
/* update ccache */
|
||||
|
||||
$sth = $pdo->prepare("SELECT DISTINCT feed_id FROM ttrss_user_entries
|
||||
WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
|
||||
$sth->execute(array_merge($ids, [$owner_uid]));
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
CCache::update($line["feed_id"], $owner_uid);
|
||||
}
|
||||
}
|
||||
|
||||
static function getLastArticleId() {
|
||||
$pdo = DB::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT ref_id AS id FROM ttrss_user_entries
|
||||
WHERE owner_uid = ? ORDER BY ref_id DESC LIMIT 1");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
return $row['id'];
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static function get_article_labels($id, $owner_uid = false) {
|
||||
$rv = array();
|
||||
|
||||
if (!$owner_uid) $owner_uid = $_SESSION["uid"];
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT label_cache FROM
|
||||
ttrss_user_entries WHERE ref_id = ? AND owner_uid = ?");
|
||||
$sth->execute([$id, $owner_uid]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$label_cache = $row["label_cache"];
|
||||
|
||||
if ($label_cache) {
|
||||
$tmp = json_decode($label_cache, true);
|
||||
|
||||
if (!$tmp || $tmp["no-labels"] == 1)
|
||||
return $rv;
|
||||
else
|
||||
return $tmp;
|
||||
}
|
||||
}
|
||||
|
||||
$sth = $pdo->prepare("SELECT DISTINCT label_id,caption,fg_color,bg_color
|
||||
FROM ttrss_labels2, ttrss_user_labels2
|
||||
WHERE id = label_id
|
||||
AND article_id = ?
|
||||
AND owner_uid = ?
|
||||
ORDER BY caption");
|
||||
$sth->execute([$id, $owner_uid]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
$rk = array(Labels::label_to_feed_id($line["label_id"]),
|
||||
$line["caption"], $line["fg_color"],
|
||||
$line["bg_color"]);
|
||||
array_push($rv, $rk);
|
||||
}
|
||||
|
||||
if (count($rv) > 0)
|
||||
Labels::update_cache($owner_uid, $id, $rv);
|
||||
else
|
||||
Labels::update_cache($owner_uid, $id, array("no-labels" => 1));
|
||||
|
||||
return $rv;
|
||||
}
|
||||
|
||||
}
|
62
classes/auth/base.php
Normal file
62
classes/auth/base.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
class Auth_Base {
|
||||
private $pdo;
|
||||
|
||||
function __construct() {
|
||||
$this->pdo = Db::pdo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(unused)
|
||||
*/
|
||||
function check_password($owner_uid, $password) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(unused)
|
||||
*/
|
||||
function authenticate($login, $password) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Auto-creates specified user if allowed by system configuration
|
||||
// Can be used instead of find_user_by_login() by external auth modules
|
||||
function auto_create_user($login, $password = false) {
|
||||
if ($login && defined('AUTH_AUTO_CREATE') && AUTH_AUTO_CREATE) {
|
||||
$user_id = $this->find_user_by_login($login);
|
||||
|
||||
if (!$password) $password = make_password();
|
||||
|
||||
if (!$user_id) {
|
||||
$salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
|
||||
$pwd_hash = encrypt_password($password, $salt, true);
|
||||
|
||||
$sth = $this->pdo->prepare("INSERT INTO ttrss_users
|
||||
(login,access_level,last_login,created,pwd_hash,salt)
|
||||
VALUES (?, 0, null, NOW(), ?,?)");
|
||||
$sth->execute([$login, $pwd_hash, $salt]);
|
||||
|
||||
return $this->find_user_by_login($login);
|
||||
|
||||
} else {
|
||||
return $user_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->find_user_by_login($login);
|
||||
}
|
||||
|
||||
function find_user_by_login($login) {
|
||||
$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE
|
||||
login = ?");
|
||||
$sth->execute([$login]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
return $row["id"];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
163
classes/backend.php
Normal file
163
classes/backend.php
Normal file
@ -0,0 +1,163 @@
|
||||
<?php
|
||||
class Backend extends Handler {
|
||||
function loading() {
|
||||
header("Content-type: text/html");
|
||||
print __("Loading, please wait...") . " " .
|
||||
"<img src='images/indicator_tiny.gif'>";
|
||||
}
|
||||
|
||||
function digestTest() {
|
||||
if (isset($_SESSION['uid'])) {
|
||||
header("Content-type: text/html");
|
||||
|
||||
$rv = Digest::prepare_headlines_digest($_SESSION['uid'], 1, 1000);
|
||||
|
||||
print "<h1>HTML</h1>";
|
||||
print $rv[0];
|
||||
print "<h1>Plain text</h1>";
|
||||
print "<pre>".$rv[3]."</pre>";
|
||||
} else {
|
||||
print error_json(6);
|
||||
}
|
||||
}
|
||||
|
||||
private function display_main_help() {
|
||||
$info = get_hotkeys_info();
|
||||
$imap = get_hotkeys_map();
|
||||
$omap = array();
|
||||
|
||||
foreach ($imap[1] as $sequence => $action) {
|
||||
if (!isset($omap[$action])) $omap[$action] = array();
|
||||
|
||||
array_push($omap[$action], $sequence);
|
||||
}
|
||||
|
||||
print "<ul class='panel panel-scrollable hotkeys-help' style='height : 300px'>";
|
||||
|
||||
print "<h2>" . __("Keyboard Shortcuts") . "</h2>";
|
||||
|
||||
foreach ($info as $section => $hotkeys) {
|
||||
|
||||
print "<li><hr></li>";
|
||||
print "<li><h3>" . $section . "</h3></li>";
|
||||
|
||||
foreach ($hotkeys as $action => $description) {
|
||||
|
||||
if (is_array($omap[$action])) {
|
||||
foreach ($omap[$action] as $sequence) {
|
||||
if (strpos($sequence, "|") !== FALSE) {
|
||||
$sequence = substr($sequence,
|
||||
strpos($sequence, "|")+1,
|
||||
strlen($sequence));
|
||||
} else {
|
||||
$keys = explode(" ", $sequence);
|
||||
|
||||
for ($i = 0; $i < count($keys); $i++) {
|
||||
if (strlen($keys[$i]) > 1) {
|
||||
$tmp = '';
|
||||
foreach (str_split($keys[$i]) as $c) {
|
||||
switch ($c) {
|
||||
case '*':
|
||||
$tmp .= __('Shift') . '+';
|
||||
break;
|
||||
case '^':
|
||||
$tmp .= __('Ctrl') . '+';
|
||||
break;
|
||||
default:
|
||||
$tmp .= $c;
|
||||
}
|
||||
}
|
||||
$keys[$i] = $tmp;
|
||||
}
|
||||
}
|
||||
$sequence = join(" ", $keys);
|
||||
}
|
||||
|
||||
print "<li>";
|
||||
print "<div class='hk'><code>$sequence</code></div>";
|
||||
print "<div class='desc'>$description</div>";
|
||||
print "</li>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print "</ul>";
|
||||
|
||||
|
||||
}
|
||||
|
||||
function help() {
|
||||
$topic = basename(clean($_REQUEST["topic"])); // only one for now
|
||||
|
||||
if ($topic == "main") {
|
||||
$info = get_hotkeys_info();
|
||||
$imap = get_hotkeys_map();
|
||||
$omap = array();
|
||||
|
||||
foreach ($imap[1] as $sequence => $action) {
|
||||
if (!isset($omap[$action])) $omap[$action] = array();
|
||||
|
||||
array_push($omap[$action], $sequence);
|
||||
}
|
||||
|
||||
print "<ul class='panel panel-scrollable hotkeys-help' style='height : 300px'>";
|
||||
|
||||
$cur_section = "";
|
||||
foreach ($info as $section => $hotkeys) {
|
||||
|
||||
if ($cur_section) print "<li> </li>";
|
||||
print "<li><h3>" . $section . "</h3></li>";
|
||||
$cur_section = $section;
|
||||
|
||||
foreach ($hotkeys as $action => $description) {
|
||||
|
||||
if (is_array($omap[$action])) {
|
||||
foreach ($omap[$action] as $sequence) {
|
||||
if (strpos($sequence, "|") !== FALSE) {
|
||||
$sequence = substr($sequence,
|
||||
strpos($sequence, "|")+1,
|
||||
strlen($sequence));
|
||||
} else {
|
||||
$keys = explode(" ", $sequence);
|
||||
|
||||
for ($i = 0; $i < count($keys); $i++) {
|
||||
if (strlen($keys[$i]) > 1) {
|
||||
$tmp = '';
|
||||
foreach (str_split($keys[$i]) as $c) {
|
||||
switch ($c) {
|
||||
case '*':
|
||||
$tmp .= __('Shift') . '+';
|
||||
break;
|
||||
case '^':
|
||||
$tmp .= __('Ctrl') . '+';
|
||||
break;
|
||||
default:
|
||||
$tmp .= $c;
|
||||
}
|
||||
}
|
||||
$keys[$i] = $tmp;
|
||||
}
|
||||
}
|
||||
$sequence = join(" ", $keys);
|
||||
}
|
||||
|
||||
print "<li>";
|
||||
print "<div class='hk'><code>$sequence</code></div>";
|
||||
print "<div class='desc'>$description</div>";
|
||||
print "</li>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print "</ul>";
|
||||
}
|
||||
|
||||
print "<footer class='text-center'>";
|
||||
print "<button dojoType='dijit.form.Button'
|
||||
onclick=\"return dijit.byId('helpDlg').hide()\">".__('Close this window')."</button>";
|
||||
print "</footer>";
|
||||
|
||||
}
|
||||
}
|
211
classes/ccache.php
Normal file
211
classes/ccache.php
Normal file
@ -0,0 +1,211 @@
|
||||
<?php
|
||||
class CCache {
|
||||
static function zero_all($owner_uid) {
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("UPDATE ttrss_counters_cache SET
|
||||
value = 0 WHERE owner_uid = ?");
|
||||
$sth->execute([$owner_uid]);
|
||||
|
||||
$sth = $pdo->prepare("UPDATE ttrss_cat_counters_cache SET
|
||||
value = 0 WHERE owner_uid = ?");
|
||||
$sth->execute([$owner_uid]);
|
||||
}
|
||||
|
||||
static function remove($feed_id, $owner_uid, $is_cat = false) {
|
||||
|
||||
$feed_id = (int) $feed_id;
|
||||
|
||||
if (!$is_cat) {
|
||||
$table = "ttrss_counters_cache";
|
||||
} else {
|
||||
$table = "ttrss_cat_counters_cache";
|
||||
}
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("DELETE FROM $table WHERE
|
||||
feed_id = ? AND owner_uid = ?");
|
||||
$sth->execute([$feed_id, $owner_uid]);
|
||||
|
||||
}
|
||||
|
||||
static function update_all($owner_uid) {
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
if (get_pref('ENABLE_FEED_CATS', $owner_uid)) {
|
||||
|
||||
$sth = $pdo->prepare("SELECT feed_id FROM ttrss_cat_counters_cache
|
||||
WHERE feed_id > 0 AND owner_uid = ?");
|
||||
$sth->execute([$owner_uid]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
CCache::update($line["feed_id"], $owner_uid, true);
|
||||
}
|
||||
|
||||
/* We have to manually include category 0 */
|
||||
|
||||
CCache::update(0, $owner_uid, true);
|
||||
|
||||
} else {
|
||||
$sth = $pdo->prepare("SELECT feed_id FROM ttrss_counters_cache
|
||||
WHERE feed_id > 0 AND owner_uid = ?");
|
||||
$sth->execute([$owner_uid]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
print CCache::update($line["feed_id"], $owner_uid);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static function find($feed_id, $owner_uid, $is_cat = false,
|
||||
$no_update = false) {
|
||||
|
||||
// "" (null) is valid and should be cast to 0 (uncategorized)
|
||||
// everything else i.e. tags are not
|
||||
if (!is_numeric($feed_id) && $feed_id)
|
||||
return;
|
||||
|
||||
$feed_id = (int) $feed_id;
|
||||
|
||||
if (!$is_cat) {
|
||||
$table = "ttrss_counters_cache";
|
||||
} else {
|
||||
$table = "ttrss_cat_counters_cache";
|
||||
}
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT value FROM $table
|
||||
WHERE owner_uid = ? AND feed_id = ?
|
||||
LIMIT 1");
|
||||
|
||||
$sth->execute([$owner_uid, $feed_id]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
return $row["value"];
|
||||
} else {
|
||||
if ($no_update) {
|
||||
return -1;
|
||||
} else {
|
||||
return CCache::update($feed_id, $owner_uid, $is_cat);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static function update($feed_id, $owner_uid, $is_cat = false,
|
||||
$update_pcat = true, $pcat_fast = false) {
|
||||
|
||||
// "" (null) is valid and should be cast to 0 (uncategorized)
|
||||
// everything else i.e. tags are not
|
||||
if (!is_numeric($feed_id) && $feed_id)
|
||||
return;
|
||||
|
||||
$feed_id = (int) $feed_id;
|
||||
|
||||
$prev_unread = CCache::find($feed_id, $owner_uid, $is_cat, true);
|
||||
|
||||
/* When updating a label, all we need to do is recalculate feed counters
|
||||
* because labels are not cached */
|
||||
|
||||
if ($feed_id < 0) {
|
||||
CCache::update_all($owner_uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$is_cat) {
|
||||
$table = "ttrss_counters_cache";
|
||||
} else {
|
||||
$table = "ttrss_cat_counters_cache";
|
||||
}
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
if ($is_cat && $feed_id >= 0) {
|
||||
/* Recalculate counters for child feeds */
|
||||
|
||||
if (!$pcat_fast) {
|
||||
$sth = $pdo->prepare("SELECT id FROM ttrss_feeds
|
||||
WHERE owner_uid = :uid AND
|
||||
(cat_id = :cat OR (:cat = 0 AND cat_id IS NULL))");
|
||||
$sth->execute([":uid" => $owner_uid, ":cat" => $feed_id]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
CCache::update((int)$line["id"], $owner_uid, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
$sth = $pdo->prepare("SELECT SUM(value) AS sv
|
||||
FROM ttrss_counters_cache, ttrss_feeds
|
||||
WHERE ttrss_feeds.id = feed_id AND
|
||||
(cat_id = :cat OR (:cat = 0 AND cat_id IS NULL)) AND
|
||||
ttrss_counters_cache.owner_uid = :uid AND
|
||||
ttrss_feeds.owner_uid = :uid");
|
||||
$sth->execute([":uid" => $owner_uid, ":cat" => $feed_id]);
|
||||
$row = $sth->fetch();
|
||||
|
||||
$unread = (int) $row["sv"];
|
||||
|
||||
} else {
|
||||
$unread = (int) Feeds::getFeedArticles($feed_id, $is_cat, true, $owner_uid);
|
||||
}
|
||||
|
||||
$tr_in_progress = false;
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
} catch (Exception $e) {
|
||||
$tr_in_progress = true;
|
||||
}
|
||||
|
||||
$sth = $pdo->prepare("SELECT feed_id FROM $table
|
||||
WHERE owner_uid = ? AND feed_id = ? LIMIT 1");
|
||||
$sth->execute([$owner_uid, $feed_id]);
|
||||
|
||||
if ($sth->fetch()) {
|
||||
|
||||
$sth = $pdo->prepare("UPDATE $table SET
|
||||
value = ?, updated = NOW() WHERE
|
||||
feed_id = ? AND owner_uid = ?");
|
||||
|
||||
$sth->execute([$unread, $feed_id, $owner_uid]);
|
||||
|
||||
} else {
|
||||
$sth = $pdo->prepare("INSERT INTO $table
|
||||
(feed_id, value, owner_uid, updated)
|
||||
VALUES
|
||||
(?, ?, ?, NOW())");
|
||||
$sth->execute([$feed_id, $unread, $owner_uid]);
|
||||
}
|
||||
|
||||
if (!$tr_in_progress) $pdo->commit();
|
||||
|
||||
if ($feed_id > 0 && $prev_unread != $unread) {
|
||||
|
||||
if (!$is_cat) {
|
||||
|
||||
/* Update parent category */
|
||||
|
||||
if ($update_pcat) {
|
||||
|
||||
$sth = $pdo->prepare("SELECT cat_id FROM ttrss_feeds
|
||||
WHERE owner_uid = ? AND id = ?");
|
||||
$sth->execute([$owner_uid, $feed_id]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
CCache::update((int)$row["cat_id"], $owner_uid, true, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ($feed_id < 0) {
|
||||
CCache::update_all($owner_uid);
|
||||
}
|
||||
|
||||
return $unread;
|
||||
}
|
||||
|
||||
}
|
216
classes/counters.php
Normal file
216
classes/counters.php
Normal file
@ -0,0 +1,216 @@
|
||||
<?php
|
||||
class Counters {
|
||||
|
||||
static function getAllCounters() {
|
||||
$data = Counters::getGlobalCounters();
|
||||
|
||||
$data = array_merge($data, Counters::getVirtCounters());
|
||||
$data = array_merge($data, Counters::getLabelCounters());
|
||||
$data = array_merge($data, Counters::getFeedCounters());
|
||||
$data = array_merge($data, Counters::getCategoryCounters());
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
static function getCategoryCounters() {
|
||||
$ret_arr = array();
|
||||
|
||||
/* Labels category */
|
||||
|
||||
$cv = array("id" => -2, "kind" => "cat",
|
||||
"counter" => Feeds::getCategoryUnread(-2));
|
||||
|
||||
array_push($ret_arr, $cv);
|
||||
|
||||
$pdo = DB::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT ttrss_feed_categories.id AS cat_id, value AS unread,
|
||||
(SELECT COUNT(id) FROM ttrss_feed_categories AS c2
|
||||
WHERE c2.parent_cat = ttrss_feed_categories.id) AS num_children
|
||||
FROM ttrss_feed_categories, ttrss_cat_counters_cache
|
||||
WHERE ttrss_cat_counters_cache.feed_id = ttrss_feed_categories.id AND
|
||||
ttrss_cat_counters_cache.owner_uid = ttrss_feed_categories.owner_uid AND
|
||||
ttrss_feed_categories.owner_uid = ?");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
$line["cat_id"] = (int) $line["cat_id"];
|
||||
|
||||
if ($line["num_children"] > 0) {
|
||||
$child_counter = Feeds::getCategoryChildrenUnread($line["cat_id"], $_SESSION["uid"]);
|
||||
} else {
|
||||
$child_counter = 0;
|
||||
}
|
||||
|
||||
$cv = array("id" => $line["cat_id"], "kind" => "cat",
|
||||
"counter" => $line["unread"] + $child_counter);
|
||||
|
||||
array_push($ret_arr, $cv);
|
||||
}
|
||||
|
||||
/* Special case: NULL category doesn't actually exist in the DB */
|
||||
|
||||
$cv = array("id" => 0, "kind" => "cat",
|
||||
"counter" => (int) CCache::find(0, $_SESSION["uid"], true));
|
||||
|
||||
array_push($ret_arr, $cv);
|
||||
|
||||
return $ret_arr;
|
||||
}
|
||||
|
||||
static function getGlobalCounters($global_unread = -1) {
|
||||
$ret_arr = array();
|
||||
|
||||
if ($global_unread == -1) {
|
||||
$global_unread = Feeds::getGlobalUnread();
|
||||
}
|
||||
|
||||
$cv = array("id" => "global-unread",
|
||||
"counter" => (int) $global_unread);
|
||||
|
||||
array_push($ret_arr, $cv);
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT COUNT(id) AS fn FROM
|
||||
ttrss_feeds WHERE owner_uid = ?");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
$row = $sth->fetch();
|
||||
|
||||
$subscribed_feeds = $row["fn"];
|
||||
|
||||
$cv = array("id" => "subscribed-feeds",
|
||||
"counter" => (int) $subscribed_feeds);
|
||||
|
||||
array_push($ret_arr, $cv);
|
||||
|
||||
return $ret_arr;
|
||||
}
|
||||
|
||||
static function getVirtCounters() {
|
||||
|
||||
$ret_arr = array();
|
||||
|
||||
for ($i = 0; $i >= -4; $i--) {
|
||||
|
||||
$count = getFeedUnread($i);
|
||||
|
||||
if ($i == 0 || $i == -1 || $i == -2)
|
||||
$auxctr = Feeds::getFeedArticles($i, false);
|
||||
else
|
||||
$auxctr = 0;
|
||||
|
||||
$cv = array("id" => $i,
|
||||
"counter" => (int) $count,
|
||||
"auxcounter" => (int) $auxctr);
|
||||
|
||||
// if (get_pref('EXTENDED_FEEDLIST'))
|
||||
// $cv["xmsg"] = getFeedArticles($i)." ".__("total");
|
||||
|
||||
array_push($ret_arr, $cv);
|
||||
}
|
||||
|
||||
$feeds = PluginHost::getInstance()->get_feeds(-1);
|
||||
|
||||
if (is_array($feeds)) {
|
||||
foreach ($feeds as $feed) {
|
||||
$cv = array("id" => PluginHost::pfeed_to_feed_id($feed['id']),
|
||||
"counter" => $feed['sender']->get_unread($feed['id']));
|
||||
|
||||
if (method_exists($feed['sender'], 'get_total'))
|
||||
$cv["auxcounter"] = $feed['sender']->get_total($feed['id']);
|
||||
|
||||
array_push($ret_arr, $cv);
|
||||
}
|
||||
}
|
||||
|
||||
return $ret_arr;
|
||||
}
|
||||
|
||||
static function getLabelCounters($descriptions = false) {
|
||||
|
||||
$ret_arr = array();
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT id,caption,SUM(CASE WHEN u1.unread = true THEN 1 ELSE 0 END) AS unread, COUNT(u1.unread) AS total
|
||||
FROM ttrss_labels2 LEFT JOIN ttrss_user_labels2 ON
|
||||
(ttrss_labels2.id = label_id)
|
||||
LEFT JOIN ttrss_user_entries AS u1 ON u1.ref_id = article_id
|
||||
WHERE ttrss_labels2.owner_uid = :uid AND u1.owner_uid = :uid
|
||||
GROUP BY ttrss_labels2.id,
|
||||
ttrss_labels2.caption");
|
||||
$sth->execute([":uid" => $_SESSION['uid']]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
|
||||
$id = Labels::label_to_feed_id($line["id"]);
|
||||
|
||||
$cv = array("id" => $id,
|
||||
"counter" => (int) $line["unread"],
|
||||
"auxcounter" => (int) $line["total"]);
|
||||
|
||||
if ($descriptions)
|
||||
$cv["description"] = $line["caption"];
|
||||
|
||||
array_push($ret_arr, $cv);
|
||||
}
|
||||
|
||||
return $ret_arr;
|
||||
}
|
||||
|
||||
static function getFeedCounters($active_feed = false) {
|
||||
|
||||
$ret_arr = array();
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT ttrss_feeds.id,
|
||||
ttrss_feeds.title,
|
||||
".SUBSTRING_FOR_DATE."(ttrss_feeds.last_updated,1,19) AS last_updated,
|
||||
last_error, value AS count
|
||||
FROM ttrss_feeds, ttrss_counters_cache
|
||||
WHERE ttrss_feeds.owner_uid = ?
|
||||
AND ttrss_counters_cache.owner_uid = ttrss_feeds.owner_uid
|
||||
AND ttrss_counters_cache.feed_id = ttrss_feeds.id");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
|
||||
$id = $line["id"];
|
||||
$count = $line["count"];
|
||||
$last_error = htmlspecialchars($line["last_error"]);
|
||||
|
||||
$last_updated = make_local_datetime($line['last_updated'], false);
|
||||
|
||||
if (Feeds::feedHasIcon($id)) {
|
||||
$has_img = filemtime(Feeds::getIconFile($id));
|
||||
} else {
|
||||
$has_img = false;
|
||||
}
|
||||
|
||||
if (date('Y') - date('Y', strtotime($line['last_updated'])) > 2)
|
||||
$last_updated = '';
|
||||
|
||||
$cv = array("id" => $id,
|
||||
"updated" => $last_updated,
|
||||
"counter" => (int) $count,
|
||||
"has_img" => (int) $has_img);
|
||||
|
||||
if ($last_error)
|
||||
$cv["error"] = $last_error;
|
||||
|
||||
// if (get_pref('EXTENDED_FEEDLIST'))
|
||||
// $cv["xmsg"] = getFeedArticles($id)." ".__("total");
|
||||
|
||||
if ($active_feed && $id == $active_feed)
|
||||
$cv["title"] = truncate_string($line["title"], 30);
|
||||
|
||||
array_push($ret_arr, $cv);
|
||||
|
||||
}
|
||||
|
||||
return $ret_arr;
|
||||
}
|
||||
|
||||
}
|
116
classes/db.php
Executable file
116
classes/db.php
Executable file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
class Db
|
||||
{
|
||||
|
||||
/* @var Db $instance */
|
||||
private static $instance;
|
||||
|
||||
/* @var IDb $adapter */
|
||||
private $adapter;
|
||||
|
||||
private $link;
|
||||
|
||||
/* @var PDO $pdo */
|
||||
private $pdo;
|
||||
|
||||
private function __clone() {
|
||||
//
|
||||
}
|
||||
|
||||
private function legacy_connect() {
|
||||
|
||||
user_error("Legacy connect requested to " . DB_TYPE, E_USER_NOTICE);
|
||||
|
||||
$er = error_reporting(E_ALL);
|
||||
|
||||
switch (DB_TYPE) {
|
||||
case "mysql":
|
||||
$this->adapter = new Db_Mysqli();
|
||||
break;
|
||||
case "pgsql":
|
||||
$this->adapter = new Db_Pgsql();
|
||||
break;
|
||||
default:
|
||||
die("Unknown DB_TYPE: " . DB_TYPE);
|
||||
}
|
||||
|
||||
if (!$this->adapter) {
|
||||
print("Error initializing database adapter for " . DB_TYPE);
|
||||
exit(100);
|
||||
}
|
||||
|
||||
$this->link = $this->adapter->connect(DB_HOST, DB_USER, DB_PASS, DB_NAME, defined('DB_PORT') ? DB_PORT : "");
|
||||
|
||||
if (!$this->link) {
|
||||
print("Error connecting through adapter: " . $this->adapter->last_error());
|
||||
exit(101);
|
||||
}
|
||||
|
||||
error_reporting($er);
|
||||
}
|
||||
|
||||
// this really shouldn't be used unless a separate PDO connection is needed
|
||||
// normal usage is Db::pdo()->prepare(...) etc
|
||||
public function pdo_connect() {
|
||||
|
||||
$db_port = defined('DB_PORT') && DB_PORT ? ';port=' . DB_PORT : '';
|
||||
$db_host = defined('DB_HOST') && DB_HOST ? ';host=' . DB_HOST : '';
|
||||
|
||||
try {
|
||||
$pdo = new PDO(DB_TYPE . ':dbname=' . DB_NAME . $db_host . $db_port,
|
||||
DB_USER,
|
||||
DB_PASS);
|
||||
} catch (Exception $e) {
|
||||
print "<pre>Exception while creating PDO object:" . $e->getMessage() . "</pre>";
|
||||
exit(101);
|
||||
}
|
||||
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
if (DB_TYPE == "pgsql") {
|
||||
|
||||
$pdo->query("set client_encoding = 'UTF-8'");
|
||||
$pdo->query("set datestyle = 'ISO, european'");
|
||||
$pdo->query("set TIME ZONE 0");
|
||||
$pdo->query("set cpu_tuple_cost = 0.5");
|
||||
|
||||
} else if (DB_TYPE == "mysql") {
|
||||
$pdo->query("SET time_zone = '+0:0'");
|
||||
|
||||
if (defined('MYSQL_CHARSET') && MYSQL_CHARSET) {
|
||||
$pdo->query("SET NAMES " . MYSQL_CHARSET);
|
||||
}
|
||||
}
|
||||
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
public static function instance() {
|
||||
if (self::$instance == null)
|
||||
self::$instance = new self();
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public static function get() {
|
||||
if (self::$instance == null)
|
||||
self::$instance = new self();
|
||||
|
||||
if (!self::$instance->adapter) {
|
||||
self::$instance->legacy_connect();
|
||||
}
|
||||
|
||||
return self::$instance->adapter;
|
||||
}
|
||||
|
||||
public static function pdo() {
|
||||
if (self::$instance == null)
|
||||
self::$instance = new self();
|
||||
|
||||
if (!self::$instance->pdo) {
|
||||
self::$instance->pdo = self::$instance->pdo_connect();
|
||||
}
|
||||
|
||||
return self::$instance->pdo;
|
||||
}
|
||||
}
|
85
classes/db/mysqli.php
Normal file
85
classes/db/mysqli.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
class Db_Mysqli implements IDb {
|
||||
private $link;
|
||||
private $last_error;
|
||||
|
||||
function connect($host, $user, $pass, $db, $port) {
|
||||
if ($port)
|
||||
$this->link = mysqli_connect($host, $user, $pass, $db, $port);
|
||||
else
|
||||
$this->link = mysqli_connect($host, $user, $pass, $db);
|
||||
|
||||
if ($this->link) {
|
||||
$this->init();
|
||||
|
||||
return $this->link;
|
||||
} else {
|
||||
print("Unable to connect to database (as $user to $host, database $db): " . mysqli_connect_error());
|
||||
exit(102);
|
||||
}
|
||||
}
|
||||
|
||||
function escape_string($s, $strip_tags = true) {
|
||||
if ($strip_tags) $s = strip_tags($s);
|
||||
|
||||
return mysqli_real_escape_string($this->link, $s);
|
||||
}
|
||||
|
||||
function query($query, $die_on_error = true) {
|
||||
$result = @mysqli_query($this->link, $query);
|
||||
if (!$result) {
|
||||
$this->last_error = @mysqli_error($this->link);
|
||||
|
||||
@mysqli_query($this->link, "ROLLBACK");
|
||||
user_error("Query $query failed: " . ($this->link ? $this->last_error : "No connection"),
|
||||
$die_on_error ? E_USER_ERROR : E_USER_WARNING);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function fetch_assoc($result) {
|
||||
return mysqli_fetch_assoc($result);
|
||||
}
|
||||
|
||||
|
||||
function num_rows($result) {
|
||||
return mysqli_num_rows($result);
|
||||
}
|
||||
|
||||
function fetch_result($result, $row, $param) {
|
||||
if (mysqli_data_seek($result, $row)) {
|
||||
$line = mysqli_fetch_assoc($result);
|
||||
return $line[$param];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
return mysqli_close($this->link);
|
||||
}
|
||||
|
||||
function affected_rows($result) {
|
||||
return mysqli_affected_rows($this->link);
|
||||
}
|
||||
|
||||
function last_error() {
|
||||
return mysqli_error($this->link);
|
||||
}
|
||||
|
||||
function last_query_error() {
|
||||
return $this->last_error;
|
||||
}
|
||||
|
||||
function init() {
|
||||
$this->query("SET time_zone = '+0:0'");
|
||||
|
||||
if (defined('MYSQL_CHARSET') && MYSQL_CHARSET) {
|
||||
mysqli_set_charset($this->link, MYSQL_CHARSET);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
91
classes/db/pgsql.php
Normal file
91
classes/db/pgsql.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
class Db_Pgsql implements IDb {
|
||||
private $link;
|
||||
private $last_error;
|
||||
|
||||
function connect($host, $user, $pass, $db, $port) {
|
||||
$string = "dbname=$db user=$user";
|
||||
|
||||
if ($pass) {
|
||||
$string .= " password=$pass";
|
||||
}
|
||||
|
||||
if ($host) {
|
||||
$string .= " host=$host";
|
||||
}
|
||||
|
||||
if (is_numeric($port) && $port > 0) {
|
||||
$string = "$string port=" . $port;
|
||||
}
|
||||
|
||||
$this->link = pg_connect($string);
|
||||
|
||||
if (!$this->link) {
|
||||
print("Unable to connect to database (as $user to $host, database $db):" . pg_last_error());
|
||||
exit(102);
|
||||
}
|
||||
|
||||
$this->init();
|
||||
|
||||
return $this->link;
|
||||
}
|
||||
|
||||
function escape_string($s, $strip_tags = true) {
|
||||
if ($strip_tags) $s = strip_tags($s);
|
||||
|
||||
return pg_escape_string($s);
|
||||
}
|
||||
|
||||
function query($query, $die_on_error = true) {
|
||||
$result = @pg_query($this->link, $query);
|
||||
|
||||
if (!$result) {
|
||||
$this->last_error = @pg_last_error($this->link);
|
||||
|
||||
@pg_query($this->link, "ROLLBACK");
|
||||
$query = htmlspecialchars($query); // just in case
|
||||
user_error("Query $query failed: " . ($this->link ? $this->last_error : "No connection"),
|
||||
$die_on_error ? E_USER_ERROR : E_USER_WARNING);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function fetch_assoc($result) {
|
||||
return pg_fetch_assoc($result);
|
||||
}
|
||||
|
||||
|
||||
function num_rows($result) {
|
||||
return pg_num_rows($result);
|
||||
}
|
||||
|
||||
function fetch_result($result, $row, $param) {
|
||||
return pg_fetch_result($result, $row, $param);
|
||||
}
|
||||
|
||||
function close() {
|
||||
return pg_close($this->link);
|
||||
}
|
||||
|
||||
function affected_rows($result) {
|
||||
return pg_affected_rows($result);
|
||||
}
|
||||
|
||||
function last_error() {
|
||||
return pg_last_error($this->link);
|
||||
}
|
||||
|
||||
function last_query_error() {
|
||||
return $this->last_error;
|
||||
}
|
||||
|
||||
function init() {
|
||||
$this->query("set client_encoding = 'UTF-8'");
|
||||
pg_set_client_encoding("UNICODE");
|
||||
$this->query("set datestyle = 'ISO, european'");
|
||||
$this->query("set TIME ZONE 0");
|
||||
$this->query("set cpu_tuple_cost = 0.5");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
171
classes/db/prefs.php
Normal file
171
classes/db/prefs.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
class Db_Prefs {
|
||||
private $pdo;
|
||||
private static $instance;
|
||||
private $cache;
|
||||
|
||||
function __construct() {
|
||||
$this->pdo = Db::pdo();
|
||||
$this->cache = array();
|
||||
|
||||
if ($_SESSION["uid"]) $this->cache();
|
||||
}
|
||||
|
||||
private function __clone() {
|
||||
//
|
||||
}
|
||||
|
||||
public static function get() {
|
||||
if (self::$instance == null)
|
||||
self::$instance = new self();
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
function cache() {
|
||||
$user_id = $_SESSION["uid"];
|
||||
@$profile = $_SESSION["profile"];
|
||||
|
||||
if (!is_numeric($profile) || !$profile || get_schema_version() < 63) $profile = null;
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT
|
||||
value,ttrss_prefs_types.type_name as type_name,ttrss_prefs.pref_name AS pref_name
|
||||
FROM
|
||||
ttrss_user_prefs,ttrss_prefs,ttrss_prefs_types
|
||||
WHERE
|
||||
(profile = :profile OR (:profile IS NULL AND profile IS NULL)) AND
|
||||
ttrss_prefs.pref_name NOT LIKE '_MOBILE%' AND
|
||||
ttrss_prefs_types.id = type_id AND
|
||||
owner_uid = :uid AND
|
||||
ttrss_user_prefs.pref_name = ttrss_prefs.pref_name");
|
||||
|
||||
$sth->execute([":profile" => $profile, ":uid" => $user_id]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
if ($user_id == $_SESSION["uid"]) {
|
||||
$pref_name = $line["pref_name"];
|
||||
|
||||
$this->cache[$pref_name]["type"] = $line["type_name"];
|
||||
$this->cache[$pref_name]["value"] = $line["value"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function read($pref_name, $user_id = false, $die_on_error = false) {
|
||||
|
||||
if (!$user_id) {
|
||||
$user_id = $_SESSION["uid"];
|
||||
@$profile = $_SESSION["profile"];
|
||||
} else {
|
||||
$profile = false;
|
||||
}
|
||||
|
||||
if ($user_id == $_SESSION['uid'] && isset($this->cache[$pref_name])) {
|
||||
$tuple = $this->cache[$pref_name];
|
||||
return $this->convert($tuple["value"], $tuple["type"]);
|
||||
}
|
||||
|
||||
if (!is_numeric($profile) || !$profile || get_schema_version() < 63) $profile = null;
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT
|
||||
value,ttrss_prefs_types.type_name as type_name
|
||||
FROM
|
||||
ttrss_user_prefs,ttrss_prefs,ttrss_prefs_types
|
||||
WHERE
|
||||
(profile = :profile OR (:profile IS NULL AND profile IS NULL)) AND
|
||||
ttrss_user_prefs.pref_name = :pref_name AND
|
||||
ttrss_prefs_types.id = type_id AND
|
||||
owner_uid = :uid AND
|
||||
ttrss_user_prefs.pref_name = ttrss_prefs.pref_name");
|
||||
$sth->execute([":uid" => $user_id, ":profile" => $profile, ":pref_name" => $pref_name]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$value = $row["value"];
|
||||
$type_name = $row["type_name"];
|
||||
|
||||
if ($user_id == $_SESSION["uid"]) {
|
||||
$this->cache[$pref_name]["type"] = $type_name;
|
||||
$this->cache[$pref_name]["value"] = $value;
|
||||
}
|
||||
|
||||
return $this->convert($value, $type_name);
|
||||
|
||||
} else {
|
||||
user_error("Fatal error, unknown preferences key: $pref_name (owner: $user_id)", $die_on_error ? E_USER_ERROR : E_USER_WARNING);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function convert($value, $type_name) {
|
||||
if ($type_name == "bool") {
|
||||
return $value == "true";
|
||||
} else if ($type_name == "integer") {
|
||||
return (int)$value;
|
||||
} else {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
function write($pref_name, $value, $user_id = false, $strip_tags = true) {
|
||||
if ($strip_tags) $value = strip_tags($value);
|
||||
|
||||
if (!$user_id) {
|
||||
$user_id = $_SESSION["uid"];
|
||||
@$profile = $_SESSION["profile"];
|
||||
} else {
|
||||
$profile = null;
|
||||
}
|
||||
|
||||
if (!is_numeric($profile) || !$profile || get_schema_version() < 63) $profile = null;
|
||||
|
||||
$type_name = "";
|
||||
$current_value = "";
|
||||
|
||||
if (isset($this->cache[$pref_name])) {
|
||||
$type_name = $this->cache[$pref_name]["type"];
|
||||
$current_value = $this->cache[$pref_name]["value"];
|
||||
}
|
||||
|
||||
if (!$type_name) {
|
||||
$sth = $this->pdo->prepare("SELECT type_name
|
||||
FROM ttrss_prefs,ttrss_prefs_types
|
||||
WHERE pref_name = ? AND type_id = ttrss_prefs_types.id");
|
||||
$sth->execute([$pref_name]);
|
||||
|
||||
if ($row = $sth->fetch())
|
||||
$type_name = $row["type_name"];
|
||||
|
||||
} else if ($current_value == $value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($type_name) {
|
||||
if ($type_name == "bool") {
|
||||
if ($value == "1" || $value == "true") {
|
||||
$value = "true";
|
||||
} else {
|
||||
$value = "false";
|
||||
}
|
||||
} else if ($type_name == "integer") {
|
||||
$value = (int)$value;
|
||||
}
|
||||
|
||||
if ($pref_name == 'USER_TIMEZONE' && $value == '') {
|
||||
$value = 'UTC';
|
||||
}
|
||||
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_prefs SET
|
||||
value = :value WHERE pref_name = :pref_name
|
||||
AND (profile = :profile OR (:profile IS NULL AND profile IS NULL))
|
||||
AND owner_uid = :uid");
|
||||
|
||||
$sth->execute([":pref_name" => $pref_name, ":value" => $value, ":uid" => $user_id, ":profile" => $profile]);
|
||||
|
||||
if ($user_id == $_SESSION["uid"]) {
|
||||
$this->cache[$pref_name]["type"] = $type_name;
|
||||
$this->cache[$pref_name]["value"] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
83
classes/dbupdater.php
Normal file
83
classes/dbupdater.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
class DbUpdater {
|
||||
|
||||
private $pdo;
|
||||
private $db_type;
|
||||
private $need_version;
|
||||
|
||||
function __construct($pdo, $db_type, $need_version) {
|
||||
$this->pdo = Db::pdo(); //$pdo;
|
||||
$this->db_type = $db_type;
|
||||
$this->need_version = (int) $need_version;
|
||||
}
|
||||
|
||||
function getSchemaVersion() {
|
||||
$row = $this->pdo->query("SELECT schema_version FROM ttrss_version")->fetch();
|
||||
return (int) $row['schema_version'];
|
||||
}
|
||||
|
||||
function isUpdateRequired() {
|
||||
return $this->getSchemaVersion() < $this->need_version;
|
||||
}
|
||||
|
||||
function getSchemaLines($version) {
|
||||
$filename = "schema/versions/".$this->db_type."/$version.sql";
|
||||
|
||||
if (file_exists($filename)) {
|
||||
return explode(";", preg_replace("/[\r\n]/", "", file_get_contents($filename)));
|
||||
} else {
|
||||
user_error("DB Updater: schema file for version $version is not found.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function performUpdateTo($version, $html_output = true) {
|
||||
if ($this->getSchemaVersion() == $version - 1) {
|
||||
|
||||
$lines = $this->getSchemaLines($version);
|
||||
|
||||
if (is_array($lines)) {
|
||||
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, "--") !== 0 && $line) {
|
||||
|
||||
if ($html_output)
|
||||
print "<pre>$line</pre>";
|
||||
else
|
||||
Debug::log("> $line");
|
||||
|
||||
try {
|
||||
$this->pdo->query($line); // PDO returns errors as exceptions now
|
||||
} catch (PDOException $e) {
|
||||
if ($html_output) {
|
||||
print "<div class='text-error'>Error: " . $e->getMessage() . "</div>";
|
||||
} else {
|
||||
Debug::log("Error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$this->pdo->rollBack();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$db_version = $this->getSchemaVersion();
|
||||
|
||||
if ($db_version == $version) {
|
||||
$this->pdo->commit();
|
||||
return true;
|
||||
} else {
|
||||
$this->pdo->rollBack();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
86
classes/debug.php
Normal file
86
classes/debug.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
class Debug {
|
||||
public static $LOG_DISABLED = -1;
|
||||
public static $LOG_NORMAL = 0;
|
||||
public static $LOG_VERBOSE = 1;
|
||||
public static $LOG_EXTENDED = 2;
|
||||
|
||||
private static $enabled = false;
|
||||
private static $quiet = false;
|
||||
private static $logfile = false;
|
||||
private static $loglevel = 0;
|
||||
|
||||
public static function set_logfile($logfile) {
|
||||
Debug::$logfile = $logfile;
|
||||
}
|
||||
|
||||
public static function enabled() {
|
||||
return Debug::$enabled;
|
||||
}
|
||||
|
||||
public static function set_enabled($enable) {
|
||||
Debug::$enabled = $enable;
|
||||
}
|
||||
|
||||
public static function set_quiet($quiet) {
|
||||
Debug::$quiet = $quiet;
|
||||
}
|
||||
|
||||
public static function set_loglevel($level) {
|
||||
Debug::$loglevel = $level;
|
||||
}
|
||||
|
||||
public static function get_loglevel() {
|
||||
return Debug::$loglevel;
|
||||
}
|
||||
|
||||
public static function log($message, $level = 0) {
|
||||
|
||||
if (!Debug::$enabled || Debug::$loglevel < $level) return false;
|
||||
|
||||
$ts = strftime("%H:%M:%S", time());
|
||||
if (function_exists('posix_getpid')) {
|
||||
$ts = "$ts/" . posix_getpid();
|
||||
}
|
||||
|
||||
if (Debug::$logfile) {
|
||||
$fp = fopen(Debug::$logfile, 'a+');
|
||||
|
||||
if ($fp) {
|
||||
$locked = false;
|
||||
|
||||
if (function_exists("flock")) {
|
||||
$tries = 0;
|
||||
|
||||
// try to lock logfile for writing
|
||||
while ($tries < 5 && !$locked = flock($fp, LOCK_EX | LOCK_NB)) {
|
||||
sleep(1);
|
||||
++$tries;
|
||||
}
|
||||
|
||||
if (!$locked) {
|
||||
fclose($fp);
|
||||
user_error("Unable to lock debugging log file: " . Debug::$logfile, E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fputs($fp, "[$ts] $message\n");
|
||||
|
||||
if (function_exists("flock")) {
|
||||
flock($fp, LOCK_UN);
|
||||
}
|
||||
|
||||
fclose($fp);
|
||||
|
||||
if (Debug::$quiet)
|
||||
return;
|
||||
|
||||
} else {
|
||||
user_error("Unable to open debugging log file: " . Debug::$logfile, E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
print "[$ts] $message\n";
|
||||
}
|
||||
}
|
216
classes/digest.php
Normal file
216
classes/digest.php
Normal file
@ -0,0 +1,216 @@
|
||||
<?php
|
||||
class Digest
|
||||
{
|
||||
|
||||
/**
|
||||
* Send by mail a digest of last articles.
|
||||
*
|
||||
* @param mixed $link The database connection.
|
||||
* @param integer $limit The maximum number of articles by digest.
|
||||
* @return boolean Return false if digests are not enabled.
|
||||
*/
|
||||
static function send_headlines_digests() {
|
||||
|
||||
$user_limit = 15; // amount of users to process (e.g. emails to send out)
|
||||
$limit = 1000; // maximum amount of headlines to include
|
||||
|
||||
Debug::log("Sending digests, batch of max $user_limit users, headline limit = $limit");
|
||||
|
||||
if (DB_TYPE == "pgsql") {
|
||||
$interval_qpart = "last_digest_sent < NOW() - INTERVAL '1 days'";
|
||||
} else if (DB_TYPE == "mysql") {
|
||||
$interval_qpart = "last_digest_sent < DATE_SUB(NOW(), INTERVAL 1 DAY)";
|
||||
}
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$res = $pdo->query("SELECT id,email FROM ttrss_users
|
||||
WHERE email != '' AND (last_digest_sent IS NULL OR $interval_qpart)");
|
||||
|
||||
while ($line = $res->fetch()) {
|
||||
|
||||
if (@get_pref('DIGEST_ENABLE', $line['id'], false)) {
|
||||
$preferred_ts = strtotime(get_pref('DIGEST_PREFERRED_TIME', $line['id'], '00:00'));
|
||||
|
||||
// try to send digests within 2 hours of preferred time
|
||||
if ($preferred_ts && time() >= $preferred_ts &&
|
||||
time() - $preferred_ts <= 7200
|
||||
) {
|
||||
|
||||
Debug::log("Sending digest for UID:" . $line['id'] . " - " . $line["email"]);
|
||||
|
||||
$do_catchup = get_pref('DIGEST_CATCHUP', $line['id'], false);
|
||||
|
||||
global $tz_offset;
|
||||
|
||||
// reset tz_offset global to prevent tz cache clash between users
|
||||
$tz_offset = -1;
|
||||
|
||||
$tuple = Digest::prepare_headlines_digest($line["id"], 1, $limit);
|
||||
$digest = $tuple[0];
|
||||
$headlines_count = $tuple[1];
|
||||
$affected_ids = $tuple[2];
|
||||
$digest_text = $tuple[3];
|
||||
|
||||
if ($headlines_count > 0) {
|
||||
|
||||
$mailer = new Mailer();
|
||||
|
||||
//$rc = $mail->quickMail($line["email"], $line["login"], DIGEST_SUBJECT, $digest, $digest_text);
|
||||
|
||||
$rc = $mailer->mail(["to_name" => $line["login"],
|
||||
"to_address" => $line["email"],
|
||||
"subject" => DIGEST_SUBJECT,
|
||||
"message" => $digest_text,
|
||||
"message_html" => $digest]);
|
||||
|
||||
//if (!$rc && $debug) Debug::log("ERROR: " . $mailer->lastError());
|
||||
|
||||
Debug::log("RC=$rc");
|
||||
|
||||
if ($rc && $do_catchup) {
|
||||
Debug::log("Marking affected articles as read...");
|
||||
Article::catchupArticlesById($affected_ids, 0, $line["id"]);
|
||||
}
|
||||
} else {
|
||||
Debug::log("No headlines");
|
||||
}
|
||||
|
||||
$sth = $pdo->prepare("UPDATE ttrss_users SET last_digest_sent = NOW()
|
||||
WHERE id = ?");
|
||||
$sth->execute([$line["id"]]);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug::log("All done.");
|
||||
|
||||
}
|
||||
|
||||
static function prepare_headlines_digest($user_id, $days = 1, $limit = 1000) {
|
||||
|
||||
require_once "lib/MiniTemplator.class.php";
|
||||
|
||||
$tpl = new MiniTemplator;
|
||||
$tpl_t = new MiniTemplator;
|
||||
|
||||
$tpl->readTemplateFromFile("templates/digest_template_html.txt");
|
||||
$tpl_t->readTemplateFromFile("templates/digest_template.txt");
|
||||
|
||||
$user_tz_string = get_pref('USER_TIMEZONE', $user_id);
|
||||
$local_ts = convert_timestamp(time(), 'UTC', $user_tz_string);
|
||||
|
||||
$tpl->setVariable('CUR_DATE', date('Y/m/d', $local_ts));
|
||||
$tpl->setVariable('CUR_TIME', date('G:i', $local_ts));
|
||||
|
||||
$tpl_t->setVariable('CUR_DATE', date('Y/m/d', $local_ts));
|
||||
$tpl_t->setVariable('CUR_TIME', date('G:i', $local_ts));
|
||||
|
||||
$affected_ids = array();
|
||||
|
||||
$days = (int) $days;
|
||||
|
||||
if (DB_TYPE == "pgsql") {
|
||||
$interval_qpart = "ttrss_entries.date_updated > NOW() - INTERVAL '$days days'";
|
||||
} else if (DB_TYPE == "mysql") {
|
||||
$interval_qpart = "ttrss_entries.date_updated > DATE_SUB(NOW(), INTERVAL $days DAY)";
|
||||
}
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT ttrss_entries.title,
|
||||
ttrss_feeds.title AS feed_title,
|
||||
COALESCE(ttrss_feed_categories.title, '" . __('Uncategorized') . "') AS cat_title,
|
||||
date_updated,
|
||||
ttrss_user_entries.ref_id,
|
||||
link,
|
||||
score,
|
||||
content,
|
||||
" . SUBSTRING_FOR_DATE . "(last_updated,1,19) AS last_updated
|
||||
FROM
|
||||
ttrss_user_entries,ttrss_entries,ttrss_feeds
|
||||
LEFT JOIN
|
||||
ttrss_feed_categories ON (cat_id = ttrss_feed_categories.id)
|
||||
WHERE
|
||||
ref_id = ttrss_entries.id AND feed_id = ttrss_feeds.id
|
||||
AND include_in_digest = true
|
||||
AND $interval_qpart
|
||||
AND ttrss_user_entries.owner_uid = :user_id
|
||||
AND unread = true
|
||||
AND score >= 0
|
||||
ORDER BY ttrss_feed_categories.title, ttrss_feeds.title, score DESC, date_updated DESC
|
||||
LIMIT :limit");
|
||||
$sth->bindParam(':user_id', intval($user_id, 10), PDO::PARAM_INT);
|
||||
$sth->bindParam(':limit', intval($limit, 10), PDO::PARAM_INT);
|
||||
$sth->execute();
|
||||
|
||||
$headlines_count = 0;
|
||||
$headlines = array();
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
array_push($headlines, $line);
|
||||
$headlines_count++;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < sizeof($headlines); $i++) {
|
||||
|
||||
$line = $headlines[$i];
|
||||
|
||||
array_push($affected_ids, $line["ref_id"]);
|
||||
|
||||
$updated = make_local_datetime($line['last_updated'], false,
|
||||
$user_id);
|
||||
|
||||
if (get_pref('ENABLE_FEED_CATS', $user_id)) {
|
||||
$line['feed_title'] = $line['cat_title'] . " / " . $line['feed_title'];
|
||||
}
|
||||
|
||||
$article_labels = Article::get_article_labels($line["ref_id"], $user_id);
|
||||
$article_labels_formatted = "";
|
||||
|
||||
if (is_array($article_labels) && count($article_labels) > 0) {
|
||||
$article_labels_formatted = implode(", ", array_map(function($a) {
|
||||
return $a[1];
|
||||
}, $article_labels));
|
||||
}
|
||||
|
||||
$tpl->setVariable('FEED_TITLE', $line["feed_title"]);
|
||||
$tpl->setVariable('ARTICLE_TITLE', $line["title"]);
|
||||
$tpl->setVariable('ARTICLE_LINK', $line["link"]);
|
||||
$tpl->setVariable('ARTICLE_UPDATED', $updated);
|
||||
$tpl->setVariable('ARTICLE_EXCERPT',
|
||||
truncate_string(strip_tags($line["content"]), 300));
|
||||
// $tpl->setVariable('ARTICLE_CONTENT',
|
||||
// strip_tags($article_content));
|
||||
$tpl->setVariable('ARTICLE_LABELS', $article_labels_formatted, true);
|
||||
|
||||
$tpl->addBlock('article');
|
||||
|
||||
$tpl_t->setVariable('FEED_TITLE', $line["feed_title"]);
|
||||
$tpl_t->setVariable('ARTICLE_TITLE', $line["title"]);
|
||||
$tpl_t->setVariable('ARTICLE_LINK', $line["link"]);
|
||||
$tpl_t->setVariable('ARTICLE_UPDATED', $updated);
|
||||
$tpl_t->setVariable('ARTICLE_LABELS', $article_labels_formatted, true);
|
||||
$tpl_t->setVariable('ARTICLE_EXCERPT',
|
||||
truncate_string(strip_tags($line["excerpt"]), 300), true);
|
||||
|
||||
$tpl_t->addBlock('article');
|
||||
|
||||
if ($headlines[$i]['feed_title'] != $headlines[$i + 1]['feed_title']) {
|
||||
$tpl->addBlock('feed');
|
||||
$tpl_t->addBlock('feed');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$tpl->addBlock('digest');
|
||||
$tpl->generateOutputToString($tmp);
|
||||
|
||||
$tpl_t->addBlock('digest');
|
||||
$tpl_t->generateOutputToString($tmp_t);
|
||||
|
||||
return array($tmp, $headlines_count, $affected_ids, $tmp_t);
|
||||
}
|
||||
|
||||
}
|
206
classes/dlg.php
Normal file
206
classes/dlg.php
Normal file
@ -0,0 +1,206 @@
|
||||
<?php
|
||||
class Dlg extends Handler_Protected {
|
||||
private $param;
|
||||
private $params;
|
||||
|
||||
function before($method) {
|
||||
if (parent::before($method)) {
|
||||
header("Content-Type: text/html"); # required for iframe
|
||||
|
||||
$this->param = $_REQUEST["param"];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function importOpml() {
|
||||
print_notice("If you have imported labels and/or filters, you might need to reload preferences to see your new data.");
|
||||
|
||||
print "<div class='panel panel-scrollable'>";
|
||||
|
||||
$opml = new Opml($_REQUEST);
|
||||
|
||||
$opml->opml_import($_SESSION["uid"]);
|
||||
|
||||
print "</div>";
|
||||
|
||||
print "<footer class='text-center'>";
|
||||
print "<button dojoType='dijit.form.Button'
|
||||
onclick=\"dijit.byId('opmlImportDlg').execute()\">".
|
||||
__('Close this window')."</button>";
|
||||
print "</footer>";
|
||||
|
||||
print "</div>";
|
||||
|
||||
//return;
|
||||
}
|
||||
|
||||
function pubOPMLUrl() {
|
||||
$url_path = Opml::opml_publish_url();
|
||||
|
||||
print "<header>" . __("Your Public OPML URL is:") . "</header>";
|
||||
|
||||
print "<section>";
|
||||
|
||||
print "<div class='panel text-center'>";
|
||||
print "<a id='pub_opml_url' href='$url_path' target='_blank'>$url_path</a>";
|
||||
print "</div>";
|
||||
|
||||
print "</section>";
|
||||
|
||||
print "<footer class='text-center'>";
|
||||
|
||||
print "<button dojoType='dijit.form.Button' onclick=\"return Helpers.OPML.changeKey()\">".
|
||||
__('Generate new URL')."</button> ";
|
||||
|
||||
print "<button dojoType='dijit.form.Button' onclick=\"return CommonDialogs.closeInfoBox()\">".
|
||||
__('Close this window')."</button>";
|
||||
|
||||
print "</footer>";
|
||||
|
||||
//return;
|
||||
}
|
||||
|
||||
function explainError() {
|
||||
print "<div class=\"errorExplained\">";
|
||||
|
||||
if ($this->param == 1) {
|
||||
print __("Update daemon is enabled in configuration, but daemon process is not running, which prevents all feeds from updating. Please start the daemon process or contact instance owner.");
|
||||
|
||||
$stamp = (int) file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
|
||||
|
||||
print "<p>" . __("Last update:") . " " . date("Y.m.d, G:i", $stamp);
|
||||
|
||||
}
|
||||
|
||||
if ($this->param == 3) {
|
||||
print __("Update daemon is taking too long to perform a feed update. This could indicate a problem like crash or a hang. Please check the daemon process or contact instance owner.");
|
||||
|
||||
$stamp = (int) file_get_contents(LOCK_DIRECTORY . "/update_daemon.stamp");
|
||||
|
||||
print "<p>" . __("Last update:") . " " . date("Y.m.d, G:i", $stamp);
|
||||
|
||||
}
|
||||
|
||||
print "</div>";
|
||||
|
||||
print "<footer class='text-center'>";
|
||||
print "<button onclick=\"return CommonDialogs.closeInfoBox()\">".
|
||||
__('Close this window')."</button>";
|
||||
print "</footer>";
|
||||
|
||||
//return;
|
||||
}
|
||||
|
||||
function printTagCloud() {
|
||||
print "<div class='panel text-center'>";
|
||||
|
||||
// from here: http://www.roscripts.com/Create_tag_cloud-71.html
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT tag_name, COUNT(post_int_id) AS count
|
||||
FROM ttrss_tags WHERE owner_uid = ?
|
||||
GROUP BY tag_name ORDER BY count DESC LIMIT 50");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
|
||||
$tags = array();
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
$tags[$line["tag_name"]] = $line["count"];
|
||||
}
|
||||
|
||||
if(count($tags) == 0 ){ return; }
|
||||
|
||||
ksort($tags);
|
||||
|
||||
$max_size = 32; // max font size in pixels
|
||||
$min_size = 11; // min font size in pixels
|
||||
|
||||
// largest and smallest array values
|
||||
$max_qty = max(array_values($tags));
|
||||
$min_qty = min(array_values($tags));
|
||||
|
||||
// find the range of values
|
||||
$spread = $max_qty - $min_qty;
|
||||
if ($spread == 0) { // we don't want to divide by zero
|
||||
$spread = 1;
|
||||
}
|
||||
|
||||
// set the font-size increment
|
||||
$step = ($max_size - $min_size) / ($spread);
|
||||
|
||||
// loop through the tag array
|
||||
foreach ($tags as $key => $value) {
|
||||
// calculate font-size
|
||||
// find the $value in excess of $min_qty
|
||||
// multiply by the font-size increment ($size)
|
||||
// and add the $min_size set above
|
||||
$size = round($min_size + (($value - $min_qty) * $step));
|
||||
|
||||
$key_escaped = str_replace("'", "\\'", $key);
|
||||
|
||||
echo "<a href=\"#\" onclick=\"Feeds.open({feed:'$key_escaped'}) \" style=\"font-size: " .
|
||||
$size . "px\" title=\"$value articles tagged with " .
|
||||
$key . '">' . $key . '</a> ';
|
||||
}
|
||||
|
||||
|
||||
|
||||
print "</div>";
|
||||
|
||||
print "<footer class='text-center'>";
|
||||
print "<button dojoType='dijit.form.Button'
|
||||
onclick=\"return CommonDialogs.closeInfoBox()\">".
|
||||
__('Close this window')."</button>";
|
||||
print "</footer>";
|
||||
|
||||
}
|
||||
|
||||
function generatedFeed() {
|
||||
|
||||
$this->params = explode(":", $this->param, 3);
|
||||
$feed_id = $this->params[0];
|
||||
$is_cat = (bool) $this->params[1];
|
||||
|
||||
$key = get_feed_access_key($feed_id, $is_cat);
|
||||
|
||||
$url_path = htmlspecialchars($this->params[2]) . "&key=" . $key;
|
||||
|
||||
$feed_title = Feeds::getFeedTitle($feed_id, $is_cat);
|
||||
|
||||
print "<header>".T_sprintf("%s can be accessed via the following secret URL:", $feed_title)."</header>";
|
||||
|
||||
print "<section>";
|
||||
print "<div class='panel text-center'>";
|
||||
print "<a id='gen_feed_url' href='$url_path' target='_blank'>$url_path</a>";
|
||||
print "</div>";
|
||||
print "</section>";
|
||||
|
||||
print "<footer>";
|
||||
|
||||
print "<button dojoType='dijit.form.Button' style='float : left' class='alt-info' onclick='window.open(\"https://tt-rss.org/wiki/GeneratedFeeds\")'>
|
||||
<i class='material-icons'>help</i> ".__("More info...")."</button>";
|
||||
|
||||
print "<button dojoType='dijit.form.Button' onclick=\"return CommonDialogs.genUrlChangeKey('$feed_id', '$is_cat')\">".
|
||||
__('Generate new URL')."</button> ";
|
||||
|
||||
print "<button dojoType='dijit.form.Button' onclick=\"return CommonDialogs.closeInfoBox()\">".
|
||||
__('Close this window')."</button>";
|
||||
|
||||
print "</footer>";
|
||||
|
||||
//return;
|
||||
}
|
||||
|
||||
function defaultPasswordWarning() {
|
||||
|
||||
print_warning(__("You are using default tt-rss password. Please change it in the Preferences (Personal data / Authentication)."));
|
||||
|
||||
print "<footer class='text-center'>";
|
||||
print "<button dojoType='dijit.form.Button' onclick=\"document.location.href = 'prefs.php'\">".
|
||||
__('Open Preferences')."</button> ";
|
||||
print "<button dojoType='dijit.form.Button'
|
||||
onclick=\"return CommonDialogs.closeInfoBox()\">".
|
||||
__('Close this window')."</button>";
|
||||
print "</footeer>";
|
||||
}
|
||||
}
|
10
classes/feedenclosure.php
Normal file
10
classes/feedenclosure.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
class FeedEnclosure {
|
||||
public $link;
|
||||
public $type;
|
||||
public $length;
|
||||
public $title;
|
||||
public $height;
|
||||
public $width;
|
||||
}
|
||||
|
16
classes/feeditem.php
Normal file
16
classes/feeditem.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
abstract class FeedItem {
|
||||
abstract function get_id();
|
||||
abstract function get_date();
|
||||
abstract function get_link();
|
||||
abstract function get_title();
|
||||
abstract function get_description();
|
||||
abstract function get_content();
|
||||
abstract function get_comments_url();
|
||||
abstract function get_comments_count();
|
||||
abstract function get_categories();
|
||||
abstract function get_enclosures();
|
||||
abstract function get_author();
|
||||
abstract function get_language();
|
||||
}
|
||||
|
160
classes/feeditem/atom.php
Executable file
160
classes/feeditem/atom.php
Executable file
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
class FeedItem_Atom extends FeedItem_Common {
|
||||
const NS_XML = "http://www.w3.org/XML/1998/namespace";
|
||||
|
||||
function get_id() {
|
||||
$id = $this->elem->getElementsByTagName("id")->item(0);
|
||||
|
||||
if ($id) {
|
||||
return $id->nodeValue;
|
||||
} else {
|
||||
return clean($this->get_link());
|
||||
}
|
||||
}
|
||||
|
||||
function get_date() {
|
||||
$updated = $this->elem->getElementsByTagName("updated")->item(0);
|
||||
|
||||
if ($updated) {
|
||||
return strtotime($updated->nodeValue);
|
||||
}
|
||||
|
||||
$published = $this->elem->getElementsByTagName("published")->item(0);
|
||||
|
||||
if ($published) {
|
||||
return strtotime($published->nodeValue);
|
||||
}
|
||||
|
||||
$date = $this->xpath->query("dc:date", $this->elem)->item(0);
|
||||
|
||||
if ($date) {
|
||||
return strtotime($date->nodeValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function get_link() {
|
||||
$links = $this->elem->getElementsByTagName("link");
|
||||
|
||||
foreach ($links as $link) {
|
||||
if ($link && $link->hasAttribute("href") &&
|
||||
(!$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, clean(trim($link->getAttribute("href"))));
|
||||
else
|
||||
return clean(trim($link->getAttribute("href")));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function get_title() {
|
||||
$title = $this->elem->getElementsByTagName("title")->item(0);
|
||||
|
||||
if ($title) {
|
||||
return clean(trim($title->nodeValue));
|
||||
}
|
||||
}
|
||||
|
||||
function get_content() {
|
||||
$content = $this->elem->getElementsByTagName("content")->item(0);
|
||||
|
||||
if ($content) {
|
||||
if ($content->hasAttribute('type')) {
|
||||
if ($content->getAttribute('type') == 'xhtml') {
|
||||
for ($i = 0; $i < $content->childNodes->length; $i++) {
|
||||
$child = $content->childNodes->item($i);
|
||||
|
||||
if ($child->hasChildNodes()) {
|
||||
return $this->doc->saveHTML($child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->subtree_or_text($content);
|
||||
}
|
||||
}
|
||||
|
||||
function get_description() {
|
||||
$content = $this->elem->getElementsByTagName("summary")->item(0);
|
||||
|
||||
if ($content) {
|
||||
if ($content->hasAttribute('type')) {
|
||||
if ($content->getAttribute('type') == 'xhtml') {
|
||||
for ($i = 0; $i < $content->childNodes->length; $i++) {
|
||||
$child = $content->childNodes->item($i);
|
||||
|
||||
if ($child->hasChildNodes()) {
|
||||
return $this->doc->saveHTML($child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->subtree_or_text($content);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function get_categories() {
|
||||
$categories = $this->elem->getElementsByTagName("category");
|
||||
$cats = array();
|
||||
|
||||
foreach ($categories as $cat) {
|
||||
if ($cat->hasAttribute("term"))
|
||||
array_push($cats, trim($cat->getAttribute("term")));
|
||||
}
|
||||
|
||||
$categories = $this->xpath->query("dc:subject", $this->elem);
|
||||
|
||||
foreach ($categories as $cat) {
|
||||
array_push($cats, clean(trim($cat->nodeValue)));
|
||||
}
|
||||
|
||||
return $cats;
|
||||
}
|
||||
|
||||
function get_enclosures() {
|
||||
$links = $this->elem->getElementsByTagName("link");
|
||||
|
||||
$encs = array();
|
||||
|
||||
foreach ($links as $link) {
|
||||
if ($link && $link->hasAttribute("href") && $link->hasAttribute("rel")) {
|
||||
if ($link->getAttribute("rel") == "enclosure") {
|
||||
$enc = new FeedEnclosure();
|
||||
|
||||
$enc->type = clean($link->getAttribute("type"));
|
||||
$enc->link = clean($link->getAttribute("href"));
|
||||
$enc->length = clean($link->getAttribute("length"));
|
||||
|
||||
array_push($encs, $enc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$encs = array_merge($encs, parent::get_enclosures());
|
||||
|
||||
return $encs;
|
||||
}
|
||||
|
||||
function get_language() {
|
||||
$lang = $this->elem->getAttributeNS(self::NS_XML, "lang");
|
||||
|
||||
if (!empty($lang)) {
|
||||
return clean($lang);
|
||||
} else {
|
||||
// Fall back to the language declared on the feed, if any.
|
||||
foreach ($this->doc->childNodes as $child) {
|
||||
if (method_exists($child, "getAttributeNS")) {
|
||||
return clean($child->getAttributeNS(self::NS_XML, "lang"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
162
classes/feeditem/common.php
Executable file
162
classes/feeditem/common.php
Executable file
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
abstract class FeedItem_Common extends FeedItem {
|
||||
protected $elem;
|
||||
protected $xpath;
|
||||
protected $doc;
|
||||
|
||||
function __construct($elem, $doc, $xpath) {
|
||||
$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_element() {
|
||||
return $this->elem;
|
||||
}
|
||||
|
||||
function get_author() {
|
||||
$author = $this->elem->getElementsByTagName("author")->item(0);
|
||||
|
||||
if ($author) {
|
||||
$name = $author->getElementsByTagName("name")->item(0);
|
||||
|
||||
if ($name) return clean($name->nodeValue);
|
||||
|
||||
$email = $author->getElementsByTagName("email")->item(0);
|
||||
|
||||
if ($email) return clean($email->nodeValue);
|
||||
|
||||
if ($author->nodeValue)
|
||||
return clean($author->nodeValue);
|
||||
}
|
||||
|
||||
$author = $this->xpath->query("dc:creator", $this->elem)->item(0);
|
||||
|
||||
if ($author) {
|
||||
return clean($author->nodeValue);
|
||||
}
|
||||
}
|
||||
|
||||
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 clean($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 clean($com_url->nodeValue);
|
||||
}
|
||||
|
||||
function get_comments_count() {
|
||||
//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 clean($comments->nodeValue);
|
||||
}
|
||||
}
|
||||
|
||||
// this is common for both Atom and RSS types and deals with various media: elements
|
||||
function get_enclosures() {
|
||||
$encs = [];
|
||||
|
||||
$enclosures = $this->xpath->query("media:content", $this->elem);
|
||||
|
||||
foreach ($enclosures as $enclosure) {
|
||||
$enc = new FeedEnclosure();
|
||||
|
||||
$enc->type = clean($enclosure->getAttribute("type"));
|
||||
$enc->link = clean($enclosure->getAttribute("url"));
|
||||
$enc->length = clean($enclosure->getAttribute("length"));
|
||||
$enc->height = clean($enclosure->getAttribute("height"));
|
||||
$enc->width = clean($enclosure->getAttribute("width"));
|
||||
|
||||
$medium = clean($enclosure->getAttribute("medium"));
|
||||
if (!$enc->type && $medium) {
|
||||
$enc->type = strtolower("$medium/generic");
|
||||
}
|
||||
|
||||
$desc = $this->xpath->query("media:description", $enclosure)->item(0);
|
||||
if ($desc) $enc->title = clean($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 = clean($content->getAttribute("type"));
|
||||
$enc->link = clean($content->getAttribute("url"));
|
||||
$enc->length = clean($content->getAttribute("length"));
|
||||
$enc->height = clean($content->getAttribute("height"));
|
||||
$enc->width = clean($content->getAttribute("width"));
|
||||
|
||||
$medium = clean($content->getAttribute("medium"));
|
||||
if (!$enc->type && $medium) {
|
||||
$enc->type = strtolower("$medium/generic");
|
||||
}
|
||||
|
||||
$desc = $this->xpath->query("media:description", $content)->item(0);
|
||||
if ($desc) {
|
||||
$enc->title = clean($desc->nodeValue);
|
||||
} else {
|
||||
$desc = $this->xpath->query("media:description", $enclosure)->item(0);
|
||||
if ($desc) $enc->title = clean($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 = clean($enclosure->getAttribute("url"));
|
||||
$enc->height = clean($enclosure->getAttribute("height"));
|
||||
$enc->width = clean($enclosure->getAttribute("width"));
|
||||
|
||||
array_push($encs, $enc);
|
||||
}
|
||||
|
||||
return $encs;
|
||||
}
|
||||
|
||||
function count_children($node) {
|
||||
return $node->getElementsByTagName("*")->length;
|
||||
}
|
||||
|
||||
function subtree_or_text($node) {
|
||||
if ($this->count_children($node) == 0) {
|
||||
return $node->nodeValue;
|
||||
} else {
|
||||
return $node->c14n();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
147
classes/feeditem/rss.php
Executable file
147
classes/feeditem/rss.php
Executable file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
class FeedItem_RSS extends FeedItem_Common {
|
||||
function get_id() {
|
||||
$id = $this->elem->getElementsByTagName("guid")->item(0);
|
||||
|
||||
if ($id) {
|
||||
return clean($id->nodeValue);
|
||||
} else {
|
||||
return clean($this->get_link());
|
||||
}
|
||||
}
|
||||
|
||||
function get_date() {
|
||||
$pubDate = $this->elem->getElementsByTagName("pubDate")->item(0);
|
||||
|
||||
if ($pubDate) {
|
||||
return strtotime($pubDate->nodeValue);
|
||||
}
|
||||
|
||||
$date = $this->xpath->query("dc:date", $this->elem)->item(0);
|
||||
|
||||
if ($date) {
|
||||
return strtotime($date->nodeValue);
|
||||
}
|
||||
}
|
||||
|
||||
function get_link() {
|
||||
$links = $this->xpath->query("atom:link", $this->elem);
|
||||
|
||||
foreach ($links as $link) {
|
||||
if ($link && $link->hasAttribute("href") &&
|
||||
(!$link->hasAttribute("rel")
|
||||
|| $link->getAttribute("rel") == "alternate"
|
||||
|| $link->getAttribute("rel") == "standout")) {
|
||||
|
||||
return clean(trim($link->getAttribute("href")));
|
||||
}
|
||||
}
|
||||
|
||||
$link = $this->elem->getElementsByTagName("guid")->item(0);
|
||||
|
||||
if ($link && $link->hasAttributes() && $link->getAttribute("isPermaLink") == "true") {
|
||||
return clean(trim($link->nodeValue));
|
||||
}
|
||||
|
||||
$link = $this->elem->getElementsByTagName("link")->item(0);
|
||||
|
||||
if ($link) {
|
||||
return clean(trim($link->nodeValue));
|
||||
}
|
||||
}
|
||||
|
||||
function get_title() {
|
||||
$title = $this->xpath->query("title", $this->elem)->item(0);
|
||||
|
||||
if ($title) {
|
||||
return clean(trim($title->nodeValue));
|
||||
}
|
||||
|
||||
// if the document has a default namespace then querying for
|
||||
// title would fail because of reasons so let's try the old way
|
||||
$title = $this->elem->getElementsByTagName("title")->item(0);
|
||||
|
||||
if ($title) {
|
||||
return clean(trim($title->nodeValue));
|
||||
}
|
||||
}
|
||||
|
||||
function get_content() {
|
||||
$contentA = $this->xpath->query("content:encoded", $this->elem)->item(0);
|
||||
$contentB = $this->elem->getElementsByTagName("description")->item(0);
|
||||
|
||||
if ($contentA && !$contentB) {
|
||||
return $this->subtree_or_text($contentA);
|
||||
}
|
||||
|
||||
|
||||
if ($contentB && !$contentA) {
|
||||
return $this->subtree_or_text($contentB);
|
||||
}
|
||||
|
||||
if ($contentA && $contentB) {
|
||||
$resultA = $this->subtree_or_text($contentA);
|
||||
$resultB = $this->subtree_or_text($contentB);
|
||||
|
||||
return mb_strlen($resultA) > mb_strlen($resultB) ? $resultA : $resultB;
|
||||
}
|
||||
}
|
||||
|
||||
function get_description() {
|
||||
$summary = $this->elem->getElementsByTagName("description")->item(0);
|
||||
|
||||
if ($summary) {
|
||||
return $summary->nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
function get_categories() {
|
||||
$categories = $this->elem->getElementsByTagName("category");
|
||||
$cats = array();
|
||||
|
||||
foreach ($categories as $cat) {
|
||||
array_push($cats, trim($cat->nodeValue));
|
||||
}
|
||||
|
||||
$categories = $this->xpath->query("dc:subject", $this->elem);
|
||||
|
||||
foreach ($categories as $cat) {
|
||||
array_push($cats, clean(trim($cat->nodeValue)));
|
||||
}
|
||||
|
||||
return $cats;
|
||||
}
|
||||
|
||||
function get_enclosures() {
|
||||
$enclosures = $this->elem->getElementsByTagName("enclosure");
|
||||
|
||||
$encs = array();
|
||||
|
||||
foreach ($enclosures as $enclosure) {
|
||||
$enc = new FeedEnclosure();
|
||||
|
||||
$enc->type = clean($enclosure->getAttribute("type"));
|
||||
$enc->link = clean($enclosure->getAttribute("url"));
|
||||
$enc->length = clean($enclosure->getAttribute("length"));
|
||||
$enc->height = clean($enclosure->getAttribute("height"));
|
||||
$enc->width = clean($enclosure->getAttribute("width"));
|
||||
|
||||
array_push($encs, $enc);
|
||||
}
|
||||
|
||||
$encs = array_merge($encs, parent::get_enclosures());
|
||||
|
||||
return $encs;
|
||||
}
|
||||
|
||||
function get_language() {
|
||||
$languages = $this->doc->getElementsByTagName('language');
|
||||
|
||||
if (count($languages) == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return clean($languages[0]->textContent);
|
||||
}
|
||||
|
||||
}
|
286
classes/feedparser.php
Normal file
286
classes/feedparser.php
Normal file
@ -0,0 +1,286 @@
|
||||
<?php
|
||||
class FeedParser {
|
||||
private $doc;
|
||||
private $error;
|
||||
private $libxml_errors = array();
|
||||
private $items;
|
||||
private $link;
|
||||
private $title;
|
||||
private $type;
|
||||
private $xpath;
|
||||
|
||||
const FEED_RDF = 0;
|
||||
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) {
|
||||
|
||||
$encoding = strtolower($matches[2]);
|
||||
|
||||
if (in_array($encoding, array_map('strtolower', mb_list_encodings())))
|
||||
$data = mb_convert_encoding($data, 'UTF-8', $encoding);
|
||||
|
||||
$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();
|
||||
|
||||
// libxml compiled without iconv?
|
||||
if ($error && $error->code == 32) {
|
||||
$data = $this->normalize_encoding($data);
|
||||
|
||||
if ($data) {
|
||||
libxml_clear_errors();
|
||||
|
||||
$this->doc = new DOMDocument();
|
||||
$this->doc->loadXML($data);
|
||||
|
||||
$error = libxml_get_last_error();
|
||||
}
|
||||
}
|
||||
|
||||
// some terrible invalid unicode entity?
|
||||
if ($error) {
|
||||
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();
|
||||
}
|
||||
|
||||
function init() {
|
||||
$root = $this->doc->firstChild;
|
||||
$xpath = new DOMXPath($this->doc);
|
||||
$xpath->registerNamespace('atom', 'http://www.w3.org/2005/Atom');
|
||||
$xpath->registerNamespace('atom03', 'http://purl.org/atom/ns#');
|
||||
$xpath->registerNamespace('media', 'http://search.yahoo.com/mrss/');
|
||||
$xpath->registerNamespace('rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
|
||||
$xpath->registerNamespace('slash', 'http://purl.org/rss/1.0/modules/slash/');
|
||||
$xpath->registerNamespace('dc', 'http://purl.org/dc/elements/1.1/');
|
||||
$xpath->registerNamespace('content', 'http://purl.org/rss/1.0/modules/content/');
|
||||
$xpath->registerNamespace('thread', 'http://purl.org/syndication/thread/1.0');
|
||||
|
||||
$this->xpath = $xpath;
|
||||
|
||||
$root = $xpath->query("(//atom03:feed|//atom:feed|//channel|//rdf:rdf|//rdf:RDF)");
|
||||
|
||||
if ($root && $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":
|
||||
case "atom:feed":
|
||||
$this->type = $this::FEED_ATOM;
|
||||
break;
|
||||
default:
|
||||
if( !isset($this->error) ){
|
||||
$this->error = "Unknown/unsupported feed type";
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($this->type) {
|
||||
case $this::FEED_ATOM:
|
||||
|
||||
$title = $xpath->query("//atom:feed/atom:title")->item(0);
|
||||
|
||||
if (!$title)
|
||||
$title = $xpath->query("//atom03:feed/atom03:title")->item(0);
|
||||
|
||||
|
||||
if ($title) {
|
||||
$this->title = $title->nodeValue;
|
||||
}
|
||||
|
||||
$link = $xpath->query("//atom:feed/atom:link[not(@rel)]")->item(0);
|
||||
|
||||
if (!$link)
|
||||
$link = $xpath->query("//atom:feed/atom:link[@rel='alternate']")->item(0);
|
||||
|
||||
if (!$link)
|
||||
$link = $xpath->query("//atom03:feed/atom03:link[not(@rel)]")->item(0);
|
||||
|
||||
if (!$link)
|
||||
$link = $xpath->query("//atom03:feed/atom03:link[@rel='alternate']")->item(0);
|
||||
|
||||
if ($link && $link->hasAttributes()) {
|
||||
$this->link = $link->getAttribute("href");
|
||||
}
|
||||
|
||||
$articles = $xpath->query("//atom:entry");
|
||||
|
||||
if (!$articles || $articles->length == 0)
|
||||
$articles = $xpath->query("//atom03:entry");
|
||||
|
||||
foreach ($articles as $article) {
|
||||
array_push($this->items, new FeedItem_Atom($article, $this->doc, $this->xpath));
|
||||
}
|
||||
|
||||
break;
|
||||
case $this::FEED_RSS:
|
||||
$title = $xpath->query("//channel/title")->item(0);
|
||||
|
||||
if ($title) {
|
||||
$this->title = $title->nodeValue;
|
||||
}
|
||||
|
||||
$link = $xpath->query("//channel/link")->item(0);
|
||||
|
||||
if ($link) {
|
||||
if ($link->getAttribute("href"))
|
||||
$this->link = $link->getAttribute("href");
|
||||
else if ($link->nodeValue)
|
||||
$this->link = $link->nodeValue;
|
||||
}
|
||||
|
||||
$articles = $xpath->query("//channel/item");
|
||||
|
||||
foreach ($articles as $article) {
|
||||
array_push($this->items, new FeedItem_RSS($article, $this->doc, $this->xpath));
|
||||
}
|
||||
|
||||
break;
|
||||
case $this::FEED_RDF:
|
||||
$xpath->registerNamespace('rssfake', 'http://purl.org/rss/1.0/');
|
||||
|
||||
$title = $xpath->query("//rssfake:channel/rssfake:title")->item(0);
|
||||
|
||||
if ($title) {
|
||||
$this->title = $title->nodeValue;
|
||||
}
|
||||
|
||||
$link = $xpath->query("//rssfake:channel/rssfake:link")->item(0);
|
||||
|
||||
if ($link) {
|
||||
$this->link = $link->nodeValue;
|
||||
}
|
||||
|
||||
$articles = $xpath->query("//rssfake:item");
|
||||
|
||||
foreach ($articles as $article) {
|
||||
array_push($this->items, new FeedItem_RSS($article, $this->doc, $this->xpath));
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function format_error($error) {
|
||||
if ($error) {
|
||||
return sprintf("LibXML error %s at line %d (column %d): %s",
|
||||
$error->code, $error->line, $error->column,
|
||||
$error->message);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function error() {
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
function errors() {
|
||||
return $this->libxml_errors;
|
||||
}
|
||||
|
||||
function get_link() {
|
||||
return clean($this->link);
|
||||
}
|
||||
|
||||
function get_title() {
|
||||
return clean($this->title);
|
||||
}
|
||||
|
||||
function get_items() {
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
function get_links($rel) {
|
||||
$rv = array();
|
||||
|
||||
switch ($this->type) {
|
||||
case $this::FEED_ATOM:
|
||||
$links = $this->xpath->query("//atom:feed/atom:link");
|
||||
|
||||
foreach ($links as $link) {
|
||||
if (!$rel || $link->hasAttribute('rel') && $link->getAttribute('rel') == $rel) {
|
||||
array_push($rv, clean(trim($link->getAttribute('href'))));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case $this::FEED_RSS:
|
||||
$links = $this->xpath->query("//atom:link");
|
||||
|
||||
foreach ($links as $link) {
|
||||
if (!$rel || $link->hasAttribute('rel') && $link->getAttribute('rel') == $rel) {
|
||||
array_push($rv, clean(trim($link->getAttribute('href'))));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $rv;
|
||||
}
|
||||
}
|
1887
classes/feeds.php
Executable file
1887
classes/feeds.php
Executable file
File diff suppressed because it is too large
Load Diff
23
classes/handler.php
Normal file
23
classes/handler.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
class Handler implements IHandler {
|
||||
protected $pdo;
|
||||
protected $args;
|
||||
|
||||
function __construct($args) {
|
||||
$this->pdo = Db::pdo();
|
||||
$this->args = $args;
|
||||
}
|
||||
|
||||
function csrf_ignore($method) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function before($method) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function after() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
7
classes/handler/protected.php
Normal file
7
classes/handler/protected.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
class Handler_Protected extends Handler {
|
||||
|
||||
function before($method) {
|
||||
return parent::before($method) && $_SESSION['uid'];
|
||||
}
|
||||
}
|
1275
classes/handler/public.php
Executable file
1275
classes/handler/public.php
Executable file
File diff suppressed because it is too large
Load Diff
4
classes/iauthmodule.php
Normal file
4
classes/iauthmodule.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php
|
||||
interface IAuthModule {
|
||||
function authenticate($login, $password);
|
||||
}
|
13
classes/idb.php
Normal file
13
classes/idb.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
interface IDb {
|
||||
function connect($host, $user, $pass, $db, $port);
|
||||
function escape_string($s, $strip_tags = true);
|
||||
function query($query, $die_on_error = true);
|
||||
function fetch_assoc($result);
|
||||
function num_rows($result);
|
||||
function fetch_result($result, $row, $param);
|
||||
function close();
|
||||
function affected_rows($result);
|
||||
function last_error();
|
||||
function last_query_error();
|
||||
}
|
6
classes/ihandler.php
Normal file
6
classes/ihandler.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
interface IHandler {
|
||||
function csrf_ignore($method);
|
||||
function before($method);
|
||||
function after();
|
||||
}
|
205
classes/labels.php
Normal file
205
classes/labels.php
Normal file
@ -0,0 +1,205 @@
|
||||
<?php
|
||||
class Labels
|
||||
{
|
||||
static function label_to_feed_id($label) {
|
||||
return LABEL_BASE_INDEX - 1 - abs($label);
|
||||
}
|
||||
|
||||
static function feed_to_label_id($feed) {
|
||||
return LABEL_BASE_INDEX - 1 + abs($feed);
|
||||
}
|
||||
|
||||
static function find_id($label, $owner_uid) {
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT id FROM ttrss_labels2 WHERE caption = ?
|
||||
AND owner_uid = ? LIMIT 1");
|
||||
$sth->execute([$label, $owner_uid]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
return $row['id'];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static function find_caption($label, $owner_uid) {
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT caption FROM ttrss_labels2 WHERE id = ?
|
||||
AND owner_uid = ? LIMIT 1");
|
||||
$sth->execute([$label, $owner_uid]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
return $row['caption'];
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
static function get_all_labels($owner_uid) {
|
||||
$rv = array();
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT id, fg_color, bg_color, caption FROM ttrss_labels2
|
||||
WHERE owner_uid = ? ORDER BY caption");
|
||||
$sth->execute([$owner_uid]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
array_push($rv, $line);
|
||||
}
|
||||
|
||||
return $rv;
|
||||
}
|
||||
|
||||
static function update_cache($owner_uid, $id, $labels = false, $force = false) {
|
||||
$pdo = Db::pdo();
|
||||
|
||||
if ($force)
|
||||
Labels::clear_cache($id);
|
||||
|
||||
if (!$labels)
|
||||
$labels = Article::get_article_labels($id);
|
||||
|
||||
$labels = json_encode($labels);
|
||||
|
||||
$sth = $pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
label_cache = ? WHERE ref_id = ? AND owner_uid = ?");
|
||||
$sth->execute([$labels, $id, $owner_uid]);
|
||||
|
||||
}
|
||||
|
||||
static function clear_cache($id) {
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
label_cache = '' WHERE ref_id = ?");
|
||||
$sth->execute([$id]);
|
||||
|
||||
}
|
||||
|
||||
static function remove_article($id, $label, $owner_uid) {
|
||||
|
||||
$label_id = Labels::find_id($label, $owner_uid);
|
||||
|
||||
if (!$label_id) return;
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("DELETE FROM ttrss_user_labels2
|
||||
WHERE
|
||||
label_id = ? AND
|
||||
article_id = ?");
|
||||
|
||||
$sth->execute([$label_id, $id]);
|
||||
|
||||
Labels::clear_cache($id);
|
||||
}
|
||||
|
||||
static function add_article($id, $label, $owner_uid) {
|
||||
|
||||
$label_id = Labels::find_id($label, $owner_uid);
|
||||
|
||||
if (!$label_id) return;
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT
|
||||
article_id FROM ttrss_labels2, ttrss_user_labels2
|
||||
WHERE
|
||||
label_id = id AND
|
||||
label_id = ? AND
|
||||
article_id = ? AND owner_uid = ?
|
||||
LIMIT 1");
|
||||
|
||||
$sth->execute([$label_id, $id, $owner_uid]);
|
||||
|
||||
if (!$sth->fetch()) {
|
||||
$sth = $pdo->prepare("INSERT INTO ttrss_user_labels2
|
||||
(label_id, article_id) VALUES (?, ?)");
|
||||
|
||||
$sth->execute([$label_id, $id]);
|
||||
}
|
||||
|
||||
Labels::clear_cache($id);
|
||||
|
||||
}
|
||||
|
||||
static function remove($id, $owner_uid) {
|
||||
if (!$owner_uid) $owner_uid = $_SESSION["uid"];
|
||||
|
||||
$pdo = Db::pdo();
|
||||
$tr_in_progress = false;
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
} catch (Exception $e) {
|
||||
$tr_in_progress = true;
|
||||
}
|
||||
|
||||
$sth = $pdo->prepare("SELECT caption FROM ttrss_labels2
|
||||
WHERE id = ?");
|
||||
$sth->execute([$id]);
|
||||
|
||||
$row = $sth->fetch();
|
||||
$caption = $row['caption'];
|
||||
|
||||
$sth = $pdo->prepare("DELETE FROM ttrss_labels2 WHERE id = ?
|
||||
AND owner_uid = ?");
|
||||
$sth->execute([$id, $owner_uid]);
|
||||
|
||||
if ($sth->rowCount() != 0 && $caption) {
|
||||
|
||||
/* Remove access key for the label */
|
||||
|
||||
$ext_id = LABEL_BASE_INDEX - 1 - $id;
|
||||
|
||||
$sth = $pdo->prepare("DELETE FROM ttrss_access_keys WHERE
|
||||
feed_id = ? AND owner_uid = ?");
|
||||
$sth->execute([$ext_id, $owner_uid]);
|
||||
|
||||
/* Remove cached data */
|
||||
|
||||
$sth = $pdo->prepare("UPDATE ttrss_user_entries SET label_cache = ''
|
||||
WHERE owner_uid = ?");
|
||||
$sth->execute([$owner_uid]);
|
||||
|
||||
}
|
||||
|
||||
if (!$tr_in_progress) $pdo->commit();
|
||||
}
|
||||
|
||||
static function create($caption, $fg_color = '', $bg_color = '', $owner_uid = false) {
|
||||
|
||||
if (!$owner_uid) $owner_uid = $_SESSION['uid'];
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$tr_in_progress = false;
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
} catch (Exception $e) {
|
||||
$tr_in_progress = true;
|
||||
}
|
||||
|
||||
$sth = $pdo->prepare("SELECT id FROM ttrss_labels2
|
||||
WHERE caption = ? AND owner_uid = ?");
|
||||
$sth->execute([$caption, $owner_uid]);
|
||||
|
||||
if (!$sth->fetch()) {
|
||||
$sth = $pdo->prepare("INSERT INTO ttrss_labels2
|
||||
(caption,owner_uid,fg_color,bg_color) VALUES (?, ?, ?, ?)");
|
||||
|
||||
$sth->execute([$caption, $owner_uid, $fg_color, $bg_color]);
|
||||
|
||||
$result = $sth->rowCount();
|
||||
}
|
||||
|
||||
if (!$tr_in_progress) $pdo->commit();
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
67
classes/logger.php
Executable file
67
classes/logger.php
Executable file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
class Logger {
|
||||
private static $instance;
|
||||
private $adapter;
|
||||
|
||||
public static $errornames = array(
|
||||
1 => 'E_ERROR',
|
||||
2 => 'E_WARNING',
|
||||
4 => 'E_PARSE',
|
||||
8 => 'E_NOTICE',
|
||||
16 => 'E_CORE_ERROR',
|
||||
32 => 'E_CORE_WARNING',
|
||||
64 => 'E_COMPILE_ERROR',
|
||||
128 => 'E_COMPILE_WARNING',
|
||||
256 => 'E_USER_ERROR',
|
||||
512 => 'E_USER_WARNING',
|
||||
1024 => 'E_USER_NOTICE',
|
||||
2048 => 'E_STRICT',
|
||||
4096 => 'E_RECOVERABLE_ERROR',
|
||||
8192 => 'E_DEPRECATED',
|
||||
16384 => 'E_USER_DEPRECATED',
|
||||
32767 => 'E_ALL');
|
||||
|
||||
function log_error($errno, $errstr, $file, $line, $context) {
|
||||
if ($errno == E_NOTICE) return false;
|
||||
|
||||
if ($this->adapter)
|
||||
return $this->adapter->log_error($errno, $errstr, $file, $line, $context);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
function log($string, $context = "") {
|
||||
if ($this->adapter)
|
||||
return $this->adapter->log_error(E_USER_NOTICE, $string, '', 0, $context);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private function __clone() {
|
||||
//
|
||||
}
|
||||
|
||||
function __construct() {
|
||||
switch (LOG_DESTINATION) {
|
||||
case "sql":
|
||||
$this->adapter = new Logger_SQL();
|
||||
break;
|
||||
case "syslog":
|
||||
$this->adapter = new Logger_Syslog();
|
||||
break;
|
||||
case "stdout":
|
||||
$this->adapter = new Logger_Stdout();
|
||||
break;
|
||||
default:
|
||||
$this->adapter = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function get() {
|
||||
if (self::$instance == null)
|
||||
self::$instance = new self();
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
}
|
29
classes/logger/sql.php
Executable file
29
classes/logger/sql.php
Executable file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
class Logger_SQL {
|
||||
|
||||
private $pdo;
|
||||
|
||||
function log_error($errno, $errstr, $file, $line, $context) {
|
||||
|
||||
// separate PDO connection object is used for logging
|
||||
if (!$this->pdo) $this->pdo = Db::instance()->pdo_connect();
|
||||
|
||||
if ($this->pdo && get_schema_version() > 117) {
|
||||
|
||||
$owner_uid = $_SESSION["uid"] ? $_SESSION["uid"] : null;
|
||||
|
||||
if (DB_TYPE == "mysql")
|
||||
$context = substr($context, 0, 65534);
|
||||
|
||||
$sth = $this->pdo->prepare("INSERT INTO ttrss_error_log
|
||||
(errno, errstr, filename, lineno, context, owner_uid, created_at) VALUES
|
||||
(?, ?, ?, ?, ?, ?, NOW())");
|
||||
$sth->execute([$errno, $errstr, $file, $line, $context, $owner_uid]);
|
||||
|
||||
return $sth->rowCount();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
33
classes/logger/stdout.php
Normal file
33
classes/logger/stdout.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
class Logger_Stdout {
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
function log_error($errno, $errstr, $file, $line, $context) {
|
||||
|
||||
switch ($errno) {
|
||||
case E_ERROR:
|
||||
case E_PARSE:
|
||||
case E_CORE_ERROR:
|
||||
case E_COMPILE_ERROR:
|
||||
case E_USER_ERROR:
|
||||
$priority = LOG_ERR;
|
||||
break;
|
||||
case E_WARNING:
|
||||
case E_CORE_WARNING:
|
||||
case E_COMPILE_WARNING:
|
||||
case E_USER_WARNING:
|
||||
$priority = LOG_WARNING;
|
||||
break;
|
||||
default:
|
||||
$priority = LOG_INFO;
|
||||
}
|
||||
|
||||
$errname = Logger::$errornames[$errno] . " ($errno)";
|
||||
|
||||
print "[EEE] $priority $errname ($file:$line) $errstr\n";
|
||||
|
||||
}
|
||||
|
||||
}
|
33
classes/logger/syslog.php
Normal file
33
classes/logger/syslog.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
class Logger_Syslog {
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
function log_error($errno, $errstr, $file, $line, $context) {
|
||||
|
||||
switch ($errno) {
|
||||
case E_ERROR:
|
||||
case E_PARSE:
|
||||
case E_CORE_ERROR:
|
||||
case E_COMPILE_ERROR:
|
||||
case E_USER_ERROR:
|
||||
$priority = LOG_ERR;
|
||||
break;
|
||||
case E_WARNING:
|
||||
case E_CORE_WARNING:
|
||||
case E_COMPILE_WARNING:
|
||||
case E_USER_WARNING:
|
||||
$priority = LOG_WARNING;
|
||||
break;
|
||||
default:
|
||||
$priority = LOG_INFO;
|
||||
}
|
||||
|
||||
$errname = Logger::$errornames[$errno] . " ($errno)";
|
||||
|
||||
syslog($priority, "[tt-rss] $errname ($file:$line) $errstr");
|
||||
|
||||
}
|
||||
|
||||
}
|
53
classes/mailer.php
Normal file
53
classes/mailer.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
class Mailer {
|
||||
// TODO: support HTML mail (i.e. MIME messages)
|
||||
|
||||
private $last_error = "Unable to send mail: check local configuration.";
|
||||
|
||||
function mail($params) {
|
||||
|
||||
$to_name = $params["to_name"];
|
||||
$to_address = $params["to_address"];
|
||||
$subject = $params["subject"];
|
||||
$message = $params["message"];
|
||||
$message_html = $params["message_html"];
|
||||
$from_name = $params["from_name"] ? $params["from_name"] : SMTP_FROM_NAME;
|
||||
$from_address = $params["from_address"] ? $params["from_address"] : SMTP_FROM_ADDRESS;
|
||||
|
||||
$additional_headers = $params["headers"] ? $params["headers"] : [];
|
||||
|
||||
$from_combined = $from_name ? "$from_name <$from_address>" : $from_address;
|
||||
$to_combined = $to_name ? "$to_name <$to_address>" : $to_address;
|
||||
|
||||
if (defined('_LOG_SENT_MAIL') && _LOG_SENT_MAIL)
|
||||
Logger::get()->log("Sending mail from $from_combined to $to_combined [$subject]: $message");
|
||||
|
||||
// HOOK_SEND_MAIL plugin instructions:
|
||||
// 1. return 1 or true if mail is handled
|
||||
// 2. return -1 if there's been a fatal error and no further action is allowed
|
||||
// 3. any other return value will allow cycling to the next handler and, eventually, to default mail() function
|
||||
// 4. set error message if needed via passed Mailer instance function set_error()
|
||||
|
||||
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEND_MAIL) as $p) {
|
||||
$rc = $p->hook_send_mail($this, $params);
|
||||
|
||||
if ($rc == 1)
|
||||
return $rc;
|
||||
|
||||
if ($rc == -1)
|
||||
return 0;
|
||||
}
|
||||
|
||||
$headers = [ "From: $from_combined", "Content-Type: text/plain; charset=UTF-8" ];
|
||||
|
||||
return mail($to_combined, $subject, $message, implode("\r\n", array_merge($headers, $additional_headers)));
|
||||
}
|
||||
|
||||
function set_error($message) {
|
||||
$this->last_error = $message;
|
||||
}
|
||||
|
||||
function error() {
|
||||
return $this->last_error;
|
||||
}
|
||||
}
|
654
classes/opml.php
Normal file
654
classes/opml.php
Normal file
@ -0,0 +1,654 @@
|
||||
<?php
|
||||
class Opml extends Handler_Protected {
|
||||
|
||||
function csrf_ignore($method) {
|
||||
$csrf_ignored = array("export", "import");
|
||||
|
||||
return array_search($method, $csrf_ignored) !== false;
|
||||
}
|
||||
|
||||
function export() {
|
||||
$output_name = "tt-rss_".date("Y-m-d").".opml";
|
||||
$include_settings = $_REQUEST["include_settings"] == "1";
|
||||
$owner_uid = $_SESSION["uid"];
|
||||
|
||||
$rc = $this->opml_export($output_name, $owner_uid, false, $include_settings);
|
||||
|
||||
return $rc;
|
||||
}
|
||||
|
||||
function import() {
|
||||
$owner_uid = $_SESSION["uid"];
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
|
||||
print "<html>
|
||||
<head>
|
||||
".stylesheet_tag("css/default.css")."
|
||||
<title>".__("OPML Utility")."</title>
|
||||
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
|
||||
</head>
|
||||
<body class='claro ttrss_utility'>
|
||||
<h1>".__('OPML Utility')."</h1><div class='content'>";
|
||||
|
||||
add_feed_category("Imported feeds");
|
||||
|
||||
$this->opml_notice(__("Importing OPML..."));
|
||||
|
||||
$this->opml_import($owner_uid);
|
||||
|
||||
print "<br><form method=\"GET\" action=\"prefs.php\">
|
||||
<input type=\"submit\" value=\"".__("Return to preferences")."\">
|
||||
</form>";
|
||||
|
||||
print "</div></body></html>";
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Export
|
||||
|
||||
private function opml_export_category($owner_uid, $cat_id, $hide_private_feeds = false, $include_settings = true) {
|
||||
|
||||
$cat_id = (int) $cat_id;
|
||||
|
||||
if ($hide_private_feeds)
|
||||
$hide_qpart = "(private IS false AND auth_login = '' AND auth_pass = '')";
|
||||
else
|
||||
$hide_qpart = "true";
|
||||
|
||||
$out = "";
|
||||
|
||||
$ttrss_specific_qpart = "";
|
||||
|
||||
if ($cat_id) {
|
||||
$sth = $this->pdo->prepare("SELECT title,order_id
|
||||
FROM ttrss_feed_categories WHERE id = ?
|
||||
AND owner_uid = ?");
|
||||
$sth->execute([$cat_id, $owner_uid]);
|
||||
$row = $sth->fetch();
|
||||
$cat_title = htmlspecialchars($row['title']);
|
||||
|
||||
if ($include_settings) {
|
||||
$order_id = (int)$row["order_id"];
|
||||
$ttrss_specific_qpart = "ttrssSortOrder=\"$order_id\"";
|
||||
}
|
||||
} else {
|
||||
$cat_title = "";
|
||||
}
|
||||
|
||||
if ($cat_title) $out .= "<outline text=\"$cat_title\" $ttrss_specific_qpart>\n";
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id,title
|
||||
FROM ttrss_feed_categories WHERE
|
||||
(parent_cat = :cat OR (:cat = 0 AND parent_cat IS NULL)) AND
|
||||
owner_uid = :uid ORDER BY order_id, title");
|
||||
|
||||
$sth->execute([':cat' => $cat_id, ':uid' => $owner_uid]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
$out .= $this->opml_export_category($owner_uid, $line["id"], $hide_private_feeds, $include_settings);
|
||||
}
|
||||
|
||||
$fsth = $this->pdo->prepare("select title, feed_url, site_url, update_interval, order_id
|
||||
FROM ttrss_feeds WHERE
|
||||
(cat_id = :cat OR (:cat = 0 AND cat_id IS NULL)) AND owner_uid = :uid AND $hide_qpart
|
||||
ORDER BY order_id, title");
|
||||
|
||||
$fsth->execute([':cat' => $cat_id, ':uid' => $owner_uid]);
|
||||
|
||||
while ($fline = $fsth->fetch()) {
|
||||
$title = htmlspecialchars($fline["title"]);
|
||||
$url = htmlspecialchars($fline["feed_url"]);
|
||||
$site_url = htmlspecialchars($fline["site_url"]);
|
||||
|
||||
if ($include_settings) {
|
||||
$update_interval = (int)$fline["update_interval"];
|
||||
$order_id = (int)$fline["order_id"];
|
||||
|
||||
$ttrss_specific_qpart = "ttrssSortOrder=\"$order_id\" ttrssUpdateInterval=\"$update_interval\"";
|
||||
} else {
|
||||
$ttrss_specific_qpart = "";
|
||||
}
|
||||
|
||||
if ($site_url) {
|
||||
$html_url_qpart = "htmlUrl=\"$site_url\"";
|
||||
} else {
|
||||
$html_url_qpart = "";
|
||||
}
|
||||
|
||||
$out .= "<outline type=\"rss\" text=\"$title\" xmlUrl=\"$url\" $ttrss_specific_qpart $html_url_qpart/>\n";
|
||||
}
|
||||
|
||||
if ($cat_title) $out .= "</outline>\n";
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
function opml_export($name, $owner_uid, $hide_private_feeds = false, $include_settings = true) {
|
||||
if (!$owner_uid) return;
|
||||
|
||||
if (!isset($_REQUEST["debug"])) {
|
||||
header("Content-type: application/xml+opml");
|
||||
header("Content-Disposition: attachment; filename=" . $name );
|
||||
} else {
|
||||
header("Content-type: text/xml");
|
||||
}
|
||||
|
||||
$out = "<?xml version=\"1.0\" encoding=\"utf-8\"?".">";
|
||||
|
||||
$out .= "<opml version=\"1.0\">";
|
||||
$out .= "<head>
|
||||
<dateCreated>" . date("r", time()) . "</dateCreated>
|
||||
<title>Tiny Tiny RSS Feed Export</title>
|
||||
</head>";
|
||||
$out .= "<body>";
|
||||
|
||||
$out .= $this->opml_export_category($owner_uid, 0, $hide_private_feeds, $include_settings);
|
||||
|
||||
# export tt-rss settings
|
||||
|
||||
if ($include_settings) {
|
||||
$out .= "<outline text=\"tt-rss-prefs\" schema-version=\"".SCHEMA_VERSION."\">";
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT pref_name, value FROM ttrss_user_prefs WHERE
|
||||
profile IS NULL AND owner_uid = ? ORDER BY pref_name");
|
||||
$sth->execute([$owner_uid]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
$name = $line["pref_name"];
|
||||
$value = htmlspecialchars($line["value"]);
|
||||
|
||||
$out .= "<outline pref-name=\"$name\" value=\"$value\"/>";
|
||||
}
|
||||
|
||||
$out .= "</outline>";
|
||||
|
||||
$out .= "<outline text=\"tt-rss-labels\" schema-version=\"".SCHEMA_VERSION."\">";
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT * FROM ttrss_labels2 WHERE
|
||||
owner_uid = ?");
|
||||
$sth->execute([$owner_uid]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
$name = htmlspecialchars($line['caption']);
|
||||
$fg_color = htmlspecialchars($line['fg_color']);
|
||||
$bg_color = htmlspecialchars($line['bg_color']);
|
||||
|
||||
$out .= "<outline label-name=\"$name\" label-fg-color=\"$fg_color\" label-bg-color=\"$bg_color\"/>";
|
||||
|
||||
}
|
||||
|
||||
$out .= "</outline>";
|
||||
|
||||
$out .= "<outline text=\"tt-rss-filters\" schema-version=\"".SCHEMA_VERSION."\">";
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT * FROM ttrss_filters2
|
||||
WHERE owner_uid = ? ORDER BY id");
|
||||
$sth->execute([$owner_uid]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
$line["rules"] = array();
|
||||
$line["actions"] = array();
|
||||
|
||||
$tmph = $this->pdo->prepare("SELECT * FROM ttrss_filters2_rules
|
||||
WHERE filter_id = ?");
|
||||
$tmph->execute([$line['id']]);
|
||||
|
||||
while ($tmp_line = $tmph->fetch(PDO::FETCH_ASSOC)) {
|
||||
unset($tmp_line["id"]);
|
||||
unset($tmp_line["filter_id"]);
|
||||
|
||||
$cat_filter = $tmp_line["cat_filter"];
|
||||
|
||||
if (!$tmp_line["match_on"]) {
|
||||
if ($cat_filter && $tmp_line["cat_id"] || $tmp_line["feed_id"]) {
|
||||
$tmp_line["feed"] = Feeds::getFeedTitle(
|
||||
$cat_filter ? $tmp_line["cat_id"] : $tmp_line["feed_id"],
|
||||
$cat_filter);
|
||||
} else {
|
||||
$tmp_line["feed"] = "";
|
||||
}
|
||||
} else {
|
||||
$match = [];
|
||||
foreach (json_decode($tmp_line["match_on"], true) as $feed_id) {
|
||||
|
||||
if (strpos($feed_id, "CAT:") === 0) {
|
||||
$feed_id = (int)substr($feed_id, 4);
|
||||
if ($feed_id) {
|
||||
array_push($match, [Feeds::getCategoryTitle($feed_id), true, false]);
|
||||
} else {
|
||||
array_push($match, [0, true, true]);
|
||||
}
|
||||
} else {
|
||||
if ($feed_id) {
|
||||
array_push($match, [Feeds::getFeedTitle((int)$feed_id), false, false]);
|
||||
} else {
|
||||
array_push($match, [0, false, true]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tmp_line["match"] = $match;
|
||||
unset($tmp_line["match_on"]);
|
||||
}
|
||||
|
||||
unset($tmp_line["feed_id"]);
|
||||
unset($tmp_line["cat_id"]);
|
||||
|
||||
array_push($line["rules"], $tmp_line);
|
||||
}
|
||||
|
||||
$tmph = $this->pdo->prepare("SELECT * FROM ttrss_filters2_actions
|
||||
WHERE filter_id = ?");
|
||||
$tmph->execute([$line['id']]);
|
||||
|
||||
while ($tmp_line = $tmph->fetch(PDO::FETCH_ASSOC)) {
|
||||
unset($tmp_line["id"]);
|
||||
unset($tmp_line["filter_id"]);
|
||||
|
||||
array_push($line["actions"], $tmp_line);
|
||||
}
|
||||
|
||||
unset($line["id"]);
|
||||
unset($line["owner_uid"]);
|
||||
$filter = json_encode($line);
|
||||
|
||||
$out .= "<outline filter-type=\"2\"><![CDATA[$filter]]></outline>";
|
||||
|
||||
}
|
||||
|
||||
|
||||
$out .= "</outline>";
|
||||
}
|
||||
|
||||
$out .= "</body></opml>";
|
||||
|
||||
// Format output.
|
||||
$doc = new DOMDocument();
|
||||
$doc->formatOutput = true;
|
||||
$doc->preserveWhiteSpace = false;
|
||||
$doc->loadXML($out);
|
||||
|
||||
$xpath = new DOMXpath($doc);
|
||||
$outlines = $xpath->query("//outline[@title]");
|
||||
|
||||
// cleanup empty categories
|
||||
foreach ($outlines as $node) {
|
||||
if ($node->getElementsByTagName('outline')->length == 0)
|
||||
$node->parentNode->removeChild($node);
|
||||
}
|
||||
|
||||
$res = $doc->saveXML();
|
||||
|
||||
/* // saveXML uses a two-space indent. Change to tabs.
|
||||
$res = preg_replace_callback('/^(?: )+/mu',
|
||||
create_function(
|
||||
'$matches',
|
||||
'return str_repeat("\t", intval(strlen($matches[0])/2));'),
|
||||
$res); */
|
||||
|
||||
print $res;
|
||||
}
|
||||
|
||||
// Import
|
||||
|
||||
private function opml_import_feed($node, $cat_id, $owner_uid) {
|
||||
$attrs = $node->attributes;
|
||||
|
||||
$feed_title = mb_substr($attrs->getNamedItem('text')->nodeValue, 0, 250);
|
||||
if (!$feed_title) $feed_title = mb_substr($attrs->getNamedItem('title')->nodeValue, 0, 250);
|
||||
|
||||
$feed_url = $attrs->getNamedItem('xmlUrl')->nodeValue;
|
||||
if (!$feed_url) $feed_url = $attrs->getNamedItem('xmlURL')->nodeValue;
|
||||
|
||||
$site_url = mb_substr($attrs->getNamedItem('htmlUrl')->nodeValue, 0, 250);
|
||||
|
||||
if ($feed_url) {
|
||||
$sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE
|
||||
feed_url = ? AND owner_uid = ?");
|
||||
$sth->execute([$feed_url, $owner_uid]);
|
||||
|
||||
if (!$feed_title) $feed_title = '[Unknown]';
|
||||
|
||||
if (!$sth->fetch()) {
|
||||
#$this->opml_notice("[FEED] [$feed_title/$feed_url] dst_CAT=$cat_id");
|
||||
$this->opml_notice(T_sprintf("Adding feed: %s", $feed_title == '[Unknown]' ? $feed_url : $feed_title));
|
||||
|
||||
if (!$cat_id) $cat_id = null;
|
||||
|
||||
$update_interval = (int) $attrs->getNamedItem('ttrssUpdateInterval')->nodeValue;
|
||||
if (!$update_interval) $update_interval = 0;
|
||||
|
||||
$order_id = (int) $attrs->getNamedItem('ttrssSortOrder')->nodeValue;
|
||||
if (!$order_id) $order_id = 0;
|
||||
|
||||
$sth = $this->pdo->prepare("INSERT INTO ttrss_feeds
|
||||
(title, feed_url, owner_uid, cat_id, site_url, order_id, update_interval) VALUES
|
||||
(?, ?, ?, ?, ?, ?, ?)");
|
||||
|
||||
$sth->execute([$feed_title, $feed_url, $owner_uid, $cat_id, $site_url, $order_id, $update_interval]);
|
||||
|
||||
} else {
|
||||
$this->opml_notice(T_sprintf("Duplicate feed: %s", $feed_title == '[Unknown]' ? $feed_url : $feed_title));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function opml_import_label($node, $owner_uid) {
|
||||
$attrs = $node->attributes;
|
||||
$label_name = $attrs->getNamedItem('label-name')->nodeValue;
|
||||
|
||||
if ($label_name) {
|
||||
$fg_color = $attrs->getNamedItem('label-fg-color')->nodeValue;
|
||||
$bg_color = $attrs->getNamedItem('label-bg-color')->nodeValue;
|
||||
|
||||
if (!Labels::find_id($label_name, $_SESSION['uid'])) {
|
||||
$this->opml_notice(T_sprintf("Adding label %s", htmlspecialchars($label_name)));
|
||||
Labels::create($label_name, $fg_color, $bg_color, $owner_uid);
|
||||
} else {
|
||||
$this->opml_notice(T_sprintf("Duplicate label: %s", htmlspecialchars($label_name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function opml_import_preference($node) {
|
||||
$attrs = $node->attributes;
|
||||
$pref_name = $attrs->getNamedItem('pref-name')->nodeValue;
|
||||
|
||||
if ($pref_name) {
|
||||
$pref_value = $attrs->getNamedItem('value')->nodeValue;
|
||||
|
||||
$this->opml_notice(T_sprintf("Setting preference key %s to %s",
|
||||
$pref_name, $pref_value));
|
||||
|
||||
set_pref($pref_name, $pref_value);
|
||||
}
|
||||
}
|
||||
|
||||
private function opml_import_filter($node) {
|
||||
$attrs = $node->attributes;
|
||||
|
||||
$filter_type = $attrs->getNamedItem('filter-type')->nodeValue;
|
||||
|
||||
if ($filter_type == '2') {
|
||||
$filter = json_decode($node->nodeValue, true);
|
||||
|
||||
if ($filter) {
|
||||
$match_any_rule = bool_to_sql_bool($filter["match_any_rule"]);
|
||||
$enabled = bool_to_sql_bool($filter["enabled"]);
|
||||
$inverse = bool_to_sql_bool($filter["inverse"]);
|
||||
$title = $filter["title"];
|
||||
|
||||
//print "F: $title, $inverse, $enabled, $match_any_rule";
|
||||
|
||||
$sth = $this->pdo->prepare("INSERT INTO ttrss_filters2 (match_any_rule,enabled,inverse,title,owner_uid)
|
||||
VALUES (?, ?, ?, ?, ?)");
|
||||
|
||||
$sth->execute([$match_any_rule, $enabled, $inverse, $title, $_SESSION['uid']]);
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT MAX(id) AS id FROM ttrss_filters2 WHERE
|
||||
owner_uid = ?");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
|
||||
$row = $sth->fetch();
|
||||
$filter_id = $row['id'];
|
||||
|
||||
if ($filter_id) {
|
||||
$this->opml_notice(T_sprintf("Adding filter %s...", $title));
|
||||
|
||||
foreach ($filter["rules"] as $rule) {
|
||||
$feed_id = null;
|
||||
$cat_id = null;
|
||||
|
||||
if ($rule["match"]) {
|
||||
|
||||
$match_on = [];
|
||||
|
||||
foreach ($rule["match"] as $match) {
|
||||
list ($name, $is_cat, $is_id) = $match;
|
||||
|
||||
if ($is_id) {
|
||||
array_push($match_on, ($is_cat ? "CAT:" : "") . $name);
|
||||
} else {
|
||||
|
||||
if (!$is_cat) {
|
||||
$tsth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
|
||||
WHERE title = ? AND owner_uid = ?");
|
||||
|
||||
$tsth->execute([$name, $_SESSION['uid']]);
|
||||
|
||||
if ($row = $tsth->fetch()) {
|
||||
$match_id = $row['id'];
|
||||
|
||||
array_push($match_on, $match_id);
|
||||
}
|
||||
} else {
|
||||
$tsth = $this->pdo->prepare("SELECT id FROM ttrss_feed_categories
|
||||
WHERE title = ? AND owner_uid = ?");
|
||||
$tsth->execute([$name, $_SESSION['uid']]);
|
||||
|
||||
if ($row = $tsth->fetch()) {
|
||||
$match_id = $row['id'];
|
||||
|
||||
array_push($match_on, "CAT:$match_id");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$reg_exp = $rule["reg_exp"];
|
||||
$filter_type = (int)$rule["filter_type"];
|
||||
$inverse = bool_to_sql_bool($rule["inverse"]);
|
||||
$match_on = json_encode($match_on);
|
||||
|
||||
$usth = $this->pdo->prepare("INSERT INTO ttrss_filters2_rules
|
||||
(feed_id,cat_id,match_on,filter_id,filter_type,reg_exp,cat_filter,inverse)
|
||||
VALUES
|
||||
(NULL, NULL, ?, ?, ?, ?, false, ?)");
|
||||
$usth->execute([$match_on, $filter_id, $filter_type, $reg_exp, $inverse]);
|
||||
|
||||
} else {
|
||||
|
||||
if (!$rule["cat_filter"]) {
|
||||
$tsth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
|
||||
WHERE title = ? AND owner_uid = ?");
|
||||
|
||||
$tsth->execute([$rule['feed'], $_SESSION['uid']]);
|
||||
|
||||
if ($row = $tsth->fetch()) {
|
||||
$feed_id = $row['id'];
|
||||
}
|
||||
} else {
|
||||
$tsth = $this->pdo->prepare("SELECT id FROM ttrss_feed_categories
|
||||
WHERE title = ? AND owner_uid = ?");
|
||||
|
||||
$tsth->execute([$rule['feed'], $_SESSION['uid']]);
|
||||
|
||||
if ($row = $tsth->fetch()) {
|
||||
$feed_id = $row['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$cat_filter = bool_to_sql_bool($rule["cat_filter"]);
|
||||
$reg_exp = $rule["reg_exp"];
|
||||
$filter_type = (int)$rule["filter_type"];
|
||||
$inverse = bool_to_sql_bool($rule["inverse"]);
|
||||
|
||||
$usth = $this->pdo->prepare("INSERT INTO ttrss_filters2_rules
|
||||
(feed_id,cat_id,filter_id,filter_type,reg_exp,cat_filter,inverse)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, ?)");
|
||||
$usth->execute([$feed_id, $cat_id, $filter_id, $filter_type, $reg_exp, $cat_filter, $inverse]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($filter["actions"] as $action) {
|
||||
|
||||
$action_id = (int)$action["action_id"];
|
||||
$action_param = $action["action_param"];
|
||||
|
||||
$usth = $this->pdo->prepare("INSERT INTO ttrss_filters2_actions
|
||||
(filter_id,action_id,action_param)
|
||||
VALUES
|
||||
(?, ?, ?)");
|
||||
$usth->execute([$filter_id, $action_id, $action_param]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function opml_import_category($doc, $root_node, $owner_uid, $parent_id) {
|
||||
$default_cat_id = (int) $this->get_feed_category('Imported feeds', false);
|
||||
|
||||
if ($root_node) {
|
||||
$cat_title = mb_substr($root_node->attributes->getNamedItem('text')->nodeValue, 0, 250);
|
||||
|
||||
if (!$cat_title)
|
||||
$cat_title = mb_substr($root_node->attributes->getNamedItem('title')->nodeValue, 0, 250);
|
||||
|
||||
if (!in_array($cat_title, array("tt-rss-filters", "tt-rss-labels", "tt-rss-prefs"))) {
|
||||
$cat_id = $this->get_feed_category($cat_title, $parent_id);
|
||||
|
||||
if ($cat_id === false) {
|
||||
$order_id = (int) $root_node->attributes->getNamedItem('ttrssSortOrder')->nodeValue;
|
||||
if (!$order_id) $order_id = 0;
|
||||
|
||||
add_feed_category($cat_title, $parent_id, $order_id);
|
||||
$cat_id = $this->get_feed_category($cat_title, $parent_id);
|
||||
}
|
||||
|
||||
} else {
|
||||
$cat_id = 0;
|
||||
}
|
||||
|
||||
$outlines = $root_node->childNodes;
|
||||
|
||||
} else {
|
||||
$xpath = new DOMXpath($doc);
|
||||
$outlines = $xpath->query("//opml/body/outline");
|
||||
|
||||
$cat_id = 0;
|
||||
}
|
||||
|
||||
#$this->opml_notice("[CAT] $cat_title id: $cat_id P_id: $parent_id");
|
||||
$this->opml_notice(T_sprintf("Processing category: %s", $cat_title ? $cat_title : __("Uncategorized")));
|
||||
|
||||
foreach ($outlines as $node) {
|
||||
if ($node->hasAttributes() && strtolower($node->tagName) == "outline") {
|
||||
$attrs = $node->attributes;
|
||||
$node_cat_title = $attrs->getNamedItem('text')->nodeValue;
|
||||
|
||||
if (!$node_cat_title)
|
||||
$node_cat_title = $attrs->getNamedItem('title')->nodeValue;
|
||||
|
||||
$node_feed_url = $attrs->getNamedItem('xmlUrl')->nodeValue;
|
||||
|
||||
if ($node_cat_title && !$node_feed_url) {
|
||||
$this->opml_import_category($doc, $node, $owner_uid, $cat_id);
|
||||
} else {
|
||||
|
||||
if (!$cat_id) {
|
||||
$dst_cat_id = $default_cat_id;
|
||||
} else {
|
||||
$dst_cat_id = $cat_id;
|
||||
}
|
||||
|
||||
switch ($cat_title) {
|
||||
case "tt-rss-prefs":
|
||||
$this->opml_import_preference($node);
|
||||
break;
|
||||
case "tt-rss-labels":
|
||||
$this->opml_import_label($node, $owner_uid);
|
||||
break;
|
||||
case "tt-rss-filters":
|
||||
$this->opml_import_filter($node);
|
||||
break;
|
||||
default:
|
||||
$this->opml_import_feed($node, $dst_cat_id, $owner_uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function opml_import($owner_uid) {
|
||||
if (!$owner_uid) return;
|
||||
|
||||
$doc = false;
|
||||
|
||||
if ($_FILES['opml_file']['error'] != 0) {
|
||||
print_error(T_sprintf("Upload failed with error code %d",
|
||||
$_FILES['opml_file']['error']));
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_uploaded_file($_FILES['opml_file']['tmp_name'])) {
|
||||
$tmp_file = tempnam(CACHE_DIR . '/upload', 'opml');
|
||||
|
||||
$result = move_uploaded_file($_FILES['opml_file']['tmp_name'],
|
||||
$tmp_file);
|
||||
|
||||
if (!$result) {
|
||||
print_error(__("Unable to move uploaded file."));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
print_error(__('Error: please upload OPML file.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_file($tmp_file)) {
|
||||
$doc = new DOMDocument();
|
||||
libxml_disable_entity_loader(false);
|
||||
$doc->load($tmp_file);
|
||||
libxml_disable_entity_loader(true);
|
||||
unlink($tmp_file);
|
||||
} else if (!$doc) {
|
||||
print_error(__('Error: unable to find moved OPML file.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if ($doc) {
|
||||
$this->pdo->beginTransaction();
|
||||
$this->opml_import_category($doc, false, $owner_uid, false);
|
||||
$this->pdo->commit();
|
||||
} else {
|
||||
print_error(__('Error while parsing document.'));
|
||||
}
|
||||
}
|
||||
|
||||
private function opml_notice($msg) {
|
||||
print "$msg<br/>";
|
||||
}
|
||||
|
||||
static function opml_publish_url(){
|
||||
|
||||
$url_path = get_self_url_prefix();
|
||||
$url_path .= "/opml.php?op=publish&key=" .
|
||||
get_feed_access_key('OPML:Publish', false, $_SESSION["uid"]);
|
||||
|
||||
return $url_path;
|
||||
}
|
||||
|
||||
function get_feed_category($feed_cat, $parent_cat_id = false) {
|
||||
|
||||
$parent_cat_id = (int) $parent_cat_id;
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id FROM ttrss_feed_categories
|
||||
WHERE title = :title
|
||||
AND (parent_cat = :parent OR (:parent = 0 AND parent_cat IS NULL))
|
||||
AND owner_uid = :uid");
|
||||
|
||||
$sth->execute([':title' => $feed_cat, ':parent' => $parent_cat_id, ':uid' => $_SESSION['uid']]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
return $row['id'];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
60
classes/plugin.php
Normal file
60
classes/plugin.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
abstract class Plugin {
|
||||
const API_VERSION_COMPAT = 1;
|
||||
|
||||
/** @var PDO */
|
||||
protected $pdo;
|
||||
|
||||
/* @var PluginHost $host */
|
||||
abstract function init($host);
|
||||
|
||||
abstract function about();
|
||||
// return array(1.0, "plugin", "No description", "No author", false);
|
||||
|
||||
function __construct() {
|
||||
$this->pdo = Db::pdo();
|
||||
}
|
||||
|
||||
function flags() {
|
||||
/* associative array, possible keys:
|
||||
needs_curl = boolean
|
||||
*/
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
function is_public_method($method) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function get_js() {
|
||||
return "";
|
||||
}
|
||||
|
||||
function get_prefs_js() {
|
||||
return "";
|
||||
}
|
||||
|
||||
function api_version() {
|
||||
return Plugin::API_VERSION_COMPAT;
|
||||
}
|
||||
|
||||
/* gettext-related helpers */
|
||||
|
||||
function __($msgid) {
|
||||
return _dgettext(PluginHost::object_to_domain($this), $msgid);
|
||||
}
|
||||
|
||||
function _ngettext($singular, $plural, $number) {
|
||||
return _dngettext(PluginHost::object_to_domain($this), $singular, $plural, $number);
|
||||
}
|
||||
|
||||
function T_sprintf() {
|
||||
$args = func_get_args();
|
||||
$msgid = array_shift($args);
|
||||
|
||||
return vsprintf($this->__($msgid), $args);
|
||||
}
|
||||
}
|
20
classes/pluginhandler.php
Normal file
20
classes/pluginhandler.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
class PluginHandler extends Handler_Protected {
|
||||
function csrf_ignore($method) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function catchall($method) {
|
||||
$plugin = PluginHost::getInstance()->get_plugin(clean($_REQUEST["plugin"]));
|
||||
|
||||
if ($plugin) {
|
||||
if (method_exists($plugin, $method)) {
|
||||
$plugin->$method();
|
||||
} else {
|
||||
print error_json(13);
|
||||
}
|
||||
} else {
|
||||
print error_json(14);
|
||||
}
|
||||
}
|
||||
}
|
472
classes/pluginhost.php
Executable file
472
classes/pluginhost.php
Executable file
@ -0,0 +1,472 @@
|
||||
<?php
|
||||
class PluginHost {
|
||||
private $pdo;
|
||||
private $hooks = array();
|
||||
private $plugins = array();
|
||||
private $handlers = array();
|
||||
private $commands = array();
|
||||
private $storage = array();
|
||||
private $feeds = array();
|
||||
private $api_methods = array();
|
||||
private $plugin_actions = array();
|
||||
private $owner_uid;
|
||||
private $last_registered;
|
||||
private static $instance;
|
||||
|
||||
const API_VERSION = 2;
|
||||
|
||||
// Hooks marked with *1 are run in global context and available
|
||||
// to plugins loaded in config.php only
|
||||
|
||||
const HOOK_ARTICLE_BUTTON = 1;
|
||||
const HOOK_ARTICLE_FILTER = 2;
|
||||
const HOOK_PREFS_TAB = 3;
|
||||
const HOOK_PREFS_TAB_SECTION = 4;
|
||||
const HOOK_PREFS_TABS = 5;
|
||||
const HOOK_FEED_PARSED = 6;
|
||||
const HOOK_UPDATE_TASK = 7; // *1
|
||||
const HOOK_AUTH_USER = 8;
|
||||
const HOOK_HOTKEY_MAP = 9;
|
||||
const HOOK_RENDER_ARTICLE = 10;
|
||||
const HOOK_RENDER_ARTICLE_CDM = 11;
|
||||
const HOOK_FEED_FETCHED = 12;
|
||||
const HOOK_SANITIZE = 13;
|
||||
const HOOK_RENDER_ARTICLE_API = 14;
|
||||
const HOOK_TOOLBAR_BUTTON = 15;
|
||||
const HOOK_ACTION_ITEM = 16;
|
||||
const HOOK_HEADLINE_TOOLBAR_BUTTON = 17;
|
||||
const HOOK_HOTKEY_INFO = 18;
|
||||
const HOOK_ARTICLE_LEFT_BUTTON = 19;
|
||||
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; // *1
|
||||
const HOOK_SEARCH = 25;
|
||||
const HOOK_FORMAT_ENCLOSURES = 26;
|
||||
const HOOK_SUBSCRIBE_FEED = 27;
|
||||
const HOOK_HEADLINES_BEFORE = 28;
|
||||
const HOOK_RENDER_ENCLOSURE = 29;
|
||||
const HOOK_ARTICLE_FILTER_ACTION = 30;
|
||||
const HOOK_ARTICLE_EXPORT_FEED = 31;
|
||||
const HOOK_MAIN_TOOLBAR_BUTTON = 32;
|
||||
const HOOK_ENCLOSURE_ENTRY = 33;
|
||||
const HOOK_FORMAT_ARTICLE = 34;
|
||||
const HOOK_FORMAT_ARTICLE_CDM = 35; /* RIP */
|
||||
const HOOK_FEED_BASIC_INFO = 36;
|
||||
const HOOK_SEND_LOCAL_FILE = 37;
|
||||
const HOOK_UNSUBSCRIBE_FEED = 38;
|
||||
const HOOK_SEND_MAIL = 39;
|
||||
const HOOK_FILTER_TRIGGERED = 40;
|
||||
|
||||
const KIND_ALL = 1;
|
||||
const KIND_SYSTEM = 2;
|
||||
const KIND_USER = 3;
|
||||
|
||||
static function object_to_domain($plugin) {
|
||||
return strtolower(get_class($plugin));
|
||||
}
|
||||
|
||||
function __construct() {
|
||||
$this->pdo = Db::pdo();
|
||||
|
||||
$this->storage = array();
|
||||
}
|
||||
|
||||
private function __clone() {
|
||||
//
|
||||
}
|
||||
|
||||
public static function getInstance() {
|
||||
if (self::$instance == null)
|
||||
self::$instance = new self();
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function register_plugin($name, $plugin) {
|
||||
//array_push($this->plugins, $plugin);
|
||||
$this->plugins[$name] = $plugin;
|
||||
}
|
||||
|
||||
// needed for compatibility with API 1
|
||||
function get_link() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function get_dbh() {
|
||||
return Db::get();
|
||||
}
|
||||
|
||||
function get_pdo() {
|
||||
return $this->pdo;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function get_plugin($name) {
|
||||
return $this->plugins[strtolower($name)];
|
||||
}
|
||||
|
||||
function run_hooks($type, $method, $args) {
|
||||
foreach ($this->get_hooks($type) as $hook) {
|
||||
$hook->$method($args);
|
||||
}
|
||||
}
|
||||
|
||||
function add_hook($type, $sender) {
|
||||
if (!is_array($this->hooks[$type])) {
|
||||
$this->hooks[$type] = array();
|
||||
}
|
||||
|
||||
array_push($this->hooks[$type], $sender);
|
||||
}
|
||||
|
||||
function del_hook($type, $sender) {
|
||||
if (is_array($this->hooks[$type])) {
|
||||
$key = array_Search($sender, $this->hooks[$type]);
|
||||
if ($key !== FALSE) {
|
||||
unset($this->hooks[$type][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function get_hooks($type) {
|
||||
if (isset($this->hooks[$type])) {
|
||||
return $this->hooks[$type];
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
function load_all($kind, $owner_uid = false, $skip_init = false) {
|
||||
|
||||
$plugins = array_merge(glob("plugins/*"), glob("data/plugins.local/*"));
|
||||
$plugins = array_filter($plugins, "is_dir");
|
||||
$plugins = array_map("basename", $plugins);
|
||||
|
||||
asort($plugins);
|
||||
|
||||
$this->load(join(",", $plugins), $kind, $owner_uid, $skip_init);
|
||||
}
|
||||
|
||||
function load($classlist, $kind, $owner_uid = false, $skip_init = false) {
|
||||
$plugins = explode(",", $classlist);
|
||||
|
||||
$this->owner_uid = (int) $owner_uid;
|
||||
|
||||
foreach ($plugins as $class) {
|
||||
$class = trim($class);
|
||||
$class_file = strtolower(basename($class));
|
||||
|
||||
if (!is_dir(__DIR__."/../plugins/$class_file") &&
|
||||
!is_dir(__DIR__."/../data/plugins.local/$class_file")) continue;
|
||||
|
||||
// try system plugin directory first
|
||||
$file = __DIR__ . "/../plugins/$class_file/init.php";
|
||||
$vendor_dir = __DIR__ . "/../plugins/$class_file/vendor";
|
||||
|
||||
if (!file_exists($file)) {
|
||||
$file = __DIR__ . "/../data/plugins.local/$class_file/init.php";
|
||||
$vendor_dir = __DIR__ . "/../data/plugins.local/$class_file/vendor";
|
||||
}
|
||||
|
||||
if (!isset($this->plugins[$class])) {
|
||||
if (file_exists($file)) require_once $file;
|
||||
|
||||
if (class_exists($class) && is_subclass_of($class, "Plugin")) {
|
||||
|
||||
// register plugin autoloader if necessary, for namespaced classes ONLY
|
||||
// layout corresponds to tt-rss main /vendor/author/Package/Class.php
|
||||
|
||||
if (file_exists($vendor_dir)) {
|
||||
spl_autoload_register(function($class) use ($vendor_dir) {
|
||||
|
||||
if (strpos($class, '\\') !== FALSE) {
|
||||
list ($namespace, $class_name) = explode('\\', $class, 2);
|
||||
|
||||
if ($namespace && $class_name) {
|
||||
$class_file = "$vendor_dir/$namespace/" . str_replace('\\', '/', $class_name) . ".php";
|
||||
|
||||
if (file_exists($class_file))
|
||||
require_once $class_file;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$plugin = new $class($this);
|
||||
|
||||
$plugin_api = $plugin->api_version();
|
||||
|
||||
if ($plugin_api < PluginHost::API_VERSION) {
|
||||
user_error("Plugin $class is not compatible with current API version (need: " . PluginHost::API_VERSION . ", got: $plugin_api)", E_USER_WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file_exists(dirname($file) . "/locale")) {
|
||||
_bindtextdomain($class, dirname($file) . "/locale");
|
||||
_bind_textdomain_codeset($class, "UTF-8");
|
||||
}
|
||||
|
||||
$this->last_registered = $class;
|
||||
|
||||
switch ($kind) {
|
||||
case $this::KIND_SYSTEM:
|
||||
if ($this->is_system($plugin)) {
|
||||
if (!$skip_init) $plugin->init($this);
|
||||
$this->register_plugin($class, $plugin);
|
||||
}
|
||||
break;
|
||||
case $this::KIND_USER:
|
||||
if (!$this->is_system($plugin)) {
|
||||
if (!$skip_init) $plugin->init($this);
|
||||
$this->register_plugin($class, $plugin);
|
||||
}
|
||||
break;
|
||||
case $this::KIND_ALL:
|
||||
if (!$skip_init) $plugin->init($this);
|
||||
$this->register_plugin($class, $plugin);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function is_system($plugin) {
|
||||
$about = $plugin->about();
|
||||
|
||||
return @$about[3];
|
||||
}
|
||||
|
||||
// only system plugins are allowed to modify routing
|
||||
function add_handler($handler, $method, $sender) {
|
||||
$handler = str_replace("-", "_", strtolower($handler));
|
||||
$method = strtolower($method);
|
||||
|
||||
if ($this->is_system($sender)) {
|
||||
if (!is_array($this->handlers[$handler])) {
|
||||
$this->handlers[$handler] = array();
|
||||
}
|
||||
|
||||
$this->handlers[$handler][$method] = $sender;
|
||||
}
|
||||
}
|
||||
|
||||
function del_handler($handler, $method, $sender) {
|
||||
$handler = str_replace("-", "_", strtolower($handler));
|
||||
$method = strtolower($method);
|
||||
|
||||
if ($this->is_system($sender)) {
|
||||
unset($this->handlers[$handler][$method]);
|
||||
}
|
||||
}
|
||||
|
||||
function lookup_handler($handler, $method) {
|
||||
$handler = str_replace("-", "_", strtolower($handler));
|
||||
$method = strtolower($method);
|
||||
|
||||
if (is_array($this->handlers[$handler])) {
|
||||
if (isset($this->handlers[$handler]["*"])) {
|
||||
return $this->handlers[$handler]["*"];
|
||||
} else {
|
||||
return $this->handlers[$handler][$method];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function add_command($command, $description, $sender, $suffix = "", $arghelp = "") {
|
||||
$command = str_replace("-", "_", strtolower($command));
|
||||
|
||||
$this->commands[$command] = array("description" => $description,
|
||||
"suffix" => $suffix,
|
||||
"arghelp" => $arghelp,
|
||||
"class" => $sender);
|
||||
}
|
||||
|
||||
function del_command($command) {
|
||||
$command = "-" . strtolower($command);
|
||||
|
||||
unset($this->commands[$command]);
|
||||
}
|
||||
|
||||
function lookup_command($command) {
|
||||
$command = "-" . strtolower($command);
|
||||
|
||||
if (is_array($this->commands[$command])) {
|
||||
return $this->commands[$command]["class"];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function get_commands() {
|
||||
return $this->commands;
|
||||
}
|
||||
|
||||
function run_commands($args) {
|
||||
foreach ($this->get_commands() as $command => $data) {
|
||||
if (isset($args[$command])) {
|
||||
$command = str_replace("-", "", $command);
|
||||
$data["class"]->$command($args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function load_data() {
|
||||
if ($this->owner_uid) {
|
||||
$sth = $this->pdo->prepare("SELECT name, content FROM ttrss_plugin_storage
|
||||
WHERE owner_uid = ?");
|
||||
$sth->execute([$this->owner_uid]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
$this->storage[$line["name"]] = unserialize($line["content"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function save_data($plugin) {
|
||||
if ($this->owner_uid) {
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id FROM ttrss_plugin_storage WHERE
|
||||
owner_uid= ? AND name = ?");
|
||||
$sth->execute([$this->owner_uid, $plugin]);
|
||||
|
||||
if (!isset($this->storage[$plugin]))
|
||||
$this->storage[$plugin] = array();
|
||||
|
||||
$content = serialize($this->storage[$plugin]);
|
||||
|
||||
if ($sth->fetch()) {
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_plugin_storage SET content = ?
|
||||
WHERE owner_uid= ? AND name = ?");
|
||||
$sth->execute([(string)$content, $this->owner_uid, $plugin]);
|
||||
|
||||
} else {
|
||||
$sth = $this->pdo->prepare("INSERT INTO ttrss_plugin_storage
|
||||
(name,owner_uid,content) VALUES
|
||||
(?, ?, ?)");
|
||||
$sth->execute([$plugin, $this->owner_uid, (string)$content]);
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
}
|
||||
}
|
||||
|
||||
function set($sender, $name, $value, $sync = true) {
|
||||
$idx = get_class($sender);
|
||||
|
||||
if (!isset($this->storage[$idx]))
|
||||
$this->storage[$idx] = array();
|
||||
|
||||
$this->storage[$idx][$name] = $value;
|
||||
|
||||
if ($sync) $this->save_data(get_class($sender));
|
||||
}
|
||||
|
||||
function get($sender, $name, $default_value = false) {
|
||||
$idx = get_class($sender);
|
||||
|
||||
if (isset($this->storage[$idx][$name])) {
|
||||
return $this->storage[$idx][$name];
|
||||
} else {
|
||||
return $default_value;
|
||||
}
|
||||
}
|
||||
|
||||
function get_all($sender) {
|
||||
$idx = get_class($sender);
|
||||
|
||||
$data = $this->storage[$idx];
|
||||
|
||||
return $data ? $data : [];
|
||||
}
|
||||
|
||||
function clear_data($sender) {
|
||||
if ($this->owner_uid) {
|
||||
$idx = get_class($sender);
|
||||
|
||||
unset($this->storage[$idx]);
|
||||
|
||||
$sth = $this->pdo->prepare("DELETE FROM ttrss_plugin_storage WHERE name = ?
|
||||
AND owner_uid = ?");
|
||||
$sth->execute([$idx, $this->owner_uid]);
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin feed functions are *EXPERIMENTAL*!
|
||||
|
||||
// cat_id: only -1 is supported (Special)
|
||||
function add_feed($cat_id, $title, $icon, $sender) {
|
||||
if (!$this->feeds[$cat_id]) $this->feeds[$cat_id] = array();
|
||||
|
||||
$id = count($this->feeds[$cat_id]);
|
||||
|
||||
array_push($this->feeds[$cat_id],
|
||||
array('id' => $id, 'title' => $title, 'sender' => $sender, 'icon' => $icon));
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
function get_feeds($cat_id) {
|
||||
return $this->feeds[$cat_id];
|
||||
}
|
||||
|
||||
// convert feed_id (e.g. -129) to pfeed_id first
|
||||
function get_feed_handler($pfeed_id) {
|
||||
foreach ($this->feeds as $cat) {
|
||||
foreach ($cat as $feed) {
|
||||
if ($feed['id'] == $pfeed_id) {
|
||||
return $feed['sender'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function pfeed_to_feed_id($label) {
|
||||
return PLUGIN_FEED_BASE_INDEX - 1 - abs($label);
|
||||
}
|
||||
|
||||
static function feed_to_pfeed_id($feed) {
|
||||
return PLUGIN_FEED_BASE_INDEX - 1 + abs($feed);
|
||||
}
|
||||
|
||||
function add_api_method($name, $sender) {
|
||||
if ($this->is_system($sender)) {
|
||||
$this->api_methods[strtolower($name)] = $sender;
|
||||
}
|
||||
}
|
||||
|
||||
function get_api_method($name) {
|
||||
return $this->api_methods[$name];
|
||||
}
|
||||
|
||||
function add_filter_action($sender, $action_name, $action_desc) {
|
||||
$sender_class = get_class($sender);
|
||||
|
||||
if (!isset($this->plugin_actions[$sender_class]))
|
||||
$this->plugin_actions[$sender_class] = array();
|
||||
|
||||
array_push($this->plugin_actions[$sender_class],
|
||||
array("action" => $action_name, "description" => $action_desc, "sender" => $sender));
|
||||
}
|
||||
|
||||
function get_filter_actions() {
|
||||
return $this->plugin_actions;
|
||||
}
|
||||
}
|
1799
classes/pref/feeds.php
Executable file
1799
classes/pref/feeds.php
Executable file
File diff suppressed because it is too large
Load Diff
1251
classes/pref/filters.php
Executable file
1251
classes/pref/filters.php
Executable file
File diff suppressed because it is too large
Load Diff
313
classes/pref/labels.php
Normal file
313
classes/pref/labels.php
Normal file
@ -0,0 +1,313 @@
|
||||
<?php
|
||||
class Pref_Labels extends Handler_Protected {
|
||||
|
||||
function csrf_ignore($method) {
|
||||
$csrf_ignored = array("index", "getlabeltree", "edit");
|
||||
|
||||
return array_search($method, $csrf_ignored) !== false;
|
||||
}
|
||||
|
||||
function edit() {
|
||||
$label_id = clean($_REQUEST['id']);
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT * FROM ttrss_labels2 WHERE
|
||||
id = ? AND owner_uid = ?");
|
||||
$sth->execute([$label_id, $_SESSION['uid']]);
|
||||
|
||||
if ($line = $sth->fetch()) {
|
||||
|
||||
print_hidden("id", "$label_id");
|
||||
print_hidden("op", "pref-labels");
|
||||
print_hidden("method", "save");
|
||||
|
||||
print "<form onsubmit='return false;'>";
|
||||
|
||||
print "<header>".__("Caption")."</header>";
|
||||
|
||||
print "<section>";
|
||||
|
||||
$fg_color = $line['fg_color'];
|
||||
$bg_color = $line['bg_color'] ? $line['bg_color'] : '#fff7d5';
|
||||
|
||||
print "<input style='font-size : 16px; color : $fg_color; background : $bg_color; transition : background 0.1s linear'
|
||||
id='labelEdit_caption' name='caption' dojoType='dijit.form.ValidationTextBox'
|
||||
required='true' value=\"".htmlspecialchars($line['caption'])."\">";
|
||||
|
||||
print "</section>";
|
||||
|
||||
print "<header>" . __("Colors") . "</header>";
|
||||
print "<section>";
|
||||
|
||||
print "<table>";
|
||||
print "<tr><th style='text-align : left'>".__("Foreground:")."</th><th style='text-align : left'>".__("Background:")."</th></tr>";
|
||||
print "<tr><td style='padding-right : 10px'>";
|
||||
|
||||
print "<input dojoType='dijit.form.TextBox'
|
||||
style='display : none' id='labelEdit_fgColor'
|
||||
name='fg_color' value='$fg_color'>";
|
||||
print "<input dojoType='dijit.form.TextBox'
|
||||
style='display : none' id='labelEdit_bgColor'
|
||||
name='bg_color' value='$bg_color'>";
|
||||
|
||||
print "<div dojoType='dijit.ColorPalette'>
|
||||
<script type='dojo/method' event='onChange' args='fg_color'>
|
||||
dijit.byId('labelEdit_fgColor').attr('value', fg_color);
|
||||
dijit.byId('labelEdit_caption').domNode.setStyle({color: fg_color});
|
||||
</script>
|
||||
</div>";
|
||||
|
||||
print "</td><td>";
|
||||
|
||||
print "<div dojoType='dijit.ColorPalette'>
|
||||
<script type='dojo/method' event='onChange' args='bg_color'>
|
||||
dijit.byId('labelEdit_bgColor').attr('value', bg_color);
|
||||
dijit.byId('labelEdit_caption').domNode.setStyle({backgroundColor: bg_color});
|
||||
</script>
|
||||
</div>";
|
||||
|
||||
print "</td></tr></table>";
|
||||
print "</section>";
|
||||
|
||||
print "<footer>";
|
||||
print "<button dojoType='dijit.form.Button' type='submit' class='alt-primary' onclick=\"dijit.byId('labelEditDlg').execute()\">".
|
||||
__('Save')."</button>";
|
||||
print "<button dojoType='dijit.form.Button' onclick=\"dijit.byId('labelEditDlg').hide()\">".
|
||||
__('Cancel')."</button>";
|
||||
print "</footer>";
|
||||
|
||||
print "</form>";
|
||||
}
|
||||
}
|
||||
|
||||
function getlabeltree() {
|
||||
$root = array();
|
||||
$root['id'] = 'root';
|
||||
$root['name'] = __('Labels');
|
||||
$root['items'] = array();
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT *
|
||||
FROM ttrss_labels2
|
||||
WHERE owner_uid = ?
|
||||
ORDER BY caption");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
$label = array();
|
||||
$label['id'] = 'LABEL:' . $line['id'];
|
||||
$label['bare_id'] = $line['id'];
|
||||
$label['name'] = $line['caption'];
|
||||
$label['fg_color'] = $line['fg_color'];
|
||||
$label['bg_color'] = $line['bg_color'];
|
||||
$label['type'] = 'label';
|
||||
$label['checkbox'] = false;
|
||||
|
||||
array_push($root['items'], $label);
|
||||
}
|
||||
|
||||
$fl = array();
|
||||
$fl['identifier'] = 'id';
|
||||
$fl['label'] = 'name';
|
||||
$fl['items'] = array($root);
|
||||
|
||||
print json_encode($fl);
|
||||
return;
|
||||
}
|
||||
|
||||
function colorset() {
|
||||
$kind = clean($_REQUEST["kind"]);
|
||||
$ids = explode(',', clean($_REQUEST["ids"]));
|
||||
$color = clean($_REQUEST["color"]);
|
||||
$fg = clean($_REQUEST["fg"]);
|
||||
$bg = clean($_REQUEST["bg"]);
|
||||
|
||||
foreach ($ids as $id) {
|
||||
|
||||
if ($kind == "fg" || $kind == "bg") {
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_labels2 SET
|
||||
${kind}_color = ? WHERE id = ?
|
||||
AND owner_uid = ?");
|
||||
|
||||
$sth->execute([$color, $id, $_SESSION['uid']]);
|
||||
|
||||
} else {
|
||||
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_labels2 SET
|
||||
fg_color = ?, bg_color = ? WHERE id = ?
|
||||
AND owner_uid = ?");
|
||||
|
||||
$sth->execute([$fg, $bg, $id, $_SESSION['uid']]);
|
||||
}
|
||||
|
||||
/* Remove cached data */
|
||||
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET label_cache = ''
|
||||
WHERE owner_uid = ?");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
}
|
||||
}
|
||||
|
||||
function colorreset() {
|
||||
$ids = explode(',', clean($_REQUEST["ids"]));
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_labels2 SET
|
||||
fg_color = '', bg_color = '' WHERE id = ?
|
||||
AND owner_uid = ?");
|
||||
$sth->execute([$id, $_SESSION['uid']]);
|
||||
|
||||
/* Remove cached data */
|
||||
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET label_cache = ''
|
||||
WHERE owner_uid = ?");
|
||||
$sth->execute([$_SESSION['uid']]);
|
||||
}
|
||||
}
|
||||
|
||||
function save() {
|
||||
|
||||
$id = clean($_REQUEST["id"]);
|
||||
$caption = trim(clean($_REQUEST["caption"]));
|
||||
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT caption FROM ttrss_labels2
|
||||
WHERE id = ? AND owner_uid = ?");
|
||||
$sth->execute([$id, $_SESSION['uid']]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$old_caption = $row["caption"];
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id FROM ttrss_labels2
|
||||
WHERE caption = ? AND owner_uid = ?");
|
||||
$sth->execute([$caption, $_SESSION['uid']]);
|
||||
|
||||
if (!$sth->fetch()) {
|
||||
if ($caption) {
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_labels2 SET
|
||||
caption = ? WHERE id = ? AND
|
||||
owner_uid = ?");
|
||||
$sth->execute([$caption, $id, $_SESSION['uid']]);
|
||||
|
||||
/* Update filters that reference label being renamed */
|
||||
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_filters2_actions SET
|
||||
action_param = ? WHERE action_param = ?
|
||||
AND action_id = 7
|
||||
AND filter_id IN (SELECT id FROM ttrss_filters2 WHERE owner_uid = ?)");
|
||||
|
||||
$sth->execute([$caption, $old_caption, $_SESSION['uid']]);
|
||||
|
||||
print clean($_REQUEST["value"]);
|
||||
} else {
|
||||
print $old_caption;
|
||||
}
|
||||
} else {
|
||||
print $old_caption;
|
||||
}
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
|
||||
}
|
||||
|
||||
function remove() {
|
||||
|
||||
$ids = explode(",", clean($_REQUEST["ids"]));
|
||||
|
||||
foreach ($ids as $id) {
|
||||
Labels::remove($id, $_SESSION["uid"]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function add() {
|
||||
$caption = clean($_REQUEST["caption"]);
|
||||
$output = clean($_REQUEST["output"]);
|
||||
|
||||
if ($caption) {
|
||||
|
||||
if (Labels::create($caption)) {
|
||||
if (!$output) {
|
||||
print T_sprintf("Created label <b>%s</b>", htmlspecialchars($caption));
|
||||
}
|
||||
}
|
||||
|
||||
if ($output == "select") {
|
||||
header("Content-Type: text/xml");
|
||||
|
||||
print "<rpc-reply><payload>";
|
||||
|
||||
print_label_select("select_label",
|
||||
$caption, "");
|
||||
|
||||
print "</payload></rpc-reply>";
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function index() {
|
||||
|
||||
print "<div dojoType='dijit.layout.BorderContainer' gutters='false'>";
|
||||
print "<div style='padding : 0px' dojoType='dijit.layout.ContentPane' region='top'>";
|
||||
print "<div dojoType='dijit.Toolbar'>";
|
||||
|
||||
print "<div dojoType='dijit.form.DropDownButton'>".
|
||||
"<span>" . __('Select')."</span>";
|
||||
print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
|
||||
print "<div onclick=\"dijit.byId('labelTree').model.setAllChecked(true)\"
|
||||
dojoType=\"dijit.MenuItem\">".__('All')."</div>";
|
||||
print "<div onclick=\"dijit.byId('labelTree').model.setAllChecked(false)\"
|
||||
dojoType=\"dijit.MenuItem\">".__('None')."</div>";
|
||||
print "</div></div>";
|
||||
|
||||
print"<button dojoType=\"dijit.form.Button\" onclick=\"CommonDialogs.addLabel()\">".
|
||||
__('Create label')."</button dojoType=\"dijit.form.Button\"> ";
|
||||
|
||||
print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('labelTree').removeSelected()\">".
|
||||
__('Remove')."</button dojoType=\"dijit.form.Button\"> ";
|
||||
|
||||
print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('labelTree').resetColors()\">".
|
||||
__('Clear colors')."</button dojoType=\"dijit.form.Button\">";
|
||||
|
||||
|
||||
print "</div>"; #toolbar
|
||||
print "</div>"; #pane
|
||||
print "<div style='padding : 0px' dojoType=\"dijit.layout.ContentPane\" region=\"center\">";
|
||||
|
||||
print "<div id=\"labellistLoading\">
|
||||
<img src='images/indicator_tiny.gif'>".
|
||||
__("Loading, please wait...")."</div>";
|
||||
|
||||
print "<div dojoType=\"dojo.data.ItemFileWriteStore\" jsId=\"labelStore\"
|
||||
url=\"backend.php?op=pref-labels&method=getlabeltree\">
|
||||
</div>
|
||||
<div dojoType=\"lib.CheckBoxStoreModel\" jsId=\"labelModel\" store=\"labelStore\"
|
||||
query=\"{id:'root'}\" rootId=\"root\"
|
||||
childrenAttrs=\"items\" checkboxStrict=\"false\" checkboxAll=\"false\">
|
||||
</div>
|
||||
<div dojoType=\"fox.PrefLabelTree\" id=\"labelTree\"
|
||||
model=\"labelModel\" openOnClick=\"true\">
|
||||
<script type=\"dojo/method\" event=\"onLoad\" args=\"item\">
|
||||
Element.hide(\"labellistLoading\");
|
||||
</script>
|
||||
<script type=\"dojo/method\" event=\"onClick\" args=\"item\">
|
||||
var id = String(item.id);
|
||||
var bare_id = id.substr(id.indexOf(':')+1);
|
||||
|
||||
if (id.match('LABEL:')) {
|
||||
dijit.byId('labelTree').editLabel(bare_id);
|
||||
}
|
||||
</script>
|
||||
</div>";
|
||||
|
||||
print "</div>"; #pane
|
||||
|
||||
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB,
|
||||
"hook_prefs_tab", "prefLabels");
|
||||
|
||||
print "</div>"; #container
|
||||
|
||||
}
|
||||
}
|
1120
classes/pref/prefs.php
Normal file
1120
classes/pref/prefs.php
Normal file
File diff suppressed because it is too large
Load Diff
104
classes/pref/system.php
Normal file
104
classes/pref/system.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
class Pref_System extends Handler_Protected {
|
||||
|
||||
function before($method) {
|
||||
if (parent::before($method)) {
|
||||
if ($_SESSION["access_level"] < 10) {
|
||||
print __("Your access level is insufficient to open this tab.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function csrf_ignore($method) {
|
||||
$csrf_ignored = array("index");
|
||||
|
||||
return array_search($method, $csrf_ignored) !== false;
|
||||
}
|
||||
|
||||
function clearLog() {
|
||||
$this->pdo->query("DELETE FROM ttrss_error_log");
|
||||
}
|
||||
|
||||
function index() {
|
||||
|
||||
print "<div dojoType=\"dijit.layout.AccordionContainer\" region=\"center\">";
|
||||
print "<div dojoType=\"dijit.layout.AccordionPane\"
|
||||
title=\"<i class='material-icons'>report</i> ".__('Event Log')."\">";
|
||||
|
||||
if (LOG_DESTINATION == "sql") {
|
||||
|
||||
$res = $this->pdo->query("SELECT errno, errstr, filename, lineno,
|
||||
created_at, login, context FROM ttrss_error_log
|
||||
LEFT JOIN ttrss_users ON (owner_uid = ttrss_users.id)
|
||||
ORDER BY ttrss_error_log.id DESC
|
||||
LIMIT 100");
|
||||
|
||||
print "<button dojoType=\"dijit.form.Button\"
|
||||
onclick=\"Helpers.updateEventLog()\">".__('Refresh')."</button> ";
|
||||
|
||||
print " <button dojoType=\"dijit.form.Button\"
|
||||
class=\"alt-danger\" onclick=\"Helpers.clearEventLog()\">".__('Clear')."</button> ";
|
||||
|
||||
print "<p><table width=\"100%\" cellspacing=\"10\" class=\"prefErrorLog\">";
|
||||
|
||||
print "<tr class=\"title\">
|
||||
<td width='5%'>".__("Error")."</td>
|
||||
<td>".__("Filename")."</td>
|
||||
<td>".__("Message")."</td>
|
||||
<td width='5%'>".__("User")."</td>
|
||||
<td width='5%'>".__("Date")."</td>
|
||||
</tr>";
|
||||
|
||||
while ($line = $res->fetch()) {
|
||||
print "<tr class=\"errrow\">";
|
||||
|
||||
foreach ($line as $k => $v) {
|
||||
$line[$k] = htmlspecialchars($v);
|
||||
}
|
||||
|
||||
print "<td class='errno'>" . Logger::$errornames[$line["errno"]] . " (" . $line["errno"] . ")</td>";
|
||||
print "<td class='filename'>" . $line["filename"] . ":" . $line["lineno"] . "</td>";
|
||||
print "<td class='errstr'>" . $line["errstr"] . "<hr/>" . nl2br($line["context"]) . "</td>";
|
||||
print "<td class='login'>" . $line["login"] . "</td>";
|
||||
|
||||
print "<td class='timestamp'>" .
|
||||
make_local_datetime(
|
||||
$line["created_at"], false) . "</td>";
|
||||
|
||||
print "</tr>";
|
||||
}
|
||||
|
||||
print "</table>";
|
||||
} else {
|
||||
|
||||
print_notice("Please set LOG_DESTINATION to 'sql' in config.php to enable database logging.");
|
||||
|
||||
}
|
||||
|
||||
print "</div>";
|
||||
|
||||
print "<div dojoType=\"dijit.layout.AccordionPane\"
|
||||
title=\"<i class='material-icons'>info</i> ".__('PHP Information')."\">";
|
||||
|
||||
ob_start();
|
||||
phpinfo();
|
||||
$info = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
print "<div class='phpinfo'>";
|
||||
print preg_replace( '%^.*<body>(.*)</body>.*$%ms','$1', $info);
|
||||
print "</div>";
|
||||
|
||||
print "</div>";
|
||||
|
||||
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB,
|
||||
"hook_prefs_tab", "prefSystem");
|
||||
|
||||
print "</div>"; #container
|
||||
}
|
||||
|
||||
}
|
438
classes/pref/users.php
Normal file
438
classes/pref/users.php
Normal file
@ -0,0 +1,438 @@
|
||||
<?php
|
||||
class Pref_Users extends Handler_Protected {
|
||||
function before($method) {
|
||||
if (parent::before($method)) {
|
||||
if ($_SESSION["access_level"] < 10) {
|
||||
print __("Your access level is insufficient to open this tab.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function csrf_ignore($method) {
|
||||
$csrf_ignored = array("index", "edit", "userdetails");
|
||||
|
||||
return array_search($method, $csrf_ignored) !== false;
|
||||
}
|
||||
|
||||
function edit() {
|
||||
global $access_level_names;
|
||||
|
||||
print "<form id='user_edit_form' onsubmit='return false' dojoType='dijit.form.Form'>";
|
||||
|
||||
print '<div dojoType="dijit.layout.TabContainer" style="height : 400px">
|
||||
<div dojoType="dijit.layout.ContentPane" title="'.__('Edit user').'">';
|
||||
|
||||
//print "<form id=\"user_edit_form\" onsubmit='return false' dojoType=\"dijit.form.Form\">";
|
||||
|
||||
$id = (int) clean($_REQUEST["id"]);
|
||||
|
||||
print_hidden("id", "$id");
|
||||
print_hidden("op", "pref-users");
|
||||
print_hidden("method", "editSave");
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT * FROM ttrss_users WHERE id = ?");
|
||||
$sth->execute([$id]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
|
||||
$login = $row["login"];
|
||||
$access_level = $row["access_level"];
|
||||
$email = $row["email"];
|
||||
|
||||
$sel_disabled = ($id == $_SESSION["uid"] || $login == "admin") ? "disabled" : "";
|
||||
|
||||
print "<header>".__("User")."</header>";
|
||||
print "<section>";
|
||||
|
||||
if ($sel_disabled) {
|
||||
print_hidden("login", "$login");
|
||||
}
|
||||
|
||||
print "<fieldset>";
|
||||
print "<label>" . __("Login:") . "</label>";
|
||||
print "<input style='font-size : 16px'
|
||||
dojoType='dijit.form.ValidationTextBox' required='1'
|
||||
$sel_disabled name='login' value=\"$login\">";
|
||||
print "</fieldset>";
|
||||
|
||||
print "</section>";
|
||||
|
||||
print "<header>".__("Authentication")."</header>";
|
||||
print "<section>";
|
||||
|
||||
print "<fieldset>";
|
||||
|
||||
print "<label>" . __('Access level: ') . "</label> ";
|
||||
|
||||
if (!$sel_disabled) {
|
||||
print_select_hash("access_level", $access_level, $access_level_names,
|
||||
"dojoType=\"dijit.form.Select\" $sel_disabled");
|
||||
} else {
|
||||
print_select_hash("", $access_level, $access_level_names,
|
||||
"dojoType=\"dijit.form.Select\" $sel_disabled");
|
||||
print_hidden("access_level", "$access_level");
|
||||
}
|
||||
|
||||
print "</fieldset>";
|
||||
print "<fieldset>";
|
||||
|
||||
print "<label>" . __("New password:") . "</label> ";
|
||||
print "<input dojoType='dijit.form.TextBox' type='password' size='20' placeholder='Change password'
|
||||
name='password'>";
|
||||
|
||||
print "</fieldset>";
|
||||
|
||||
print "</section>";
|
||||
|
||||
print "<header>".__("Options")."</header>";
|
||||
print "<section>";
|
||||
|
||||
print "<fieldset>";
|
||||
print "<label>" . __("E-mail:") . "</label> ";
|
||||
print "<input dojoType='dijit.form.TextBox' size='30' name='email'
|
||||
value=\"$email\">";
|
||||
print "</fieldset>";
|
||||
|
||||
print "</section>";
|
||||
|
||||
print "</table>";
|
||||
|
||||
}
|
||||
|
||||
print '</div>'; #tab
|
||||
print "<div href=\"backend.php?op=pref-users&method=userdetails&id=$id\"
|
||||
dojoType=\"dijit.layout.ContentPane\" title=\"".__('User details')."\">";
|
||||
|
||||
print '</div>';
|
||||
print '</div>';
|
||||
|
||||
print "<footer>
|
||||
<button dojoType='dijit.form.Button' class='alt-primary' type='submit' onclick=\"dijit.byId('userEditDlg').execute()\">".
|
||||
__('Save')."</button>
|
||||
<button dojoType='dijit.form.Button' onclick=\"dijit.byId('userEditDlg').hide()\">".
|
||||
__('Cancel')."</button>
|
||||
</footer>";
|
||||
|
||||
print "</form>";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function userdetails() {
|
||||
$id = (int) clean($_REQUEST["id"]);
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT login,
|
||||
".SUBSTRING_FOR_DATE."(last_login,1,16) AS last_login,
|
||||
access_level,
|
||||
(SELECT COUNT(int_id) FROM ttrss_user_entries
|
||||
WHERE owner_uid = id) AS stored_articles,
|
||||
".SUBSTRING_FOR_DATE."(created,1,16) AS created
|
||||
FROM ttrss_users
|
||||
WHERE id = ?");
|
||||
$sth->execute([$id]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
print "<table width='100%'>";
|
||||
|
||||
$last_login = make_local_datetime(
|
||||
$row["last_login"], true);
|
||||
|
||||
$created = make_local_datetime(
|
||||
$row["created"], true);
|
||||
|
||||
$stored_articles = $row["stored_articles"];
|
||||
|
||||
print "<tr><td>".__('Registered')."</td><td>$created</td></tr>";
|
||||
print "<tr><td>".__('Last logged in')."</td><td>$last_login</td></tr>";
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT COUNT(id) as num_feeds FROM ttrss_feeds
|
||||
WHERE owner_uid = ?");
|
||||
$sth->execute([$id]);
|
||||
$row = $sth->fetch();
|
||||
$num_feeds = $row["num_feeds"];
|
||||
|
||||
print "<tr><td>".__('Subscribed feeds count')."</td><td>$num_feeds</td></tr>";
|
||||
print "<tr><td>".__('Stored articles')."</td><td>$stored_articles</td></tr>";
|
||||
|
||||
print "</table>";
|
||||
|
||||
print "<h1>".__('Subscribed feeds')."</h1>";
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id,title,site_url FROM ttrss_feeds
|
||||
WHERE owner_uid = ? ORDER BY title");
|
||||
$sth->execute([$id]);
|
||||
|
||||
print "<ul class=\"panel panel-scrollable list list-unstyled\">";
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
|
||||
$icon_file = ICONS_URL."/".$line["id"].".ico";
|
||||
|
||||
if (file_exists($icon_file) && filesize($icon_file) > 0) {
|
||||
$feed_icon = "<img class=\"icon\" src=\"$icon_file\">";
|
||||
} else {
|
||||
$feed_icon = "<img class=\"icon\" src=\"images/blank_icon.gif\">";
|
||||
}
|
||||
|
||||
print "<li>$feed_icon <a href=\"".$line["site_url"]."\">".$line["title"]."</a></li>";
|
||||
|
||||
}
|
||||
|
||||
print "</ul>";
|
||||
|
||||
|
||||
} else {
|
||||
print "<h1>".__('User not found')."</h1>";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function editSave() {
|
||||
$login = trim(clean($_REQUEST["login"]));
|
||||
$uid = clean($_REQUEST["id"]);
|
||||
$access_level = (int) clean($_REQUEST["access_level"]);
|
||||
$email = trim(clean($_REQUEST["email"]));
|
||||
$password = clean($_REQUEST["password"]);
|
||||
|
||||
if ($password) {
|
||||
$salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
|
||||
$pwd_hash = encrypt_password($password, $salt, true);
|
||||
$pass_query_part = "pwd_hash = ".$this->pdo->quote($pwd_hash).",
|
||||
salt = ".$this->pdo->quote($salt).",";
|
||||
} else {
|
||||
$pass_query_part = "";
|
||||
}
|
||||
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_users SET $pass_query_part login = ?,
|
||||
access_level = ?, email = ?, otp_enabled = false WHERE id = ?");
|
||||
$sth->execute([$login, $access_level, $email, $uid]);
|
||||
|
||||
}
|
||||
|
||||
function remove() {
|
||||
$ids = explode(",", clean($_REQUEST["ids"]));
|
||||
|
||||
foreach ($ids as $id) {
|
||||
if ($id != $_SESSION["uid"] && $id != 1) {
|
||||
$sth = $this->pdo->prepare("DELETE FROM ttrss_tags WHERE owner_uid = ?");
|
||||
$sth->execute([$id]);
|
||||
|
||||
$sth = $this->pdo->prepare("DELETE FROM ttrss_feeds WHERE owner_uid = ?");
|
||||
$sth->execute([$id]);
|
||||
|
||||
$sth = $this->pdo->prepare("DELETE FROM ttrss_users WHERE id = ?");
|
||||
$sth->execute([$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function add() {
|
||||
$login = trim(clean($_REQUEST["login"]));
|
||||
$tmp_user_pwd = make_password();
|
||||
$salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
|
||||
$pwd_hash = encrypt_password($tmp_user_pwd, $salt, true);
|
||||
|
||||
if (!$login) return; // no blank usernames
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE
|
||||
login = ?");
|
||||
$sth->execute([$login]);
|
||||
|
||||
if (!$sth->fetch()) {
|
||||
|
||||
$sth = $this->pdo->prepare("INSERT INTO ttrss_users
|
||||
(login,pwd_hash,access_level,last_login,created, salt)
|
||||
VALUES (?, ?, 0, null, NOW(), ?)");
|
||||
$sth->execute([$login, $pwd_hash, $salt]);
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE
|
||||
login = ? AND pwd_hash = ?");
|
||||
$sth->execute([$login, $pwd_hash]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
|
||||
$new_uid = $row['id'];
|
||||
|
||||
print T_sprintf("Added user %s with password %s",
|
||||
$login, $tmp_user_pwd);
|
||||
|
||||
initialize_user($new_uid);
|
||||
|
||||
} else {
|
||||
|
||||
print T_sprintf("Could not create user %s", $login);
|
||||
|
||||
}
|
||||
} else {
|
||||
print T_sprintf("User %s already exists.", $login);
|
||||
}
|
||||
}
|
||||
|
||||
static function resetUserPassword($uid, $format_output = false) {
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
$sth = $pdo->prepare("SELECT login FROM ttrss_users WHERE id = ?");
|
||||
$sth->execute([$uid]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
|
||||
$login = $row["login"];
|
||||
|
||||
$new_salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
|
||||
$tmp_user_pwd = make_password();
|
||||
|
||||
$pwd_hash = encrypt_password($tmp_user_pwd, $new_salt, true);
|
||||
|
||||
$sth = $pdo->prepare("UPDATE ttrss_users
|
||||
SET pwd_hash = ?, salt = ?, otp_enabled = false
|
||||
WHERE id = ?");
|
||||
$sth->execute([$pwd_hash, $new_salt, $uid]);
|
||||
|
||||
$message = T_sprintf("Changed password of user %s to %s", "<strong>$login</strong>", "<strong>$tmp_user_pwd</strong>");
|
||||
|
||||
if ($format_output)
|
||||
print_notice($message);
|
||||
else
|
||||
print $message;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function resetPass() {
|
||||
$uid = clean($_REQUEST["id"]);
|
||||
Pref_Users::resetUserPassword($uid);
|
||||
}
|
||||
|
||||
function index() {
|
||||
|
||||
global $access_level_names;
|
||||
|
||||
print "<div dojoType='dijit.layout.BorderContainer' gutters='false'>";
|
||||
print "<div style='padding : 0px' dojoType='dijit.layout.ContentPane' region='top'>";
|
||||
print "<div dojoType='dijit.Toolbar'>";
|
||||
|
||||
$user_search = trim(clean($_REQUEST["search"]));
|
||||
|
||||
if (array_key_exists("search", $_REQUEST)) {
|
||||
$_SESSION["prefs_user_search"] = $user_search;
|
||||
} else {
|
||||
$user_search = $_SESSION["prefs_user_search"];
|
||||
}
|
||||
|
||||
print "<div style='float : right; padding-right : 4px;'>
|
||||
<input dojoType='dijit.form.TextBox' id='user_search' size='20' type='search'
|
||||
value=\"$user_search\">
|
||||
<button dojoType='dijit.form.Button' onclick='Users.reload()'>".
|
||||
__('Search')."</button>
|
||||
</div>";
|
||||
|
||||
$sort = clean($_REQUEST["sort"]);
|
||||
|
||||
if (!$sort || $sort == "undefined") {
|
||||
$sort = "login";
|
||||
}
|
||||
|
||||
print "<div dojoType='dijit.form.DropDownButton'>".
|
||||
"<span>" . __('Select')."</span>";
|
||||
print "<div dojoType='dijit.Menu' style='display: none'>";
|
||||
print "<div onclick=\"Tables.select('prefUserList', true)\"
|
||||
dojoType='dijit.MenuItem'>".__('All')."</div>";
|
||||
print "<div onclick=\"Tables.select('prefUserList', false)\"
|
||||
dojoType='dijit.MenuItem'>".__('None')."</div>";
|
||||
print "</div></div>";
|
||||
|
||||
print "<button dojoType='dijit.form.Button' onclick='Users.add()'>".__('Create user')."</button>";
|
||||
|
||||
print "
|
||||
<button dojoType='dijit.form.Button' onclick='Users.editSelected()'>".
|
||||
__('Edit')."</button dojoType=\"dijit.form.Button\">
|
||||
<button dojoType='dijit.form.Button' onclick='Users.removeSelected()'>".
|
||||
__('Remove')."</button dojoType=\"dijit.form.Button\">
|
||||
<button dojoType='dijit.form.Button' onclick='Users.resetSelected()'>".
|
||||
__('Reset password')."</button dojoType=\"dijit.form.Button\">";
|
||||
|
||||
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION,
|
||||
"hook_prefs_tab_section", "prefUsersToolbar");
|
||||
|
||||
print "</div>"; #toolbar
|
||||
print "</div>"; #pane
|
||||
print "<div style='padding : 0px' dojoType='dijit.layout.ContentPane' region='center'>";
|
||||
|
||||
$sort = validate_field($sort,
|
||||
["login", "access_level", "created", "num_feeds", "created", "last_login"], "login");
|
||||
|
||||
if ($sort != "login") $sort = "$sort DESC";
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT
|
||||
tu.id,
|
||||
login,access_level,email,
|
||||
".SUBSTRING_FOR_DATE."(last_login,1,16) as last_login,
|
||||
".SUBSTRING_FOR_DATE."(created,1,16) as created,
|
||||
(SELECT COUNT(id) FROM ttrss_feeds WHERE owner_uid = tu.id) AS num_feeds
|
||||
FROM
|
||||
ttrss_users tu
|
||||
WHERE
|
||||
(:search = '' OR login LIKE :search) AND tu.id > 0
|
||||
ORDER BY $sort");
|
||||
$sth->execute([":search" => $user_search ? "%$user_search%" : ""]);
|
||||
|
||||
print "<p><table width='100%' cellspacing='0' class='prefUserList' id='prefUserList'>";
|
||||
|
||||
print "<tr class='title'>
|
||||
<td align='center' width='5%'> </td>
|
||||
<td width='20%'><a href='#' onclick=\"Users.reload('login')\">".__('Login')."</a></td>
|
||||
<td width='20%'><a href='#' onclick=\"Users.reload('access_level')\">".__('Access Level')."</a></td>
|
||||
<td width='10%'><a href='#' onclick=\"Users.reload('num_feeds')\">".__('Subscribed feeds')."</a></td>
|
||||
<td width='20%'><a href='#' onclick=\"Users.reload('created')\">".__('Registered')."</a></td>
|
||||
<td width='20%'><a href='#' onclick=\"Users.reload('last_login')\">".__('Last login')."</a></td></tr>";
|
||||
|
||||
$lnum = 0;
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
|
||||
$uid = $line["id"];
|
||||
|
||||
print "<tr data-row-id='$uid' onclick='Users.edit($uid)'>";
|
||||
|
||||
$line["login"] = htmlspecialchars($line["login"]);
|
||||
$line["created"] = make_local_datetime($line["created"], false);
|
||||
$line["last_login"] = make_local_datetime($line["last_login"], false);
|
||||
|
||||
print "<td align='center'><input onclick='Tables.onRowChecked(this); event.stopPropagation();'
|
||||
dojoType='dijit.form.CheckBox' type='checkbox'></td>";
|
||||
|
||||
print "<td title='".__('Click to edit')."'><i class='material-icons'>person</i> " . $line["login"] . "</td>";
|
||||
|
||||
print "<td>" . $access_level_names[$line["access_level"]] . "</td>";
|
||||
print "<td>" . $line["num_feeds"] . "</td>";
|
||||
print "<td>" . $line["created"] . "</td>";
|
||||
print "<td>" . $line["last_login"] . "</td>";
|
||||
|
||||
print "</tr>";
|
||||
|
||||
++$lnum;
|
||||
}
|
||||
|
||||
print "</table>";
|
||||
|
||||
if ($lnum == 0) {
|
||||
if (!$user_search) {
|
||||
print_warning(__('No users defined.'));
|
||||
} else {
|
||||
print_warning(__('No matching users found.'));
|
||||
}
|
||||
}
|
||||
|
||||
print "</div>"; #pane
|
||||
|
||||
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB,
|
||||
"hook_prefs_tab", "prefUsers");
|
||||
|
||||
print "</div>"; #container
|
||||
|
||||
}
|
||||
}
|
605
classes/rpc.php
Executable file
605
classes/rpc.php
Executable file
@ -0,0 +1,605 @@
|
||||
<?php
|
||||
class RPC extends Handler_Protected {
|
||||
|
||||
function csrf_ignore($method) {
|
||||
$csrf_ignored = array("sanitycheck", "completelabels", "saveprofile");
|
||||
|
||||
return array_search($method, $csrf_ignored) !== false;
|
||||
}
|
||||
|
||||
function setprofile() {
|
||||
$_SESSION["profile"] = (int) clean($_REQUEST["id"]);
|
||||
|
||||
// default value
|
||||
if (!$_SESSION["profile"]) $_SESSION["profile"] = null;
|
||||
}
|
||||
|
||||
function remprofiles() {
|
||||
$ids = explode(",", trim(clean($_REQUEST["ids"])));
|
||||
|
||||
foreach ($ids as $id) {
|
||||
if ($_SESSION["profile"] != $id) {
|
||||
$sth = $this->pdo->prepare("DELETE FROM ttrss_settings_profiles WHERE id = ? AND
|
||||
owner_uid = ?");
|
||||
$sth->execute([$id, $_SESSION['uid']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Silent
|
||||
function addprofile() {
|
||||
$title = trim(clean($_REQUEST["title"]));
|
||||
|
||||
if ($title) {
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id FROM ttrss_settings_profiles
|
||||
WHERE title = ? AND owner_uid = ?");
|
||||
$sth->execute([$title, $_SESSION['uid']]);
|
||||
|
||||
if (!$sth->fetch()) {
|
||||
|
||||
$sth = $this->pdo->prepare("INSERT INTO ttrss_settings_profiles (title, owner_uid)
|
||||
VALUES (?, ?)");
|
||||
|
||||
$sth->execute([$title, $_SESSION['uid']]);
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id FROM ttrss_settings_profiles WHERE
|
||||
title = ? AND owner_uid = ?");
|
||||
$sth->execute([$title, $_SESSION['uid']]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$profile_id = $row['id'];
|
||||
|
||||
if ($profile_id) {
|
||||
initialize_user_prefs($_SESSION["uid"], $profile_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
}
|
||||
}
|
||||
|
||||
function saveprofile() {
|
||||
$id = clean($_REQUEST["id"]);
|
||||
$title = trim(clean($_REQUEST["value"]));
|
||||
|
||||
if ($id == 0) {
|
||||
print __("Default profile");
|
||||
return;
|
||||
}
|
||||
|
||||
if ($title) {
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_settings_profiles
|
||||
SET title = ? WHERE id = ? AND
|
||||
owner_uid = ?");
|
||||
|
||||
$sth->execute([$title, $id, $_SESSION['uid']]);
|
||||
print $title;
|
||||
}
|
||||
}
|
||||
|
||||
// Silent
|
||||
function remarchive() {
|
||||
$ids = explode(",", clean($_REQUEST["ids"]));
|
||||
|
||||
$sth = $this->pdo->prepare("DELETE FROM ttrss_archived_feeds WHERE
|
||||
(SELECT COUNT(*) FROM ttrss_user_entries
|
||||
WHERE orig_feed_id = :id) = 0 AND
|
||||
id = :id AND owner_uid = :uid");
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$sth->execute([":id" => $id, ":uid" => $_SESSION['uid']]);
|
||||
}
|
||||
}
|
||||
|
||||
function addfeed() {
|
||||
$feed = clean($_REQUEST['feed']);
|
||||
$cat = clean($_REQUEST['cat']);
|
||||
$need_auth = isset($_REQUEST['need_auth']);
|
||||
$login = $need_auth ? clean($_REQUEST['login']) : '';
|
||||
$pass = $need_auth ? trim(clean($_REQUEST['pass'])) : '';
|
||||
|
||||
$rc = Feeds::subscribe_to_feed($feed, $cat, $login, $pass);
|
||||
|
||||
print json_encode(array("result" => $rc));
|
||||
}
|
||||
|
||||
function togglepref() {
|
||||
$key = clean($_REQUEST["key"]);
|
||||
set_pref($key, !get_pref($key));
|
||||
$value = get_pref($key);
|
||||
|
||||
print json_encode(array("param" =>$key, "value" => $value));
|
||||
}
|
||||
|
||||
function setpref() {
|
||||
// set_pref escapes input, so no need to double escape it here
|
||||
$key = clean($_REQUEST['key']);
|
||||
$value = $_REQUEST['value'];
|
||||
|
||||
set_pref($key, $value, false, $key != 'USER_STYLESHEET');
|
||||
|
||||
print json_encode(array("param" =>$key, "value" => $value));
|
||||
}
|
||||
|
||||
function mark() {
|
||||
$mark = clean($_REQUEST["mark"]);
|
||||
$id = clean($_REQUEST["id"]);
|
||||
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET marked = ?,
|
||||
last_marked = NOW()
|
||||
WHERE ref_id = ? AND owner_uid = ?");
|
||||
|
||||
$sth->execute([$mark, $id, $_SESSION['uid']]);
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS"));
|
||||
}
|
||||
|
||||
function delete() {
|
||||
$ids = explode(",", clean($_REQUEST["ids"]));
|
||||
$ids_qmarks = arr_qmarks($ids);
|
||||
|
||||
$sth = $this->pdo->prepare("DELETE FROM ttrss_user_entries
|
||||
WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
|
||||
$sth->execute(array_merge($ids, [$_SESSION['uid']]));
|
||||
|
||||
Article::purge_orphans();
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS"));
|
||||
}
|
||||
|
||||
function unarchive() {
|
||||
$ids = explode(",", clean($_REQUEST["ids"]));
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT feed_url,site_url,title FROM ttrss_archived_feeds
|
||||
WHERE id = (SELECT orig_feed_id FROM ttrss_user_entries WHERE ref_id = :id
|
||||
AND owner_uid = :uid) AND owner_uid = :uid");
|
||||
$sth->execute([":uid" => $_SESSION['uid'], ":id" => $id]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$feed_url = $row['feed_url'];
|
||||
$site_url = $row['site_url'];
|
||||
$title = $row['title'];
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE feed_url = ?
|
||||
AND owner_uid = ?");
|
||||
$sth->execute([$feed_url, $_SESSION['uid']]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$feed_id = $row["id"];
|
||||
} else {
|
||||
if (!$title) $title = '[Unknown]';
|
||||
|
||||
$sth = $this->pdo->prepare("INSERT INTO ttrss_feeds
|
||||
(owner_uid,feed_url,site_url,title,cat_id,auth_login,auth_pass,update_method)
|
||||
VALUES (?, ?, ?, ?, NULL, '', '', 0)");
|
||||
$sth->execute([$_SESSION['uid'], $feed_url, $site_url, $title]);
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE feed_url = ?
|
||||
AND owner_uid = ?");
|
||||
$sth->execute([$feed_url, $_SESSION['uid']]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$feed_id = $row['id'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($feed_id) {
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries
|
||||
SET feed_id = ?, orig_feed_id = NULL
|
||||
WHERE ref_id = ? AND owner_uid = ?");
|
||||
$sth->execute([$feed_id, $id, $_SESSION['uid']]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
}
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS"));
|
||||
}
|
||||
|
||||
function archive() {
|
||||
$ids = explode(",", clean($_REQUEST["ids"]));
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$this->archive_article($id, $_SESSION["uid"]);
|
||||
}
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS"));
|
||||
}
|
||||
|
||||
private function archive_article($id, $owner_uid) {
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
if (!$owner_uid) $owner_uid = $_SESSION['uid'];
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT feed_id FROM ttrss_user_entries
|
||||
WHERE ref_id = ? AND owner_uid = ?");
|
||||
$sth->execute([$id, $owner_uid]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
|
||||
/* prepare the archived table */
|
||||
|
||||
$feed_id = (int) $row['feed_id'];
|
||||
|
||||
if ($feed_id) {
|
||||
$sth = $this->pdo->prepare("SELECT id FROM ttrss_archived_feeds
|
||||
WHERE id = ? AND owner_uid = ?");
|
||||
$sth->execute([$feed_id, $owner_uid]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$new_feed_id = $row['id'];
|
||||
} else {
|
||||
$row = $this->pdo->query("SELECT MAX(id) AS id FROM ttrss_archived_feeds")->fetch();
|
||||
$new_feed_id = (int)$row['id'] + 1;
|
||||
|
||||
$sth = $this->pdo->prepare("INSERT INTO ttrss_archived_feeds
|
||||
(id, owner_uid, title, feed_url, site_url, created)
|
||||
SELECT ?, owner_uid, title, feed_url, site_url, NOW() from ttrss_feeds
|
||||
WHERE id = ?");
|
||||
|
||||
$sth->execute([$new_feed_id, $feed_id]);
|
||||
}
|
||||
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries
|
||||
SET orig_feed_id = ?, feed_id = NULL
|
||||
WHERE ref_id = ? AND owner_uid = ?");
|
||||
$sth->execute([$new_feed_id, $id, $owner_uid]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
}
|
||||
|
||||
function publ() {
|
||||
$pub = clean($_REQUEST["pub"]);
|
||||
$id = clean($_REQUEST["id"]);
|
||||
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
published = ?, last_published = NOW()
|
||||
WHERE ref_id = ? AND owner_uid = ?");
|
||||
|
||||
$sth->execute([$pub, $id, $_SESSION['uid']]);
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS"));
|
||||
}
|
||||
|
||||
function getAllCounters() {
|
||||
@$seq = (int) $_REQUEST['seq'];
|
||||
|
||||
$reply = [
|
||||
'counters' => Counters::getAllCounters(),
|
||||
'seq' => $seq
|
||||
];
|
||||
|
||||
if ($seq % 2 == 0)
|
||||
$reply['runtime-info'] = make_runtime_info();
|
||||
|
||||
print json_encode($reply);
|
||||
}
|
||||
|
||||
/* GET["cmode"] = 0 - mark as read, 1 - as unread, 2 - toggle */
|
||||
function catchupSelected() {
|
||||
$ids = explode(",", clean($_REQUEST["ids"]));
|
||||
$cmode = (int)clean($_REQUEST["cmode"]);
|
||||
|
||||
Article::catchupArticlesById($ids, $cmode);
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS", "ids" => $ids));
|
||||
}
|
||||
|
||||
function markSelected() {
|
||||
$ids = explode(",", clean($_REQUEST["ids"]));
|
||||
$cmode = (int)clean($_REQUEST["cmode"]);
|
||||
|
||||
$this->markArticlesById($ids, $cmode);
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS"));
|
||||
}
|
||||
|
||||
function publishSelected() {
|
||||
$ids = explode(",", clean($_REQUEST["ids"]));
|
||||
$cmode = (int)clean($_REQUEST["cmode"]);
|
||||
|
||||
$this->publishArticlesById($ids, $cmode);
|
||||
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS"));
|
||||
}
|
||||
|
||||
function sanityCheck() {
|
||||
$_SESSION["hasAudio"] = clean($_REQUEST["hasAudio"]) === "true";
|
||||
$_SESSION["hasSandbox"] = clean($_REQUEST["hasSandbox"]) === "true";
|
||||
$_SESSION["hasMp3"] = clean($_REQUEST["hasMp3"]) === "true";
|
||||
$_SESSION["clientTzOffset"] = clean($_REQUEST["clientTzOffset"]);
|
||||
|
||||
$reply = array();
|
||||
|
||||
$reply['error'] = sanity_check();
|
||||
|
||||
if ($reply['error']['code'] == 0) {
|
||||
$reply['init-params'] = make_init_params();
|
||||
$reply['runtime-info'] = make_runtime_info();
|
||||
}
|
||||
|
||||
print json_encode($reply);
|
||||
}
|
||||
|
||||
function completeLabels() {
|
||||
$search = clean($_REQUEST["search"]);
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT DISTINCT caption FROM
|
||||
ttrss_labels2
|
||||
WHERE owner_uid = ? AND
|
||||
LOWER(caption) LIKE LOWER(?) ORDER BY caption
|
||||
LIMIT 5");
|
||||
$sth->execute([$_SESSION['uid'], "%$search%"]);
|
||||
|
||||
print "<ul>";
|
||||
while ($line = $sth->fetch()) {
|
||||
print "<li>" . $line["caption"] . "</li>";
|
||||
}
|
||||
print "</ul>";
|
||||
}
|
||||
|
||||
// Silent
|
||||
function massSubscribe() {
|
||||
|
||||
$payload = json_decode(clean($_REQUEST["payload"]), false);
|
||||
$mode = clean($_REQUEST["mode"]);
|
||||
|
||||
if (!$payload || !is_array($payload)) return;
|
||||
|
||||
if ($mode == 1) {
|
||||
foreach ($payload as $feed) {
|
||||
|
||||
$title = $feed[0];
|
||||
$feed_url = $feed[1];
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE
|
||||
feed_url = ? AND owner_uid = ?");
|
||||
$sth->execute([$feed_url, $_SESSION['uid']]);
|
||||
|
||||
if (!$sth->fetch()) {
|
||||
$sth = $this->pdo->prepare("INSERT INTO ttrss_feeds
|
||||
(owner_uid,feed_url,title,cat_id,site_url)
|
||||
VALUES (?, ?, ?, NULL, '')");
|
||||
|
||||
$sth->execute([$_SESSION['uid'], $feed_url, $title]);
|
||||
}
|
||||
}
|
||||
} else if ($mode == 2) {
|
||||
// feed archive
|
||||
foreach ($payload as $id) {
|
||||
$sth = $this->pdo->prepare("SELECT * FROM ttrss_archived_feeds
|
||||
WHERE id = ? AND owner_uid = ?");
|
||||
$sth->execute([$id, $_SESSION['uid']]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$site_url = $row['site_url'];
|
||||
$feed_url = $row['feed_url'];
|
||||
$title = $row['title'];
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE
|
||||
feed_url = ? AND owner_uid = ?");
|
||||
$sth->execute([$feed_url, $_SESSION['uid']]);
|
||||
|
||||
if (!$sth->fetch()) {
|
||||
$sth = $this->pdo->prepare("INSERT INTO ttrss_feeds
|
||||
(owner_uid,feed_url,title,cat_id,site_url)
|
||||
VALUES (?, ?, ?, NULL, ?)");
|
||||
|
||||
$sth->execute([$_SESSION['uid'], $feed_url, $title, $site_url]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function catchupFeed() {
|
||||
$feed_id = clean($_REQUEST['feed_id']);
|
||||
$is_cat = clean($_REQUEST['is_cat']) == "true";
|
||||
$mode = clean($_REQUEST['mode']);
|
||||
$search_query = clean($_REQUEST['search_query']);
|
||||
$search_lang = clean($_REQUEST['search_lang']);
|
||||
|
||||
Feeds::catchup_feed($feed_id, $is_cat, false, $mode, [$search_query, $search_lang]);
|
||||
|
||||
// return counters here synchronously so that frontend can figure out next unread feed properly
|
||||
print json_encode(['counters' => Counters::getAllCounters()]);
|
||||
|
||||
//print json_encode(array("message" => "UPDATE_COUNTERS"));
|
||||
}
|
||||
|
||||
function setpanelmode() {
|
||||
$wide = (int) clean($_REQUEST["wide"]);
|
||||
|
||||
setcookie("ttrss_widescreen", $wide,
|
||||
time() + COOKIE_LIFETIME_LONG);
|
||||
|
||||
print json_encode(array("wide" => $wide));
|
||||
}
|
||||
|
||||
static function updaterandomfeed_real() {
|
||||
|
||||
// Test if the feed need a update (update interval exceded).
|
||||
if (DB_TYPE == "pgsql") {
|
||||
$update_limit_qpart = "AND ((
|
||||
ttrss_feeds.update_interval = 0
|
||||
AND ttrss_feeds.last_updated < NOW() - CAST((ttrss_user_prefs.value || ' minutes') AS INTERVAL)
|
||||
) 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')";
|
||||
} else {
|
||||
$update_limit_qpart = "AND ((
|
||||
ttrss_feeds.update_interval = 0
|
||||
AND ttrss_feeds.last_updated < DATE_SUB(NOW(), INTERVAL CONVERT(ttrss_user_prefs.value, SIGNED INTEGER) MINUTE)
|
||||
) 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')";
|
||||
}
|
||||
|
||||
// Test if feed is currently being updated by another process.
|
||||
if (DB_TYPE == "pgsql") {
|
||||
$updstart_thresh_qpart = "AND (ttrss_feeds.last_update_started IS NULL OR ttrss_feeds.last_update_started < NOW() - INTERVAL '5 minutes')";
|
||||
} else {
|
||||
$updstart_thresh_qpart = "AND (ttrss_feeds.last_update_started IS NULL OR ttrss_feeds.last_update_started < DATE_SUB(NOW(), INTERVAL 5 MINUTE))";
|
||||
}
|
||||
|
||||
$random_qpart = sql_random_function();
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
// we could be invoked from public.php with no active session
|
||||
if ($_SESSION["uid"]) {
|
||||
$owner_check_qpart = "AND ttrss_feeds.owner_uid = ".$pdo->quote($_SESSION["uid"]);
|
||||
} else {
|
||||
$owner_check_qpart = "";
|
||||
}
|
||||
|
||||
// We search for feed needing update.
|
||||
$res = $pdo->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'
|
||||
$owner_check_qpart
|
||||
$update_limit_qpart
|
||||
$updstart_thresh_qpart
|
||||
ORDER BY $random_qpart LIMIT 30");
|
||||
|
||||
$num_updated = 0;
|
||||
|
||||
$tstart = time();
|
||||
|
||||
while ($line = $res->fetch()) {
|
||||
$feed_id = $line["id"];
|
||||
|
||||
if (time() - $tstart < ini_get("max_execution_time") * 0.7) {
|
||||
RSSUtils::update_rss_feed($feed_id, true);
|
||||
++$num_updated;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Purge orphans and cleanup tags
|
||||
Article::purge_orphans();
|
||||
//cleanup_tags(14, 50000);
|
||||
|
||||
if ($num_updated > 0) {
|
||||
print json_encode(array("message" => "UPDATE_COUNTERS",
|
||||
"num_updated" => $num_updated));
|
||||
} else {
|
||||
print json_encode(array("message" => "NOTHING_TO_UPDATE"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function updaterandomfeed() {
|
||||
RPC::updaterandomfeed_real();
|
||||
}
|
||||
|
||||
private function markArticlesById($ids, $cmode) {
|
||||
|
||||
$ids_qmarks = arr_qmarks($ids);
|
||||
|
||||
if ($cmode == 0) {
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
marked = false, last_marked = NOW()
|
||||
WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
|
||||
} else if ($cmode == 1) {
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
marked = true, last_marked = NOW()
|
||||
WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
|
||||
} else {
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
marked = NOT marked,last_marked = NOW()
|
||||
WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
|
||||
}
|
||||
|
||||
$sth->execute(array_merge($ids, [$_SESSION['uid']]));
|
||||
}
|
||||
|
||||
private function publishArticlesById($ids, $cmode) {
|
||||
|
||||
$ids_qmarks = arr_qmarks($ids);
|
||||
|
||||
if ($cmode == 0) {
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
published = false, last_published = NOW()
|
||||
WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
|
||||
} else if ($cmode == 1) {
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
published = true, last_published = NOW()
|
||||
WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
|
||||
} else {
|
||||
$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
|
||||
published = NOT published,last_published = NOW()
|
||||
WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
|
||||
}
|
||||
|
||||
$sth->execute(array_merge($ids, [$_SESSION['uid']]));
|
||||
}
|
||||
|
||||
function getlinktitlebyid() {
|
||||
$id = clean($_REQUEST['id']);
|
||||
|
||||
$sth = $this->pdo->prepare("SELECT link, title FROM ttrss_entries, ttrss_user_entries
|
||||
WHERE ref_id = ? AND ref_id = id AND owner_uid = ?");
|
||||
$sth->execute([$id, $_SESSION['uid']]);
|
||||
|
||||
if ($row = $sth->fetch()) {
|
||||
$link = $row['link'];
|
||||
$title = $row['title'];
|
||||
|
||||
echo json_encode(array("link" => $link, "title" => $title));
|
||||
} else {
|
||||
echo json_encode(array("error" => "ARTICLE_NOT_FOUND"));
|
||||
}
|
||||
}
|
||||
|
||||
function log() {
|
||||
$msg = clean($_REQUEST['msg']);
|
||||
$file = basename(clean($_REQUEST['file']));
|
||||
$line = (int) clean($_REQUEST['line']);
|
||||
$context = clean($_REQUEST['context']);
|
||||
|
||||
if ($msg) {
|
||||
Logger::get()->log_error(E_USER_WARNING,
|
||||
$msg, 'client-js:' . $file, $line, $context);
|
||||
|
||||
echo json_encode(array("message" => "HOST_ERROR_LOGGED"));
|
||||
} else {
|
||||
echo json_encode(array("error" => "MESSAGE_NOT_FOUND"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function checkforupdates() {
|
||||
$rv = [];
|
||||
if (CHECK_FOR_UPDATES && $_SESSION["access_level"] >= 10) {
|
||||
$newversion = trim(@fetch_file_contents(["url" => "https://raw.githubusercontent.com/Fmstrat/agriget/master/version"]));
|
||||
$oldversion = trim(file_get_contents('version'));
|
||||
if ($newversion != $oldversion) {
|
||||
$rv["id"] = $newversion;
|
||||
$rv["newversion"] = $newversion;
|
||||
$rv["oldversion"] = $oldversion;
|
||||
$rv["docker"] = file_exists('docker');
|
||||
}
|
||||
}
|
||||
print json_encode($rv);
|
||||
}
|
||||
|
||||
}
|
1566
classes/rssutils.php
Executable file
1566
classes/rssutils.php
Executable file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user