diff options
Diffstat (limited to 'classes')
-rw-r--r-- | classes/digest.php | 10 | ||||
-rw-r--r-- | classes/diskcache.php | 246 | ||||
-rwxr-xr-x | classes/feeds.php | 23 | ||||
-rwxr-xr-x | classes/handler/public.php | 12 | ||||
-rw-r--r-- | classes/labels.php | 6 | ||||
-rw-r--r-- | classes/opml.php | 20 | ||||
-rwxr-xr-x | classes/pluginhost.php | 19 | ||||
-rwxr-xr-x | classes/pref/feeds.php | 11 | ||||
-rwxr-xr-x | classes/pref/filters.php | 52 | ||||
-rw-r--r-- | classes/pref/prefs.php | 12 | ||||
-rwxr-xr-x | classes/rssutils.php | 88 | ||||
-rw-r--r-- | classes/templator.php | 21 |
12 files changed, 400 insertions, 120 deletions
diff --git a/classes/digest.php b/classes/digest.php index c9e9f24e7..9101b52f4 100644 --- a/classes/digest.php +++ b/classes/digest.php @@ -90,13 +90,11 @@ class Digest static function prepare_headlines_digest($user_id, $days = 1, $limit = 1000) { - require_once "lib/MiniTemplator.class.php"; + $tpl = new Templator(); + $tpl_t = new Templator(); - $tpl = new MiniTemplator; - $tpl_t = new MiniTemplator; - - $tpl->readTemplateFromFile("templates/digest_template_html.txt"); - $tpl_t->readTemplateFromFile("templates/digest_template.txt"); + $tpl->readTemplateFromFile("digest_template_html.txt"); + $tpl_t->readTemplateFromFile("digest_template.txt"); $user_tz_string = get_pref('USER_TIMEZONE', $user_id); $local_ts = convert_timestamp(time(), 'UTC', $user_tz_string); diff --git a/classes/diskcache.php b/classes/diskcache.php index 7e4a8335d..be1aea97f 100644 --- a/classes/diskcache.php +++ b/classes/diskcache.php @@ -2,6 +2,194 @@ class DiskCache { private $dir; + // https://stackoverflow.com/a/53662733 + private $mimeMap = [ + 'video/3gpp2' => '3g2', + 'video/3gp' => '3gp', + 'video/3gpp' => '3gp', + 'application/x-compressed' => '7zip', + 'audio/x-acc' => 'aac', + 'audio/ac3' => 'ac3', + 'application/postscript' => 'ai', + 'audio/x-aiff' => 'aif', + 'audio/aiff' => 'aif', + 'audio/x-au' => 'au', + 'video/x-msvideo' => 'avi', + 'video/msvideo' => 'avi', + 'video/avi' => 'avi', + 'application/x-troff-msvideo' => 'avi', + 'application/macbinary' => 'bin', + 'application/mac-binary' => 'bin', + 'application/x-binary' => 'bin', + 'application/x-macbinary' => 'bin', + 'image/bmp' => 'bmp', + 'image/x-bmp' => 'bmp', + 'image/x-bitmap' => 'bmp', + 'image/x-xbitmap' => 'bmp', + 'image/x-win-bitmap' => 'bmp', + 'image/x-windows-bmp' => 'bmp', + 'image/ms-bmp' => 'bmp', + 'image/x-ms-bmp' => 'bmp', + 'application/bmp' => 'bmp', + 'application/x-bmp' => 'bmp', + 'application/x-win-bitmap' => 'bmp', + 'application/cdr' => 'cdr', + 'application/coreldraw' => 'cdr', + 'application/x-cdr' => 'cdr', + 'application/x-coreldraw' => 'cdr', + 'image/cdr' => 'cdr', + 'image/x-cdr' => 'cdr', + 'zz-application/zz-winassoc-cdr' => 'cdr', + 'application/mac-compactpro' => 'cpt', + 'application/pkix-crl' => 'crl', + 'application/pkcs-crl' => 'crl', + 'application/x-x509-ca-cert' => 'crt', + 'application/pkix-cert' => 'crt', + 'text/css' => 'css', + 'text/x-comma-separated-values' => 'csv', + 'text/comma-separated-values' => 'csv', + 'application/vnd.msexcel' => 'csv', + 'application/x-director' => 'dcr', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', + 'application/x-dvi' => 'dvi', + 'message/rfc822' => 'eml', + 'application/x-msdownload' => 'exe', + 'video/x-f4v' => 'f4v', + 'audio/x-flac' => 'flac', + 'video/x-flv' => 'flv', + 'image/gif' => 'gif', + 'application/gpg-keys' => 'gpg', + 'application/x-gtar' => 'gtar', + 'application/x-gzip' => 'gzip', + 'application/mac-binhex40' => 'hqx', + 'application/mac-binhex' => 'hqx', + 'application/x-binhex40' => 'hqx', + 'application/x-mac-binhex40' => 'hqx', + 'text/html' => 'html', + 'image/x-icon' => 'ico', + 'image/x-ico' => 'ico', + 'image/vnd.microsoft.icon' => 'ico', + 'text/calendar' => 'ics', + 'application/java-archive' => 'jar', + 'application/x-java-application' => 'jar', + 'application/x-jar' => 'jar', + 'image/jp2' => 'jp2', + 'video/mj2' => 'jp2', + 'image/jpx' => 'jp2', + 'image/jpm' => 'jp2', + 'image/jpeg' => 'jpg', + 'image/pjpeg' => 'jpg', + 'application/x-javascript' => 'js', + 'application/json' => 'json', + 'text/json' => 'json', + 'application/vnd.google-earth.kml+xml' => 'kml', + 'application/vnd.google-earth.kmz' => 'kmz', + 'text/x-log' => 'log', + 'audio/x-m4a' => 'm4a', + 'audio/mp4' => 'm4a', + 'application/vnd.mpegurl' => 'm4u', + 'audio/midi' => 'mid', + 'application/vnd.mif' => 'mif', + 'video/quicktime' => 'mov', + 'video/x-sgi-movie' => 'movie', + 'audio/mpeg' => 'mp3', + 'audio/mpg' => 'mp3', + 'audio/mpeg3' => 'mp3', + 'audio/mp3' => 'mp3', + 'video/mp4' => 'mp4', + 'video/mpeg' => 'mpeg', + 'application/oda' => 'oda', + 'audio/ogg' => 'ogg', + 'video/ogg' => 'ogg', + 'application/ogg' => 'ogg', + 'font/otf' => 'otf', + 'application/x-pkcs10' => 'p10', + 'application/pkcs10' => 'p10', + 'application/x-pkcs12' => 'p12', + 'application/x-pkcs7-signature' => 'p7a', + 'application/pkcs7-mime' => 'p7c', + 'application/x-pkcs7-mime' => 'p7c', + 'application/x-pkcs7-certreqresp' => 'p7r', + 'application/pkcs7-signature' => 'p7s', + 'application/pdf' => 'pdf', + 'application/octet-stream' => 'pdf', + 'application/x-x509-user-cert' => 'pem', + 'application/x-pem-file' => 'pem', + 'application/pgp' => 'pgp', + 'application/x-httpd-php' => 'php', + 'application/php' => 'php', + 'application/x-php' => 'php', + 'text/php' => 'php', + 'text/x-php' => 'php', + 'application/x-httpd-php-source' => 'php', + 'image/png' => 'png', + 'image/x-png' => 'png', + 'application/powerpoint' => 'ppt', + 'application/vnd.ms-powerpoint' => 'ppt', + 'application/vnd.ms-office' => 'ppt', + 'application/msword' => 'ppt', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', + 'application/x-photoshop' => 'psd', + 'image/vnd.adobe.photoshop' => 'psd', + 'audio/x-realaudio' => 'ra', + 'audio/x-pn-realaudio' => 'ram', + 'application/x-rar' => 'rar', + 'application/rar' => 'rar', + 'application/x-rar-compressed' => 'rar', + 'audio/x-pn-realaudio-plugin' => 'rpm', + 'application/x-pkcs7' => 'rsa', + 'text/rtf' => 'rtf', + 'text/richtext' => 'rtx', + 'video/vnd.rn-realvideo' => 'rv', + 'application/x-stuffit' => 'sit', + 'application/smil' => 'smil', + 'text/srt' => 'srt', + 'image/svg+xml' => 'svg', + 'application/x-shockwave-flash' => 'swf', + 'application/x-tar' => 'tar', + 'application/x-gzip-compressed' => 'tgz', + 'image/tiff' => 'tiff', + 'font/ttf' => 'ttf', + 'text/plain' => 'txt', + 'text/x-vcard' => 'vcf', + 'application/videolan' => 'vlc', + 'text/vtt' => 'vtt', + 'audio/x-wav' => 'wav', + 'audio/wave' => 'wav', + 'audio/wav' => 'wav', + 'application/wbxml' => 'wbxml', + 'video/webm' => 'webm', + 'image/webp' => 'webp', + 'audio/x-ms-wma' => 'wma', + 'application/wmlc' => 'wmlc', + 'video/x-ms-wmv' => 'wmv', + 'video/x-ms-asf' => 'wmv', + 'font/woff' => 'woff', + 'font/woff2' => 'woff2', + 'application/xhtml+xml' => 'xhtml', + 'application/excel' => 'xl', + 'application/msexcel' => 'xls', + 'application/x-msexcel' => 'xls', + 'application/x-ms-excel' => 'xls', + 'application/x-excel' => 'xls', + 'application/x-dos_ms_excel' => 'xls', + 'application/xls' => 'xls', + 'application/x-xls' => 'xls', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', + 'application/vnd.ms-excel' => 'xlsx', + 'application/xml' => 'xml', + 'text/xml' => 'xml', + 'text/xsl' => 'xsl', + 'application/xspf+xml' => 'xspf', + 'application/x-compress' => 'z', + 'application/x-zip' => 'zip', + 'application/zip' => 'zip', + 'application/x-zip-compressed' => 'zip', + 'application/s-compressed' => 'zip', + 'multipart/x-zip' => 'zip', + 'text/x-scriptzsh' => 'zsh' + ]; + public function __construct($dir) { $this->dir = CACHE_DIR . "/" . clean_filename($dir); } @@ -66,8 +254,22 @@ class DiskCache { return null; } + public function getFakeExtension($filename) { + $mimetype = $this->getMimeType($filename); + + if ($mimetype) + return isset($this->mimeMap[$mimetype]) ? $this->mimeMap[$mimetype] : ""; + else + return ""; + } + public function send($filename) { - header("Content-Disposition: inline; filename=\"$filename\""); + $fake_extension = $this->getFakeExtension($filename); + + if ($fake_extension) + $fake_extension = ".$fake_extension"; + + header("Content-Disposition: inline; filename=\"${filename}${fake_extension}\""); return send_local_file($this->getFullPath($filename)); } @@ -79,6 +281,7 @@ class DiskCache { // check for locally cached (media) URLs and rewrite to local versions // this is called separately after sanitize() and plugin render article hooks to allow // plugins work on original source URLs used before caching + // NOTE: URLs should be already absolutized because this is called after sanitize() static public function rewriteUrls($str) { $res = trim($str); @@ -89,31 +292,44 @@ class DiskCache { $xpath = new DOMXPath($doc); $cache = new DiskCache("images"); - $entries = $xpath->query('(//img[@src]|//picture/source[@src]|//video[@poster]|//video/source[@src]|//audio/source[@src])'); + $entries = $xpath->query('(//img[@src]|//source[@src|@srcset]|//video[@poster|@src])'); $need_saving = false; foreach ($entries as $entry) { + foreach (array('src', 'poster') as $attr) { + if ($entry->hasAttribute($attr)) { + $url = $entry->getAttribute($attr); + $cached_filename = sha1($url); - if ($entry->hasAttribute('src') || $entry->hasAttribute('poster')) { + if ($cache->exists($cached_filename)) { + $url = $cache->getUrl($cached_filename); - // should be already absolutized because this is called after sanitize() - $src = $entry->hasAttribute('poster') ? $entry->getAttribute('poster') : $entry->getAttribute('src'); - $cached_filename = sha1($src); + $entry->setAttribute($attr, $url); + $entry->removeAttribute("srcset"); - if ($cache->exists($cached_filename)) { + $need_saving = true; + } + } + } - $src = $cache->getUrl(sha1($src)); + if ($entry->hasAttribute("srcset")) { + $tokens = explode(",", $entry->getAttribute('srcset')); - if ($entry->hasAttribute('poster')) - $entry->setAttribute('poster', $src); - else { - $entry->setAttribute('src', $src); - $entry->removeAttribute("srcset"); - } + for ($i = 0; $i < count($tokens); $i++) { + $token = trim($tokens[$i]); - $need_saving = true; + list ($url, $width) = explode(" ", $token, 2); + $cached_filename = sha1($url); + + if ($cache->exists($cached_filename)) { + $tokens[$i] = $cache->getUrl($cached_filename) . " " . $width; + + $need_saving = true; + } } + + $entry->setAttribute("srcset", implode(", ", $tokens)); } } diff --git a/classes/feeds.php b/classes/feeds.php index 77add790e..bd2334747 100755 --- a/classes/feeds.php +++ b/classes/feeds.php @@ -2267,6 +2267,24 @@ class Feeds extends Handler_Protected { if (!$not) array_push($search_words, $k); } break; + case "label": + if ($commandpair[1]) { + $label_id = Labels::find_id($commandpair[1], $_SESSION["uid"]); + + if ($label_id) { + array_push($query_keywords, "($not + (ttrss_entries.id IN ( + SELECT article_id FROM ttrss_user_labels2 WHERE + label_id = ".$pdo->quote($label_id).")))"); + } else { + array_push($query_keywords, "(false)"); + } + } else { + array_push($query_keywords, "(UPPER(ttrss_entries.title) $not LIKE UPPER(".$pdo->quote("%$k%").") + OR UPPER(ttrss_entries.content) $not LIKE UPPER(".$pdo->quote("%$k%")."))"); + if (!$not) array_push($search_words, $k); + } + break; case "unread": if ($commandpair[1]) { if ($commandpair[1] == "true") @@ -2323,7 +2341,10 @@ class Feeds extends Handler_Protected { } - $search_query_part = implode("AND", $query_keywords); + if (count($query_keywords) > 0) + $search_query_part = implode("AND", $query_keywords); + else + $search_query_part = "false"; return array($search_query_part, $search_words); } diff --git a/classes/handler/public.php b/classes/handler/public.php index 8c2700012..21430e6cc 100755 --- a/classes/handler/public.php +++ b/classes/handler/public.php @@ -5,8 +5,6 @@ class Handler_Public extends Handler { $limit, $offset, $search, $view_mode = false, $format = 'atom', $order = false, $orig_guid = false, $start_ts = false) { - require_once "lib/MiniTemplator.class.php"; - $note_style = "background-color : #fff7d5; border-width : 1px; ". "padding : 5px; border-style : dashed; border-color : #e7d796;". @@ -80,9 +78,9 @@ class Handler_Public extends Handler { if (!$feed_site_url) $feed_site_url = get_self_url_prefix(); if ($format == 'atom') { - $tpl = new MiniTemplator; + $tpl = new Templator(); - $tpl->readTemplateFromFile("templates/generated_feed.txt"); + $tpl->readTemplateFromFile("generated_feed.txt"); $tpl->setVariable('FEED_TITLE', $feed_title, true); $tpl->setVariable('VERSION', get_version(), true); @@ -1030,11 +1028,9 @@ class Handler_Public extends Handler { $resetpass_link = get_self_url_prefix() . "/public.php?op=forgotpass&hash=" . $resetpass_token . "&login=" . urlencode($login); - require_once "lib/MiniTemplator.class.php"; - - $tpl = new MiniTemplator; + $tpl = new Templator(); - $tpl->readTemplateFromFile("templates/resetpass_link_template.txt"); + $tpl->readTemplateFromFile("resetpass_link_template.txt"); $tpl->setVariable('LOGIN', $login); $tpl->setVariable('RESETPASS_LINK', $resetpass_link); diff --git a/classes/labels.php b/classes/labels.php index 19d060617..7a69a5191 100644 --- a/classes/labels.php +++ b/classes/labels.php @@ -12,7 +12,7 @@ class Labels static function find_id($label, $owner_uid) { $pdo = Db::pdo(); - $sth = $pdo->prepare("SELECT id FROM ttrss_labels2 WHERE caption = ? + $sth = $pdo->prepare("SELECT id FROM ttrss_labels2 WHERE LOWER(caption) = LOWER(?) AND owner_uid = ? LIMIT 1"); $sth->execute([$label, $owner_uid]); @@ -186,7 +186,7 @@ class Labels } $sth = $pdo->prepare("SELECT id FROM ttrss_labels2 - WHERE caption = ? AND owner_uid = ?"); + WHERE LOWER(caption) = LOWER(?) AND owner_uid = ?"); $sth->execute([$caption, $owner_uid]); if (!$sth->fetch()) { @@ -202,4 +202,4 @@ class Labels return $result; } -}
\ No newline at end of file +} diff --git a/classes/opml.php b/classes/opml.php index 48db9a8a3..c4523f83f 100644 --- a/classes/opml.php +++ b/classes/opml.php @@ -125,15 +125,16 @@ class Opml extends Handler_Protected { return $out; } - function opml_export($name, $owner_uid, $hide_private_feeds = false, $include_settings = true) { + function opml_export($filename, $owner_uid, $hide_private_feeds = false, $include_settings = true, $file_output = false) { 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"); - } + if (!$file_output) + if (!isset($_REQUEST["debug"])) { + header("Content-type: application/xml+opml"); + header("Content-Disposition: attachment; filename=$filename"); + } else { + header("Content-type: text/xml"); + } $out = "<?xml version=\"1.0\" encoding=\"utf-8\"?".">"; @@ -288,7 +289,10 @@ class Opml extends Handler_Protected { 'return str_repeat("\t", intval(strlen($matches[0])/2));'), $res); */ - print $res; + if ($file_output) + return file_put_contents($filename, $res) > 0; + else + print $res; } // Import diff --git a/classes/pluginhost.php b/classes/pluginhost.php index 6158880f2..0ab979c4b 100755 --- a/classes/pluginhost.php +++ b/classes/pluginhost.php @@ -1,6 +1,9 @@ <?php class PluginHost { private $pdo; + /* separate handle for plugin data so transaction while saving wouldn't clash with possible main + tt-rss code transactions; only initialized when first needed */ + private $pdo_data; private $hooks = array(); private $plugins = array(); private $handlers = array(); @@ -62,6 +65,7 @@ class PluginHost { const HOOK_ARTICLE_IMAGE = 42; const HOOK_FEED_TREE = 43; const HOOK_IFRAME_WHITELISTED = 44; + const HOOK_ENCLOSURE_IMPORTED = 45; const KIND_ALL = 1; const KIND_SYSTEM = 2; @@ -73,7 +77,6 @@ class PluginHost { function __construct() { $this->pdo = Db::pdo(); - $this->storage = array(); } @@ -361,9 +364,13 @@ class PluginHost { private function save_data($plugin) { if ($this->owner_uid) { - $this->pdo->beginTransaction(); - $sth = $this->pdo->prepare("SELECT id FROM ttrss_plugin_storage WHERE + if (!$this->pdo_data) + $this->pdo_data = Db::instance()->pdo_connect(); + + $this->pdo_data->beginTransaction(); + + $sth = $this->pdo_data->prepare("SELECT id FROM ttrss_plugin_storage WHERE owner_uid= ? AND name = ?"); $sth->execute([$this->owner_uid, $plugin]); @@ -373,18 +380,18 @@ class PluginHost { $content = serialize($this->storage[$plugin]); if ($sth->fetch()) { - $sth = $this->pdo->prepare("UPDATE ttrss_plugin_storage SET content = ? + $sth = $this->pdo_data->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 + $sth = $this->pdo_data->prepare("INSERT INTO ttrss_plugin_storage (name,owner_uid,content) VALUES (?, ?, ?)"); $sth->execute([$plugin, $this->owner_uid, (string)$content]); } - $this->pdo->commit(); + $this->pdo_data->commit(); } } diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php index 6d7295beb..00d2e87ea 100755 --- a/classes/pref/feeds.php +++ b/classes/pref/feeds.php @@ -449,7 +449,7 @@ class Pref_Feeds extends Handler_Protected { if ($row = $sth->fetch()) { @unlink(ICONS_DIR . "/$feed_id.ico"); - $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET favicon_avg_color = NULL + $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET favicon_avg_color = NULL, favicon_last_checked = '1970-01-01' where id = ?"); $sth->execute([$feed_id]); } @@ -1637,6 +1637,8 @@ class Pref_Feeds extends Handler_Protected { } function batchSubscribe() { + print "<form onsubmit='return false'>"; + print_hidden("op", "pref-feeds"); print_hidden("method", "batchaddfeeds"); @@ -1645,7 +1647,7 @@ class Pref_Feeds extends Handler_Protected { print "<textarea style='font-size : 12px; width : 98%; height: 200px;' - dojoType='dijit.form.SimpleTextarea' name='feeds'></textarea>"; + dojoType='fox.form.ValidationTextArea' required='1' name='feeds'></textarea>"; if (get_pref('ENABLE_FEED_CATS')) { print "<fieldset>"; @@ -1675,9 +1677,12 @@ class Pref_Feeds extends Handler_Protected { print "</fieldset>"; print "<footer> - <button dojoType='dijit.form.Button' type='submit' class='alt-primary'>".__('Subscribe')."</button> + <button dojoType='dijit.form.Button' onclick=\"return dijit.byId('batchSubDlg').execute()\" type='submit' class='alt-primary'>". + __('Subscribe')."</button> <button dojoType='dijit.form.Button' onclick=\"return dijit.byId('batchSubDlg').hide()\">".__('Cancel')."</button> </footer>"; + + print "</form>"; } function batchAddFeeds() { diff --git a/classes/pref/filters.php b/classes/pref/filters.php index a3a0ce77f..dba2568f2 100755 --- a/classes/pref/filters.php +++ b/classes/pref/filters.php @@ -3,7 +3,7 @@ class Pref_Filters extends Handler_Protected { function csrf_ignore($method) { $csrf_ignored = array("index", "getfiltertree", "edit", "newfilter", "newrule", - "newaction", "savefilterorder"); + "newaction", "savefilterorder", "testfilterdlg"); return array_search($method, $csrf_ignored) !== false; } @@ -159,22 +159,19 @@ class Pref_Filters extends Handler_Protected { print json_encode($rv); } - function testFilter() { + function testFilterDlg() { + ?> + <div> + <img id='prefFilterLoadingIndicator' src='images/indicator_tiny.gif'> + <span id='prefFilterProgressMsg'>Looking for articles...</span> + </div> - if (isset($_REQUEST["offset"])) return $this->testFilterDo(); - - //print __("Articles matching this filter:"); - - print "<div><img id='prefFilterLoadingIndicator' src='images/indicator_tiny.gif'> <span id='prefFilterProgressMsg'>Looking for articles...</span></div>"; - - print "<ul class='panel panel-scrollable list list-unstyled' id='prefFilterTestResultList'>"; - print "</ul>"; - - print "<footer class='text-center'>"; - print "<button dojoType='dijit.form.Button' onclick=\"dijit.byId('filterTestDlg').hide()\">". - __('Close this window')."</button>"; - print "</footer>"; + <ul class='panel panel-scrollable list list-unstyled' id='prefFilterTestResultList'></ul> + <footer class='text-center'> + <button dojoType='dijit.form.Button' onclick="dijit.byId('filterTestDlg').hide()"><?php echo __('Close this window') ?></button> + </footer> + <?php } private function getfilterrules_list($filter_id) { @@ -600,10 +597,6 @@ class Pref_Filters extends Handler_Protected { } function editSave() { - if (clean($_REQUEST["savemode"] && $_REQUEST["savemode"]) == "test") { - return $this->testFilter(); - } - $filter_id = clean($_REQUEST["id"]); $enabled = checkbox_to_sql_bool(clean($_REQUEST["enabled"])); $match_any_rule = checkbox_to_sql_bool(clean($_REQUEST["match_any_rule"])); @@ -714,10 +707,6 @@ class Pref_Filters extends Handler_Protected { } function add() { - if (clean($_REQUEST["savemode"] && $_REQUEST["savemode"]) == "test") { - return $this->testFilter(); - } - $enabled = checkbox_to_sql_bool(clean($_REQUEST["enabled"])); $match_any_rule = checkbox_to_sql_bool(clean($_REQUEST["match_any_rule"])); $title = clean($_REQUEST["title"]); @@ -975,19 +964,18 @@ class Pref_Filters extends Handler_Protected { print "<section>"; - print "<input dojoType=\"dijit.form.ValidationTextBox\" - required=\"true\" id=\"filterDlg_regExp\" - onchange='Filters.filterDlgCheckRegExp(this)' - onblur='Filters.filterDlgCheckRegExp(this)' - onfocus='Filters.filterDlgCheckRegExp(this)' - style=\"font-size : 16px; width : 500px\" - name=\"reg_exp\" value=\"$reg_exp\"/>"; + print "<textarea dojoType='fox.form.ValidationTextArea' + required='true' id='filterDlg_regExp' + ValidRegExp='true' + rows='4' + style='font-size : 14px; width : 490px; word-break: break-all' + name='reg_exp'>$reg_exp</textarea>"; print "<div dojoType='dijit.Tooltip' id='filterDlg_regExp_tip' connectId='filterDlg_regExp' position='below'></div>"; print "<fieldset>"; - print "<label class='checkbox'><input id=\"filterDlg_inverse\" dojoType=\"dijit.form.CheckBox\" - name=\"inverse\" $inverse_checked/> ". + print "<label class='checkbox'><input id='filterDlg_inverse' dojoType='dijit.form.CheckBox' + name='inverse' $inverse_checked/> ". __("Inverse regular expression matching")."</label>"; print "</fieldset>"; diff --git a/classes/pref/prefs.php b/classes/pref/prefs.php index ac16b5971..475cd797f 100644 --- a/classes/pref/prefs.php +++ b/classes/pref/prefs.php @@ -213,11 +213,9 @@ class Pref_Prefs extends Handler_Protected { if ($old_email != $email) { $mailer = new Mailer(); - require_once "lib/MiniTemplator.class.php"; + $tpl = new Templator(); - $tpl = new MiniTemplator; - - $tpl->readTemplateFromFile("templates/mail_change_template.txt"); + $tpl->readTemplateFromFile("mail_change_template.txt"); $tpl->setVariable('LOGIN', $row["login"]); $tpl->setVariable('NEWMAIL', $email); @@ -1087,11 +1085,9 @@ class Pref_Prefs extends Handler_Protected { if ($row = $sth->fetch()) { $mailer = new Mailer(); - require_once "lib/MiniTemplator.class.php"; - - $tpl = new MiniTemplator; + $tpl = new Templator(); - $tpl->readTemplateFromFile("templates/otp_disabled_template.txt"); + $tpl->readTemplateFromFile("otp_disabled_template.txt"); $tpl->setVariable('LOGIN', $row["login"]); $tpl->setVariable('TTRSS_HOST', SELF_URL_PATH); diff --git a/classes/rssutils.php b/classes/rssutils.php index 831ac1baf..dede50ba0 100755 --- a/classes/rssutils.php +++ b/classes/rssutils.php @@ -3,7 +3,12 @@ class RSSUtils { static function calculate_article_hash($article, $pluginhost) { $tmp = ""; + $ignored_fields = [ "feed", "guid", "guid_hashed", "owner_uid", "force_catchup" ]; + foreach ($article as $k => $v) { + if (in_array($k, $ignored_fields)) + continue; + if ($k != "feed" && isset($v)) { $x = strip_tags(is_array($v) ? implode(",", $v) : $v); @@ -469,7 +474,7 @@ class RSSUtils { foreach ($pluginhost->get_hooks(PluginHost::HOOK_FEED_PARSED) as $plugin) { Debug::log("... " . get_class($plugin), Debug::$LOG_VERBOSE); $start = microtime(true); - $plugin->hook_feed_parsed($rss); + $plugin->hook_feed_parsed($rss, $feed); Debug::log(sprintf("=== %.4f (sec)", microtime(true) - $start), Debug::$LOG_VERBOSE); } @@ -586,11 +591,11 @@ class RSSUtils { continue; } + $entry_guid_hashed_compat = 'SHA1:' . sha1("$owner_uid,$entry_guid"); + $entry_guid_hashed = json_encode(["ver" => 2, "uid" => $owner_uid, "hash" => 'SHA1:' . sha1($entry_guid)]); $entry_guid = "$owner_uid,$entry_guid"; - $entry_guid_hashed = 'SHA1:' . sha1($entry_guid); - - Debug::log("guid $entry_guid / $entry_guid_hashed", Debug::$LOG_VERBOSE); + Debug::log("guid $entry_guid (hash: $entry_guid_hashed compat: $entry_guid_hashed_compat)", Debug::$LOG_VERBOSE); $entry_timestamp = (int)$item->get_date(); @@ -632,8 +637,8 @@ class RSSUtils { Debug::log("done collecting data.", Debug::$LOG_VERBOSE); $sth = $pdo->prepare("SELECT id, content_hash, lang FROM ttrss_entries - WHERE guid = ? OR guid = ?"); - $sth->execute([$entry_guid, $entry_guid_hashed]); + WHERE guid IN (?, ?, ?)"); + $sth->execute([$entry_guid, $entry_guid_hashed, $entry_guid_hashed_compat]); if ($row = $sth->fetch()) { $base_entry_id = $row["id"]; @@ -828,8 +833,8 @@ class RSSUtils { RSSUtils::cache_media($entry_content, $site_url); $csth = $pdo->prepare("SELECT id FROM ttrss_entries - WHERE guid = ? OR guid = ?"); - $csth->execute([$entry_guid, $entry_guid_hashed]); + WHERE guid IN (?, ?, ?)"); + $csth->execute([$entry_guid, $entry_guid_hashed, $entry_guid_hashed_compat]); if (!$row = $csth->fetch()) { @@ -874,7 +879,7 @@ class RSSUtils { } - $csth->execute([$entry_guid, $entry_guid_hashed]); + $csth->execute([$entry_guid, $entry_guid_hashed, $entry_guid_hashed_compat]); $entry_ref_id = 0; $entry_int_id = 0; @@ -1032,6 +1037,11 @@ class RSSUtils { if (is_array($encs)) { foreach ($encs as $e) { + + foreach ($pluginhost->get_hooks(PluginHost::HOOK_ENCLOSURE_IMPORTED) as $plugin) { + $e = $plugin->hook_enclosure_imported($e, $feed); + } + $e_item = array( rewrite_relative_url($site_url, $e->link), $e->type, $e->length, $e->title, $e->width, $e->height); @@ -1221,6 +1231,32 @@ class RSSUtils { } } + static function cache_media_url($cache, $url, $site_url) { + $url = rewrite_relative_url($site_url, $url); + $local_filename = sha1($url); + + Debug::log("cache_media: checking $url", Debug::$LOG_VERBOSE); + + if (!$cache->exists($local_filename)) { + Debug::log("cache_media: downloading: $url to $local_filename", Debug::$LOG_VERBOSE); + + global $fetch_last_error_code; + global $fetch_last_error; + + $file_content = fetch_file_contents(array("url" => $url, + "http_referrer" => $url, + "max_size" => MAX_CACHE_FILE_SIZE)); + + if ($file_content) { + $cache->put($local_filename, $file_content); + } else { + Debug::log("cache_media: failed with $fetch_last_error_code: $fetch_last_error"); + } + } else if ($cache->isWritable($local_filename)) { + $cache->touch($local_filename); + } + } + static function cache_media($html, $site_url) { $cache = new DiskCache("images"); @@ -1229,33 +1265,24 @@ class RSSUtils { if ($doc->loadHTML($html)) { $xpath = new DOMXPath($doc); - $entries = $xpath->query('(//img[@src])|(//video/source[@src])|(//audio/source[@src])'); + $entries = $xpath->query('(//img[@src]|//source[@src|@srcset]|//video[@poster|@src])'); foreach ($entries as $entry) { - if ($entry->hasAttribute('src') && strpos($entry->getAttribute('src'), "data:") !== 0) { - $src = rewrite_relative_url($site_url, $entry->getAttribute('src')); - - $local_filename = sha1($src); - - Debug::log("cache_media: checking $src", Debug::$LOG_VERBOSE); + foreach (array('src', 'poster') as $attr) { + if ($entry->hasAttribute($attr) && strpos($entry->getAttribute($attr), "data:") !== 0) { + RSSUtils::cache_media_url($cache, $entry->getAttribute($attr), $site_url); + } + } - if (!$cache->exists($local_filename)) { - Debug::log("cache_media: downloading: $src to $local_filename", Debug::$LOG_VERBOSE); + if ($entry->hasAttribute("srcset")) { + $tokens = explode(",", $entry->getAttribute('srcset')); - global $fetch_last_error_code; - global $fetch_last_error; + for ($i = 0; $i < count($tokens); $i++) { + $token = trim($tokens[$i]); - $file_content = fetch_file_contents(array("url" => $src, - "http_referrer" => $src, - "max_size" => MAX_CACHE_FILE_SIZE)); + list ($url, $width) = explode(" ", $token, 2); - if ($file_content) { - $cache->put($local_filename, $file_content); - } else { - Debug::log("cache_media: failed with $fetch_last_error_code: $fetch_last_error"); - } - } else if ($cache->isWritable($local_filename)) { - $cache->touch($local_filename); + RSSUtils::cache_media_url($cache, $url, $site_url); } } } @@ -1343,6 +1370,7 @@ class RSSUtils { foreach ($filter["rules"] as $rule) { $match = false; $reg_exp = str_replace('/', '\/', $rule["reg_exp"]); + $reg_exp = str_replace("\n", "", $reg_exp); // reg_exp may be formatted with CRs now because of textarea, we need to strip those $rule_inverse = $rule["inverse"]; if (!$reg_exp) diff --git a/classes/templator.php b/classes/templator.php new file mode 100644 index 000000000..3d270f837 --- /dev/null +++ b/classes/templator.php @@ -0,0 +1,21 @@ +<?php +require_once "lib/MiniTemplator.class.php"; + +class Templator extends MiniTemplator { + + /* this reads tt-rss template from templates.local/ or templates/ if only base filename is given */ + function readTemplateFromFile ($fileName) { + if (strpos($fileName, "/") === FALSE) { + + $fileName = basename($fileName); + + if (file_exists("templates.local/$fileName")) + return parent::readTemplateFromFile("templates.local/$fileName"); + else + return parent::readTemplateFromFile("templates/$fileName"); + + } else { + return parent::readTemplateFromFile($fileName); + } + } +} |