/** * If link is an image (Twitpic/Twitgoo/Yfrog/Flickr for now), insert direct path to thumb as expanded url. * @TODO Move image thumbnail processng to Expand URLs plugin. * @param Logger $logger * @param str $tweet * @param Array $urls */ public static function processTweetURLs($logger, $tweet, $urls = null) { $link_dao = DAOFactory::getDAO('LinkDAO'); if (!$urls) { $urls = Post::extractURLs($tweet['post_text']); } foreach ($urls as $u) { $logger->logInfo("processing url: {$u}", __METHOD__ . ',' . __LINE__); $is_image = 0; $title = ''; $eurl = ''; if (substr($u, 0, strlen('http://twitpic.com/')) == 'http://twitpic.com/') { $eurl = 'http://twitpic.com/show/thumb/' . substr($u, strlen('http://twitpic.com/')); $is_image = 1; } elseif (substr($u, 0, strlen('http://yfrog.com/')) == 'http://yfrog.com/') { $eurl = $u . '.th.jpg'; $is_image = 1; } elseif (substr($u, 0, strlen('http://twitgoo.com/')) == 'http://twitgoo.com/') { $eurl = 'http://twitgoo.com/show/thumb/' . substr($u, strlen('http://twitgoo.com/')); $is_image = 1; } elseif (substr($u, 0, strlen('http://picplz.com/')) == 'http://picplz.com/') { $eurl = $u . '/thumb/'; $is_image = 1; } elseif (substr($u, 0, strlen('http://flic.kr/')) == 'http://flic.kr/') { $is_image = 1; } elseif (substr($u, 0, strlen('http://instagr.am/')) == 'http://instagr.am/') { // see: http://instagr.am/developer/embedding/ for reference // the following does a redirect to the actual jpg // make a check for an end slash in the url -- if it is there (likely) then adding a second // slash prior to the 'media' string will break the expanded url if ($u[strlen($u) - 1] == '/') { $eurl = $u . 'media/'; } else { $eurl = $u . '/media/'; } $logger->logDebug("expanded instagram URL to: " . $eurl, __METHOD__ . ',' . __LINE__); $is_image = 1; } if ($link_dao->insert($u, $eurl, $title, $tweet['post_id'], 'twitter', $is_image)) { $logger->logSuccess("Inserted " . $u . " (" . $eurl . ", " . $is_image . "), into links table", __METHOD__ . ',' . __LINE__); } else { $logger->logError("Did NOT insert " . $u . " (" . $eurl . ") into links table", __METHOD__ . ',' . __LINE__); } } }
/** * Fetch instance user's favorites since the last favorite stored. */ public function fetchInstanceUserFavorites() { if (!isset($this->user)) { $this->fetchInstanceUserInfo(); } $this->logger->logUserInfo("Checking for new favorites.", __METHOD__ . ',' . __LINE__); $last_fav_id = $this->instance->last_favorite_id; $this->logger->logInfo("Owner favs: " . $this->user->favorites_count . ", instance owner favs in system: " . $this->instance->owner_favs_in_system, __METHOD__ . ',' . __LINE__); $continue = true; while ($continue) { list($tweets, $http_status, $payload) = $this->getFavorites($last_fav_id); if ($http_status == 200) { if (sizeof($tweets) == 0) { // then done -- this should happen when we have run out of favs $this->logger->logInfo("It appears that we have run out of favorites to process", __METHOD__ . ',' . __LINE__); $continue = false; } else { $post_dao = DAOFactory::getDAO('FavoritePostDAO'); foreach ($tweets as $tweet) { $tweet['network'] = 'twitter'; if ($post_dao->addFavorite($this->user->user_id, $tweet) > 0) { URLProcessor::processPostURLs($tweet['post_text'], $tweet['post_id'], 'twitter', $this->logger); $this->logger->logInfo("found new fav: " . $tweet['post_id'], __METHOD__ . ',' . __LINE__); $fcount++; $this->logger->logInfo("fcount: {$fcount}", __METHOD__ . ',' . __LINE__); $this->logger->logInfo("added favorite: " . $tweet['post_id'], __METHOD__ . ',' . __LINE__); } else { // fav was already stored, so take no action. This could happen both because some // of the favs on the given page were processed last time, or because a separate process, // such as a UserStream process, is also watching for and storing favs. $status_message = "have already stored fav " . $tweet['post_id']; $this->logger->logDebug($status_message, __METHOD__ . ',' . __LINE__); } // keep track of the highest fav id we've encountered if ($tweet['post_id'] > $last_fav_id) { $last_fav_id = $tweet['post_id']; } } // end foreach } } else { $continue = false; } } }
/** * cleanUpMissedFavsUnFavs pages back through the older pages of favs, checking for favs that are not yet in * the database, as well as favs that were added to the db but are no longer returned by Twitter's API. * However, that latter calculation, for un-fav'd tweets, is currently not reliable due to a bug on Twitter's end, * and so such tweets are not currently removed from the database. * Due to the same issue with the API, it's not clear whether all favs of older tweets are going to be actually * returned from Twitter (that is, it is currently not returning some actually-favorited tweets in a given range). * So, we may miss some older tweets that were in fact favorited, until Twitter fixes this. * The number of pages to page back for each run of the crawler is set by favs_cleanup_pages option. */ public function cleanUpMissedFavsUnFavs() { // first, check that we have the resources to do work if (!($this->api->available && $this->api->available_api_calls_for_crawler)) { $this->logger->logInfo("terminating cleanUpMissedFavsUnFavs-- no API calls available", __METHOD__ . ',' . __LINE__); return true; } $this->logger->logInfo("In cleanUpMissedFavsUnFavs", __METHOD__ . ',' . __LINE__); $this->logger->logInfo("User id: " . $this->user->user_id . "\n", __METHOD__ . ',' . __LINE__); $fcount = 0; $favs_cleanup_pages = 1; // default number of pages to process each time the crawler runs // get plugin option value if it exists & is positive int, otherwise use default $topt = $this->twitter_options; if (isset($topt['favs_cleanup_pages'])) { $conf_favs_cleanup_pages = $topt['favs_cleanup_pages']->option_value; $this->logger->logInfo("conf_favs_cleanup_pages: {$conf_favs_cleanup_pages} ", __METHOD__ . ',' . __LINE__); if (is_integer((int) $conf_favs_cleanup_pages) && $conf_favs_cleanup_pages > 0) { $favs_cleanup_pages = $conf_favs_cleanup_pages; } } $this->logger->logInfo("favs_cleanup_pages: {$favs_cleanup_pages} ", __METHOD__ . ',' . __LINE__); $fpd = DAOFactory::getDAO('FavoritePostDAO'); $pagesize = 20; // number of favs per page retrieved from the API call... (tbd: any way to get //this from the API?) // get 'favs_older_pages' plugin option value if it exists & is pos. int. Use it to calculate default start // page if set, otherwise use default value. $default_start_page = 2; $topt = $this->twitter_options; if (isset($topt['favs_older_pages'])) { $conf_older_favs_pages = $topt['favs_older_pages']->option_value; if (is_integer((int) $conf_older_favs_pages) && $conf_older_favs_pages > 0) { $default_start_page = $conf_older_favs_pages + 1; } } $this->logger->logInfo("default start page: {$default_start_page} ", __METHOD__ . ',' . __LINE__); $last_page_of_favs = round($this->api->archive_limit / $pagesize); $last_unfav_page_checked = $this->instance->last_unfav_page_checked; $start_page = $last_unfav_page_checked > 0 ? $last_unfav_page_checked + 1 : $default_start_page; $this->logger->logInfo("start page: {$start_page}, with {$favs_cleanup_pages} cleanup pages", __METHOD__ . ',' . __LINE__); $curr_favs_count = $this->user->favorites_count; $count = 0; $page = $start_page; while ($count < $favs_cleanup_pages && $this->api->available && $this->api->available_api_calls_for_crawler) { // get the favs from that page try { list($tweets, $cURL_status, $twitter_data) = $this->getFavsPage($page); } catch (APICallLimitExceededException $e) { break; } if ($cURL_status != 200 || $tweets == -1) { // todo - handle more informatively $this->logger->logInfo("in cleanUpMissedFavsUnFavs, error with: {$twitter_data}", __METHOD__ . ',' . __LINE__); throw new Exception("in cleanUpUnFavs: error parsing favs"); } if (sizeof($tweets) == 0) { // then done paging backwards through the favs. // reset pointer so that we start at the recent favs again next time through. $this->instance->last_unfav_page_checked = 0; break; } $min_tweet = $tweets[sizeof($tweets) - 1]['post_id']; $max_tweet = $tweets[0]['post_id']; $this->logger->logInfo("in cleanUpUnFavs, page {$page} min and max: {$min_tweet}, {$max_tweet}", __METHOD__ . ',' . __LINE__); foreach ($tweets as $fav) { $fav['network'] = 'twitter'; // check whether the tweet is in the db-- if not, add it. if ($fpd->addFavorite($this->user->user_id, $fav) > 0) { URLProcessor::processPostURLs($fav['post_text'], $fav['post_id'], 'twitter', $this->logger); $this->logger->logInfo("added fav " . $fav['post_id'], __METHOD__ . ',' . __LINE__); $fcount++; } else { $status_message = "have already stored fav " . $fav['post_id']; $this->logger->logDebug($status_message, __METHOD__ . ',' . __LINE__); } } // now for each favorited tweet in the database within the fetched range, check whether it's still // favorited. This part of the method is currently disabled due to issues with the Twitter API, which // is not returning all of the favorited tweets any more. So, the fact that a previously-archived // tweet is not returned, no longer indicates that it was un-fav'd. // The method still IDs the 'missing' tweets, but no longer deletes them. We may want to get rid of // this check altogether at some point. $fposts = $fpd->getAllFavoritePostsUpperBound($this->user->user_id, 'twitter', $pagesize, $max_tweet + 1); foreach ($fposts as $old_fav) { $old_fav_id = $old_fav->post_id; if ($old_fav_id < $min_tweet) { $this->logger->logInfo("Old fav {$old_fav_id} out of range ", __METHOD__ . ',' . __LINE__); break; // all the rest will be out of range also then } // look for the old_fav_id in the array of fetched favs $found = false; foreach ($tweets as $tweet) { if ($old_fav_id == $tweet['post_id']) { $found = true; break; } } if (!$found) { // if it's not there... // 14/10 arghh -- Twitter is suddenly (temporarily?) not returning all fav'd tweets in a // sequence. // skipping the delete for now, keep tabs on it. Can check before delete with extra API // request, but the point of doing it this way was to avoid the additional API request. $this->logger->logInfo("Twitter claims tweet not still favorited, but this is currently " . "broken, so not deleting: " . $old_fav_id, __METHOD__ . ',' . __LINE__); // 'unfavorite' by removing from favorites table // $fpd->unFavorite($old_fav_id, $this->user->user_id); } } $this->instance->last_unfav_page_checked = $page++; if ($page > $last_page_of_favs) { $page = 0; break; } $count++; } $this->logger->logUserSuccess("Added {$fcount} older missed favorites", __METHOD__ . ',' . __LINE__); return true; }
/** * Retrieve tweets in search results for a keyword/hashtag. * @param InstanceHashtag $instance_hashtag * @return void */ public function fetchInstanceHashtagTweets($instance_hashtag) { if (isset($this->instance)) { $status_message = ""; $continue_fetching = true; $since_id = 0; $max_id = 0; $instance_hashtag_dao = DAOFactory::getDAO('InstanceHashtagDAO'); $post_dao = DAOFactory::getDAO('PostDAO'); $user_dao = DAOFactory::getDAO('UserDAO'); $hashtagpost_dao = DAOFactory::getDAO('HashtagPostDAO'); $hashtag_dao = DAOFactory::getDAO('HashtagDAO'); //Get hashtag $hashtag = $hashtag_dao->getHashtagByID($instance_hashtag->hashtag_id); while ($continue_fetching) { $endpoint = $this->api->endpoints['search_tweets']; $args = array(); $args["q"] = $hashtag->hashtag; $count_arg = isset($this->twitter_options['tweet_count_per_call']) ? $this->twitter_options['tweet_count_per_call']->option_value : 100; $args["count"] = $count_arg; $args["include_entities"] = "true"; if ($since_id == 0) { $since_id = $instance_hashtag->last_post_id; } if ($since_id > 0) { $args["since_id"] = $since_id; } if ($max_id > $since_id) { $args["max_id"] = $max_id; } try { list($http_status, $payload) = $this->api->apiRequest($endpoint, $args); } catch (APICallLimitExceededException $e) { $this->logger->logInfo($e->getMessage(), __METHOD__ . ',' . __LINE__); break; } if ($http_status == 200) { $this->logger->logDebug('Search tweets 200 ' . $endpoint->getPath(), __METHOD__ . ',' . __LINE__); $count = 0; $user_count = 0; $tweets = $this->api->parseJSONTweetsFromSearch($payload); foreach ($tweets as $tweet) { $this->logger->logDebug('Processing ' . $tweet['post_id'], __METHOD__ . ',' . __LINE__); $this->logger->logDebug('Processing ' . Utils::varDumpToString($tweet), __METHOD__ . ',' . __LINE__); $inserted_post_key = $post_dao->addPost($tweet, $this->user, $this->logger); //We need to check if post exists before add relationship between post and hashtag if ($post_dao->isPostInDB($tweet['post_id'], 'twitter')) { if (!$hashtagpost_dao->isHashtagPostInStorage($hashtag->id, $tweet['post_id'], 'twitter')) { $count = $count + 1; $hashtagpost_dao->insertHashtagPost($hashtag->hashtag, $tweet['post_id'], 'twitter'); $user = new User($tweet); $rows_updated = $user_dao->updateUser($user); if ($rows_updated > 0) { $user_count = $user_count + $rows_updated; } $this->logger->logDebug('User has been updated', __METHOD__ . ',' . __LINE__); if (isset($tweet['retweeted_post']) && isset($tweet['retweeted_post']['content'])) { $this->logger->logDebug('Retweeted post info set', __METHOD__ . ',' . __LINE__); if (!$hashtagpost_dao->isHashtagPostInStorage($hashtag->id, $tweet['retweeted_post']['content']['post_id'], 'twitter')) { $this->logger->logDebug('Retweeted post not in storage', __METHOD__ . ',' . __LINE__); $count++; $hashtagpost_dao->insertHashtagPost($hashtag->hashtag, $tweet['retweeted_post']['content']['post_id'], 'twitter'); $user_retweet = new User($tweet['retweeted_post']['content']); $rows_retweet_updated = $user_dao->updateUser($user_retweet); if ($rows_retweet_updated > 0) { $user_count = $user_count + $rows_retweet_updated; } } else { $this->logger->logDebug('Retweeted post in storage', __METHOD__ . ',' . __LINE__); } } else { $this->logger->logDebug('Retweeted post info not set', __METHOD__ . ',' . __LINE__); } $this->logger->logDebug('About to process URLs', __METHOD__ . ',' . __LINE__); URLProcessor::processPostURLs($tweet['post_text'], $tweet['post_id'], 'twitter', $this->logger); $this->logger->logDebug('URLs have been processed', __METHOD__ . ',' . __LINE__); } } if ($tweet['post_id'] > $instance_hashtag->last_post_id) { $instance_hashtag->last_post_id = $tweet['post_id']; } if ($instance_hashtag->earliest_post_id == 0 || $tweet['post_id'] < $instance_hashtag->earliest_post_id) { $instance_hashtag->earliest_post_id = $tweet['post_id']; } if ($max_id == 0 || $tweet['post_id'] < $max_id) { $max_id = $tweet['post_id']; } $this->logger->logDebug('Instance hashtag markers updated', __METHOD__ . ',' . __LINE__); } //Status message for tweets and users $status_message = ' ' . count($tweets) . " tweet(s) found and {$count} saved"; $this->logger->logUserSuccess($status_message, __METHOD__ . ',' . __LINE__); $status_message = ' ' . count($tweets) . " tweet(s) found and {$user_count} users saved"; $this->logger->logUserSuccess($status_message, __METHOD__ . ',' . __LINE__); //Save instance_hashtag important values if ($instance_hashtag->last_post_id > 0) { $instance_hashtag_dao->updateLastPostID($instance_hashtag->instance_id, $instance_hashtag->hashtag_id, $instance_hashtag->last_post_id); } if ($instance_hashtag->earliest_post_id > 0) { $instance_hashtag_dao->updateEarliestPostID($instance_hashtag->instance_id, $instance_hashtag->hashtag_id, $instance_hashtag->earliest_post_id); } //Not to continue fetching if search not return the maxim number of tweets if (count($tweets) < $count_arg) { $continue_fetching = false; } } else { $status_message = "Stop fetching tweets. cURL_status = " . $cURL_status; $this->logger->logUserSuccess($status_message, __METHOD__ . ',' . __LINE__); $continue_fetching = false; } } } }
/** * @param $content * @return array */ private function parsePost($content) { $logger = Logger::getInstance('stream_log_location'); $rt_string = "RT @"; $post = array(); $mentions = array(); $urls = array(); $hashtags = array(); $entities = array(); $user_array = array(); try { $post['is_rt'] = false; $post['in_rt_of_user_id'] = ''; $user = $content['user']; // parse info into user and post arrays $post['post_id'] = $content['id_str']; $post['author_user_id'] = $user['id_str']; $post['author_username'] = $user['screen_name']; $post['author_fullname'] = $user['name']; $post['author_avatar'] = $user['profile_image_url']; $post['author_follower_count'] = $user['followers_count']; $post['post_text'] = $content['text']; $post['is_protected'] = $user['protected']; $post['source'] = $content['source']; $post['location'] = $user['location']; $post['description'] = $user['description']; $post['url'] = $user['url']; $post['author_friends_count'] = $user['friends_count']; $post['author_post_count'] = $user['statuses_count']; $post['author_joined'] = gmdate("Y-m-d H:i:s", strToTime($user['created_at'])); $post['favorited'] = $content['favorited']; $user_array['url'] = $user['url']; // for now, retain existing 'place' handling, where a place is set in the post. // Set new place_id field as well, and add point coord information if it exists $logger->logDebug("point coords: " . print_r($content['coordinates'], true), __METHOD__ . ',' . __LINE__); $place = $content['place']; if ($place != null) { $post['place'] = $place['full_name']; $post['place_id'] = $place['id']; if (isset($content['coordinates'])) { $place['point_coords'] = $content['coordinates']; } } else { $post['place'] = null; $post['place_id'] = null; // it's possible for the point coords to be set even if the place is not. if (isset($content['coordinates'])) { $place = array(); $place['point_coords'] = $content['coordinates']; } } $post['pub_date'] = gmdate("Y-m-d H:i:s", strToTime($content['created_at'])); $post['in_reply_to_user_id'] = $content['in_reply_to_user_id_str']; $post['in_reply_to_post_id'] = $content['in_reply_to_status_id_str']; $post['network'] = 'twitter'; $post['reply_count_cache'] = 0; if (isset($content['entities'])) { foreach ($content['entities']['user_mentions'] as $m) { $mention_info = array(); $mention_info['user_id'] = $m['id_str']; $mention_info['user_name'] = $m['screen_name']; $mentions[] = $mention_info; } // get urls foreach ($content['entities']['urls'] as $u) { // This block broken under 0.11 /* $url_info = array(); $url_info['url']= $u['url']; if (isset($u['expanded_url'])) { $url_info['expanded_url'] = $u['expanded_url']; print "expanded url for: " . $url_info['url'] . ": " . $url_info['expanded_url'] . "\n"; } else { $url_info['expanded_url'] = ''; } $urls[] = $url_info; */ // just need an array of urls now... if (isset($u['expanded_url'])) { array_push($urls, $u['expanded_url']); } else { if (isset($u['url'])) { array_push($urls, $u['url']); } } } // get hashtags foreach ($content['entities']['hashtags'] as $h) { $hashtags[] = $h['text']; } } $logger->logDebug($post['post_text'] . " -- " . $post['author_username'], __METHOD__ . ',' . __LINE__); if (!isset($content['retweeted_status'])) { if (isset($content['retweet_count'])) { // do this only for the original post (rt will have rt count too) $retweet_count_api = $content['retweet_count']; $pos = strrpos($content['retweet_count'], '+'); if ($pos != false) { // remove '+', e.g. '100+' -- so currently 100 is max that can be indicated $retweet_count_api = substr($content['retweet_count'], 0, $pos); } $post['retweet_count_api'] = $retweet_count_api; $this->logger->logDebug($content['id_str'] . " is not a retweet but orig., count is: " . $content['retweet_count'] . "/ " . $retweet_count_api, __METHOD__ . ',' . __LINE__); } // // parse to see if 'old-style' retweet "RT @..." for first 'mention'-- if so, set that information if (sizeof($mentions) > 0) { $first_mention = $mentions[0]['user_name']; $logger->logDebug("first mention: {$first_mention}", __METHOD__ . ',' . __LINE__); if (RetweetDetector::isRetweet($post['post_text'], $first_mention)) { $post['is_rt'] = true; $post['in_rt_of_user_id'] = $mentions[0]['user_id']; $logger->logDebug("detected retweet of: " . $post['in_rt_of_user_id'] . ", " . $first_mention, __METHOD__ . ',' . __LINE__); } } } else { // then this is a retweet. // Process its original too. $this->logger->logInfo("this is a retweet, will first process original post " . $content['retweeted_status']['id_str'] . " from user " . $content['retweeted_status']['user']['id_str'], __METHOD__ . ',' . __LINE__); list($orig_post, $orig_entities, $orig_user_array) = $this->parsePost($content['retweeted_status']); $rtp = array(); $rtp['content'] = $orig_post; $rtp['entities'] = $orig_entities; $rtp['user_array'] = $orig_user_array; $post['retweeted_post'] = $rtp; $post['in_retweet_of_post_id'] = $content['retweeted_status']['id_str']; $post['in_rt_of_user_id'] = $content['retweeted_status']['user']['id_str']; } $user_array = $this->parseUser($user, $post['pub_date']); } catch (Exception $e) { $logger->logErrro("exception: {$e}", __METHOD__ . ',' . __LINE__); } $entities['urls'] = $urls; $entities['mentions'] = $mentions; $entities['hashtags'] = $hashtags; $entities['place'] = $place; // add 'place' object to entities array; may be null return array($post, $entities, $user_array); }
/** * Sanitize Globals * * Internal method serving for the following purposes: * * - Unsets $_GET data, if query strings are not enabled * - Cleans POST, COOKIE and SERVER data * - Standardizes newline characters to PHP_EOL * * @return void */ protected function _sanitize_globals() { // Is $_GET data allowed? If not we'll set the $_GET to an empty array if ($this->_allow_get_array === FALSE) { $_GET = array(); } elseif (is_array($_GET)) { foreach ($_GET as $key => $val) { $_GET[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); } } // Clean $_POST Data if (is_array($_POST)) { foreach ($_POST as $key => $val) { $_POST[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); } } // Clean $_COOKIE Data if (is_array($_COOKIE)) { // Also get rid of specially treated cookies that might be set by a server // or silly application, that are of no use to a CI application anyway // but that when present will trip our 'Disallowed Key Characters' alarm // http://www.ietf.org/rfc/rfc2109.txt // note that the key names below are single quoted strings, and are not PHP variables unset($_COOKIE['$Version'], $_COOKIE['$Path'], $_COOKIE['$Domain']); foreach ($_COOKIE as $key => $val) { if (($cookie_key = $this->_clean_input_keys($key)) !== FALSE) { $_COOKIE[$cookie_key] = $this->_clean_input_data($val); } else { unset($_COOKIE[$key]); } } } // Sanitize PHP_SELF $_SERVER['PHP_SELF'] = strip_tags($_SERVER['PHP_SELF']); Logger::logDebug('Global POST, GET and COOKIE data sanitized'); }
/** * Update/serve cached output * * @uses Config * @uses URI * * @return bool TRUE on success or FALSE on failure */ public function _display_cache() { $cache_path = Core::$tempDir . DS . 'Output' . DS; // Build the file path. The file name is an MD5 hash of the full URI $main = $this->config->main; $uri = $main->base_url . $main->index_page . $this->uri->uri_string; if (($cache_query_string = $this->config->cache->cache_query_string) && !empty($_SERVER['QUERY_STRING'])) { if (is_array($cache_query_string)) { $uri .= '?' . http_build_query(array_intersect_key($_GET, array_flip($cache_query_string))); } else { $uri .= '?' . $_SERVER['QUERY_STRING']; } } $filepath = $cache_path . md5($uri); if (!file_exists($filepath) or !($fp = @fopen($filepath, 'rb'))) { Logger::logDebug("No cache file for {$uri} found"); return FALSE; } flock($fp, LOCK_SH); $cache = filesize($filepath) > 0 ? fread($fp, filesize($filepath)) : ''; flock($fp, LOCK_UN); fclose($fp); // Look for embedded serialized file info. if (!preg_match('/^(.*)ENDCI--->/', $cache, $match)) { return FALSE; } $cache_info = unserialize($match[1]); $expire = $cache_info['expire']; $last_modified = filemtime($filepath); // Has the file expired? if ($_SERVER['REQUEST_TIME'] >= $expire && Core::isReallyWritable($cache_path)) { // If so we'll delete it. @unlink($filepath); Logger::logDebug('Cache file has expired. File deleted.'); return FALSE; } else { // Or else send the HTTP cache control headers. $this->set_cache_header($last_modified, $expire); } // Add headers from cache file. foreach ($cache_info['headers'] as $header) { $this->set_header($header[0], $header[1]); } // Display the cache $this->_display(substr($cache, strlen($match[0]))); Logger::logDebug('Cache file is current. Sending it to browser.'); return TRUE; }