]> git.wh0rd.org - tt-rss.git/blobdiff - include/functions.php
add support for http 304 not modified (no timestamp calculation bullshit like last...
[tt-rss.git] / include / functions.php
index 2f9a55654c1a28b5e584f620bea5abbfe1a2ca09..8ea8719cad59ce72582958263287fb4033005075 100644 (file)
@@ -1,6 +1,6 @@
 <?php
        define('EXPECTED_CONFIG_VERSION', 26);
-       define('SCHEMA_VERSION', 130);
+       define('SCHEMA_VERSION', 132);
 
        define('LABEL_BASE_INDEX', -1024);
        define('PLUGIN_FEED_BASE_INDEX', -128);
 
        /**
         * Define a constant if not already defined
-        *
-        * @param string $name The constant name.
-        * @param mixed $value The constant value.
-        * @access public
-        * @return boolean True if defined successfully or not.
         */
        function define_default($name, $value) {
                defined($name) or define($name, $value);
        }
 
-       ///// Some defaults that you can override in config.php //////
+       /* Some tunables you can override in config.php using define(): */
 
        define_default('FEED_FETCH_TIMEOUT', 45);
        // How may seconds to wait for response when requesting feed from a site
        define_default('FILE_FETCH_CONNECT_TIMEOUT', 15);
        // How many seconds to wait for initial response from website when
        // fetching files from remote sites
+       define_default('DAEMON_UPDATE_LOGIN_LIMIT', 30);
+       // stop updating feeds if users haven't logged in for X days
+       define_default('DAEMON_FEED_LIMIT', 500);
+       // feed limit for one update batch
+       define_default('DAEMON_SLEEP_INTERVAL', 120);
+       // default sleep interval between feed updates (sec)
+       define_default('MIN_CACHE_FILE_SIZE', 1024);
+       // do not cache files smaller than that (bytes)
+       define_default('CACHE_MAX_DAYS', 7);
+       // max age in days for various automatically cached (temporary) files
+
+       /* tunables end here */
 
        if (DB_TYPE == "pgsql") {
                define('SUBSTRING_FOR_DATE', 'SUBSTRING_FOR_DATE');
 
        require_once 'db-prefs.php';
        require_once 'version.php';
-       require_once 'labels.php';
        require_once 'controls.php';
 
        define('SELF_USER_AGENT', 'Tiny Tiny RSS/' . VERSION . ' (http://tt-rss.org/)');
        ini_set('user_agent', SELF_USER_AGENT);
 
-       require_once 'lib/pubsubhubbub/Publisher.php';
-
        $schema_version = false;
 
        function _debug_suppress($suppress) {
                global $fetch_last_error_code;
                global $fetch_last_error_content;
                global $fetch_last_content_type;
+               global $fetch_last_modified;
                global $fetch_curl_used;
 
                $fetch_last_error = false;
                $fetch_last_error_content = "";
                $fetch_last_content_type = "";
                $fetch_curl_used = false;
+               $fetch_last_modified = "";
 
                if (!is_array($options)) {
 
                        // falling back on compatibility shim
-                       $option_names = [ "url", "type", "login", "pass", "post_query", "timeout", "timestamp", "useragent" ];
+                       $option_names = [ "url", "type", "login", "pass", "post_query", "timeout", "last_modified", "useragent" ];
                        $tmp = [];
 
                        for ($i = 0; $i < func_num_args(); $i++) {
                $pass = isset($options["pass"]) ? $options["pass"] : false;
                $post_query = isset($options["post_query"]) ? $options["post_query"] : false;
                $timeout = isset($options["timeout"]) ? $options["timeout"] : false;
-               $timestamp = isset($options["timestamp"]) ? $options["timestamp"] : 0;
+               $last_modified = isset($options["last_modified"]) ? $options["last_modified"] : "";
                $useragent = isset($options["useragent"]) ? $options["useragent"] : false;
                $followlocation = isset($options["followlocation"]) ? $options["followlocation"] : true;
 
 
                        $ch = curl_init($url);
 
-                       if ($timestamp && !$post_query) {
+                       if ($last_modified && !$post_query) {
                                curl_setopt($ch, CURLOPT_HTTPHEADER,
-                                       array("If-Modified-Since: ".gmdate('D, d M Y H:i:s \G\M\T', $timestamp)));
+                                       array("If-Modified-Since: $last_modified"));
                        }
 
                        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout ? $timeout : FILE_FETCH_CONNECT_TIMEOUT);
                        curl_setopt($ch, CURLOPT_MAXREDIRS, 20);
                        curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
                        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+                       curl_setopt($ch, CURLOPT_HEADER, true);
                        curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
                        curl_setopt($ch, CURLOPT_USERAGENT, $useragent ? $useragent :
                                SELF_USER_AGENT);
                        if ($login && $pass)
                                curl_setopt($ch, CURLOPT_USERPWD, "$login:$pass");
 
-                       $contents = @curl_exec($ch);
+                       $ret = @curl_exec($ch);
+
+                       $headers_length = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
+                       $headers = explode("\r\n", substr($ret, 0, $headers_length));
+                       $contents = substr($ret, $headers_length);
+
+                       foreach ($headers as $header) {
+                               list ($key, $value) = explode(": ", $header);
+
+                               if (strtolower($key) == "last-modified") {
+                                       $fetch_last_modified = $value;
+                               }
+                       }
 
                        if (curl_errno($ch) === 23 || curl_errno($ch) === 61) {
                                curl_setopt($ch, CURLOPT_ENCODING, 'none');
                                $contents = @curl_exec($ch);
                        }
 
-                       if ($contents === false) {
-                               $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
-                               curl_close($ch);
-                               return false;
-                       }
-
                        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                        $fetch_last_content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
 
                                return false;
                        }
 
+                       if (!$contents) {
+                               $fetch_last_error = curl_errno($ch) . " " . curl_error($ch);
+                               curl_close($ch);
+                               return false;
+                       }
+
+                       /*$fetch_last_modified = curl_getinfo($ch, CURLINFO_FILETIME);
+
+                       if ($fetch_last_modified != -1) {
+                               echo date("Y-m-d H:i:s", $fetch_last_modified); die;
+                       }*/
+
                        curl_close($ch);
 
                        return $contents;
 
                        // TODO: should this support POST requests or not? idk
 
-                       if (!$post_query && $timestamp) {
+                       if (!$post_query && $last_modified) {
                                 $context = stream_context_create(array(
                                          'http' => array(
                                                        'method' => 'GET',
                                                    'ignore_errors' => true,
                                                    'timeout' => $timeout ? $timeout : FILE_FETCH_TIMEOUT,
                                                        'protocol_version'=> 1.1,
-                                                       'header' => "If-Modified-Since: ".gmdate("D, d M Y H:i:s \\G\\M\\T\r\n", $timestamp)
-                                         )));
+                                                       'header' => "If-Modified-Since: $last_modified\r\n")
+                                         ));
                        } else {
                                 $context = stream_context_create(array(
                                          'http' => array(
 
                        if (isset($http_response_header) && is_array($http_response_header)) {
                                foreach ($http_response_header as $h) {
-                                       if (substr(strtolower($h), 0, 13) == 'content-type:') {
-                                               $fetch_last_content_type = substr($h, 14);
+                                       list ($key, $value) = explode(": ", $h);
+
+                                       $key = strtolower($key);
+
+                                       if ($key == 'content-type') {
+                                               $fetch_last_content_type = $value;
                                                // don't abort here b/c there might be more than one
                                                // e.g. if we were being redirected -- last one is the right one
+                                       } else if ($key == 'last-modified') {
+                                               $fetch_last_modified = $value;
                                        }
 
                                        if (substr(strtolower($h), 0, 7) == 'http/1.') {
                }
        }
 
-       function getAllCounters() {
-               $data = getGlobalCounters();
-
-               $data = array_merge($data, getVirtCounters());
-               $data = array_merge($data, getLabelCounters());
-               $data = array_merge($data, getFeedCounters());
-               $data = array_merge($data, getCategoryCounters());
-
-               return $data;
-       }
-
-       function getCategoryCounters() {
-               $ret_arr = array();
-
-               /* Labels category */
-
-               $cv = array("id" => -2, "kind" => "cat",
-                       "counter" => Feeds::getCategoryUnread(-2));
-
-               array_push($ret_arr, $cv);
-
-               $result = db_query("SELECT 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 = id AND
-                       ttrss_cat_counters_cache.owner_uid = ttrss_feed_categories.owner_uid AND
-                       ttrss_feed_categories.owner_uid = " . $_SESSION["uid"]);
-
-               while ($line = db_fetch_assoc($result)) {
-                       $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;
-       }
-
        function getFeedUnread($feed, $is_cat = false) {
                return Feeds::getFeedArticles($feed, $is_cat, true, $_SESSION["uid"]);
        }
 
-       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);
-
-               $result = db_query("SELECT COUNT(id) AS fn FROM
-                       ttrss_feeds WHERE owner_uid = " . $_SESSION["uid"]);
-
-               $subscribed_feeds = db_fetch_result($result, 0, "fn");
-
-               $cv = array("id" => "subscribed-feeds",
-                       "counter" => (int) $subscribed_feeds);
-
-               array_push($ret_arr, $cv);
-
-               return $ret_arr;
-       }
-
-       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;
-       }
-
-       function getLabelCounters($descriptions = false) {
-
-               $ret_arr = array();
-
-               $owner_uid = $_SESSION["uid"];
-
-               $result = db_query("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 = $owner_uid AND u1.owner_uid = $owner_uid
-                               GROUP BY ttrss_labels2.id,
-                                       ttrss_labels2.caption");
-
-               while ($line = db_fetch_assoc($result)) {
-
-                       $id = 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;
-       }
-
-       function getFeedCounters($active_feed = false) {
-
-               $ret_arr = array();
-
-               $query = "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 = ".$_SESSION["uid"]."
-                               AND ttrss_counters_cache.owner_uid = ttrss_feeds.owner_uid
-                               AND ttrss_counters_cache.feed_id = id";
-
-               $result = db_query($query);
-
-               while ($line = db_fetch_assoc($result)) {
-
-                       $id = $line["id"];
-                       $count = $line["count"];
-                       $last_error = htmlspecialchars($line["last_error"]);
-
-                       $last_updated = make_local_datetime($line['last_updated'], false);
-
-                       $has_img = feed_has_icon($id);
-
-                       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;
-       }
 
        /*function get_pgsql_version() {
                $result = db_query("SELECT version() AS version");
                $params["icon_cross"] = base64_img("images/cross.png");
                $params["icon_indicator_white"] = base64_img("images/indicator_white.gif");
 
+               $params["labels"] = Labels::get_all_labels($_SESSION["uid"]);
+
                return $params;
        }
 
                $data['dep_ts'] = calculate_dep_timestamp();
                $data['reload_on_ts_change'] = !defined('_NO_RELOAD_ON_TS_CHANGE');
 
+               $data["labels"] = Labels::get_all_labels($_SESSION["uid"]);
 
                if (CHECK_FOR_UPDATES && !$disable_update_check && $_SESSION["last_version_check"] + 86400 + rand(-1000, 1000) < time()) {
                        $update_result = @check_for_update();
                $doc->loadHTML($charset_hack . $res);
                $xpath = new DOMXPath($doc);
 
-               $ttrss_uses_https = parse_url(get_self_url_prefix(), PHP_URL_SCHEME) === 'https';
-               $rewrite_base_url = $site_url ? $site_url : SELF_URL_PATH;
+               $rewrite_base_url = $site_url ? $site_url : get_self_url_prefix();
 
                $entries = $xpath->query('(//a[@href]|//img[@src]|//video/source[@src]|//audio/source[@src])');
 
                                if ($entry->hasAttribute('src')) {
                                        $is_https_url = parse_url($entry->getAttribute('src'), PHP_URL_SCHEME) === 'https';
 
-                                       if ($ttrss_uses_https && !$is_https_url) {
+                                       if (is_prefix_https() && !$is_https_url) {
 
                                                if ($entry->hasAttribute('srcset')) {
                                                        $entry->removeAttribute('srcset');
                        if (!iframe_whitelisted($entry)) {
                                $entry->setAttribute('sandbox', 'allow-scripts');
                        } else {
-                               if ($_SERVER['HTTPS'] == "on") {
+                               if (is_prefix_https()) {
                                        $entry->setAttribute("src",
                                                str_replace("http://", "https://",
                                                        $entry->getAttribute("src")));
                return $tag;
        }
 
+       function is_server_https() {
+               return (!empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] != 'off')) || $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https';
+       }
+
+       function is_prefix_https() {
+               return parse_url(SELF_URL_PATH, PHP_URL_SCHEME) == 'https';
+       }
+
+       // this returns SELF_URL_PATH sans ending slash
        function get_self_url_prefix() {
                if (strrpos(SELF_URL_PATH, "/") === strlen(SELF_URL_PATH)-1) {
                        return substr(SELF_URL_PATH, 0, strlen(SELF_URL_PATH)-1);
                }
        }
 
-       /**
-        * Compute the Mozilla Firefox feed adding URL from server HOST and REQUEST_URI.
-        *
-        * @return string The Mozilla Firefox feed adding URL.
-        */
-       function add_feed_url() {
-               //$url_path = ($_SERVER['HTTPS'] != "on" ? 'http://' :  'https://') . $_SERVER["HTTP_HOST"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
-
-               $url_path = get_self_url_prefix() .
-                       "/public.php?op=subscribe&feed_url=%s";
-               return $url_path;
-       } // function add_feed_url
-
        function encrypt_password($pass, $salt = '', $mode2 = false) {
                if ($salt && $mode2) {
                        return "MODE2:" . hash('sha256', $salt . $pass);
        function load_filters($feed_id, $owner_uid) {
                $filters = array();
 
-               $cat_id = (int)getFeedCategory($feed_id);
+               $cat_id = (int)Feeds::getFeedCategory($feed_id);
 
                if ($cat_id == 0)
                        $null_cat_qpart = "cat_id IS NULL OR";
                $result = db_query("SELECT * FROM ttrss_filters2 WHERE
                                owner_uid = $owner_uid AND enabled = true ORDER BY order_id, title");
 
-               $check_cats = join(",", array_merge(
+               $check_cats = array_merge(
                        Feeds::getParentCategories($cat_id, $owner_uid),
-                       array($cat_id)));
+                       [$cat_id]);
+
+               $check_cats_str = join(",", $check_cats);
+               $check_cats_fullids = array_map(function($a) { return "CAT:$a"; }, $check_cats);
 
                while ($line = db_fetch_assoc($result)) {
                        $filter_id = $line["id"];
 
+            $match_any_rule = sql_bool_to_bool($line["match_any_rule"]);
+
                        $result2 = db_query("SELECT
-                                       r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, t.name AS type_name
+                                       r.reg_exp, r.inverse, r.feed_id, r.cat_id, r.cat_filter, r.match_on, t.name AS type_name
                                        FROM ttrss_filters2_rules AS r,
                                        ttrss_filter_types AS t
                                        WHERE
-                                               ($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats)) AND
-                                               (feed_id IS NULL OR feed_id = '$feed_id') AND
+                                           (match_on IS NOT NULL OR
+                                                 (($null_cat_qpart (cat_id IS NULL AND cat_filter = false) OR cat_id IN ($check_cats_str)) AND
+                                                 (feed_id IS NULL OR feed_id = '$feed_id'))) AND
                                                filter_type = t.id AND filter_id = '$filter_id'");
 
                        $rules = array();
                        while ($rule_line = db_fetch_assoc($result2)) {
        #                               print_r($rule_line);
 
-                               $rule = array();
-                               $rule["reg_exp"] = $rule_line["reg_exp"];
-                               $rule["type"] = $rule_line["type_name"];
-                               $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
+                if ($rule_line["match_on"]) {
+                    $match_on = json_decode($rule_line["match_on"], true);
 
-                               array_push($rules, $rule);
-                       }
+                    if (in_array("0", $match_on) || in_array($feed_id, $match_on) || count(array_intersect($check_cats_fullids, $match_on)) > 0) {
 
-                       $result2 = db_query("SELECT a.action_param,t.name AS type_name
-                                       FROM ttrss_filters2_actions AS a,
-                                       ttrss_filter_actions AS t
-                                       WHERE
-                                               action_id = t.id AND filter_id = '$filter_id'");
+                        $rule = array();
+                        $rule["reg_exp"] = $rule_line["reg_exp"];
+                        $rule["type"] = $rule_line["type_name"];
+                        $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
+
+                        array_push($rules, $rule);
+                    } else if (!$match_any_rule) {
+                        // this filter contains a rule that doesn't match to this feed/category combination
+                        // thus filter has to be rejected
 
-                       while ($action_line = db_fetch_assoc($result2)) {
-       #                               print_r($action_line);
+                        $rules = [];
+                        break;
+                    }
 
-                               $action = array();
-                               $action["type"] = $action_line["type_name"];
-                               $action["param"] = $action_line["action_param"];
+                } else {
 
-                               array_push($actions, $action);
+                    $rule = array();
+                    $rule["reg_exp"] = $rule_line["reg_exp"];
+                    $rule["type"] = $rule_line["type_name"];
+                    $rule["inverse"] = sql_bool_to_bool($rule_line["inverse"]);
+
+                    array_push($rules, $rule);
+                }
                        }
 
+                       if (count($rules) > 0) {
+                $result2 = db_query("SELECT a.action_param,t.name AS type_name
+                        FROM ttrss_filters2_actions AS a,
+                        ttrss_filter_actions AS t
+                        WHERE
+                            action_id = t.id AND filter_id = '$filter_id'");
+
+                while ($action_line = db_fetch_assoc($result2)) {
+                    #                          print_r($action_line);
+
+                    $action = array();
+                    $action["type"] = $action_line["type_name"];
+                    $action["param"] = $action_line["action_param"];
+
+                    array_push($actions, $action);
+                }
+            }
 
                        $filter = array();
                        $filter["match_any_rule"] = sql_bool_to_bool($line["match_any_rule"]);
                return null;
        }
 
-       function getFeedCategory($feed) {
-               $result = db_query("SELECT cat_id FROM ttrss_feeds
-                               WHERE id = '$feed'");
-
-               if (db_num_rows($result) > 0) {
-                       return db_fetch_result($result, 0, "cat_id");
-               } else {
-                       return false;
-               }
-
-       }
-
        function implements_interface($class, $interface) {
                return in_array($interface, class_implements($class));
        }
        function init_js_translations() {
 
                print 'var T_messages = new Object();
-       
+
                        function __(msg) {
                                if (T_messages[msg]) {
                                        return T_messages[msg];
                                        return msg;
                                }
                        }
-       
+
                        function ngettext(msg1, msg2, n) {
                                return __((parseInt(n) > 1) ? msg2 : msg1);
                        }';
                }
        }
 
-       function label_to_feed_id($label) {
-               return LABEL_BASE_INDEX - 1 - abs($label);
-       }
-
-       function feed_to_label_id($feed) {
-               return LABEL_BASE_INDEX - 1 + abs($feed);
-       }
-
        function get_theme_path($theme) {
                $check = "themes/$theme";
                if (file_exists($check)) return $check;