Automated build for v0.01
This commit is contained in:
		
						commit
						791b998489
					
				
					 2771 changed files with 222096 additions and 0 deletions
				
			
		
							
								
								
									
										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
											
										
									
								
							
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Fmstrat
						Fmstrat