Source of file SimpleCachePublisher.php
Size: 16,598 Bytes - Last Modified: 2021-12-23T10:26:52+00:00
/var/www/docs.ssmods.com/process/src/code/services/SimpleCachePublisher.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546 | <?php /** * Service style class responsible for publishing urls into a cache * * @author marcus@silverstripe.com.au * @license BSD License http://silverstripe.org/bsd-license/ */ class SimpleCachePublisher { const CACHE_PUBLISH = '__cache_publish'; public $excludeTypes = array( 'Site', 'UserDefinedForm', 'SolrSearchPage', 'RedirectorPage', ); /** * @var SimpleCache */ public $cache; protected $staticBaseUrl = null; protected $echoProgress = false; /** * Does the user need to "opt in" for caching pages? * * If so, the page must have the CacheThis property set * * If this is set to false (ie page cache always in effect) then the * user must have the "DontCache" property set to NOT cache the page * * @var boolean */ protected $optInCaching = true; /** * Use queuedjobs for generating cached data? * * @var boolean */ public $useJobs = true; /** * If useJobs = false, we _may_ still opt to use them of the jobThreshold is set * * @var int */ public $jobThreshold = 0; public function setStaticBaseUrl($value) { $this->staticBaseUrl = $value; } public function setOptInCaching($value) { $this->optInCaching = $value; } public function getOptInCaching() { return $this->optInCaching; } public function publishDataObject(DataObject $object, $specificUrls = null) { if ($this->dontCache($object)) { return; } if ($this->useJobs && class_exists('AbstractQueuedJob')) { // instead of republishing, we'll actually create a queued job $job = new SimpleCachePublishingJob($object, $specificUrls); singleton('QueuedJobService')->queueJob($job); } else { $currentBase = Config::inst()->get('Director', 'alternate_base_url'); if ($object->SiteID && class_exists('Multisites')) { // let's set the base directly $base = $object->Site()->getUrl(); if ($base != $currentBase) { if(substr($base, -1) !== '/') { // ensures base URL has a trailing slash $base .= '/'; } Config::inst()->update('Director', 'alternate_base_url', $base); } } if (!$specificUrls) { $specificUrls = array(); if ($object->hasMethod('pagesAffectedByChanges')) { $specificUrls = $object->pagesAffectedByChanges(); } else { $specificUrls = array($object->AbsoluteLink()); } } $regenUrls = array(); foreach ($specificUrls as $url) { if (Director::is_relative_url($url)) { $url = Director::absoluteURL($url); } $regenUrls[] = $url; } if (count($regenUrls)) { $object->extend('updateAffectedPages', $regenUrls); if (class_exists('AbstractQueuedJob') && $this->jobThreshold && count($regenUrls) >= $this->jobThreshold) { $job = new SimpleCachePublishingJob($object, $regenUrls); singleton('QueuedJobService')->queueJob($job); } else { $this->publishUrls($regenUrls); } } $this->recacheFragments($object); Config::inst()->update('Director', 'alternate_base_url', $currentBase); } } /** * Clears the dynamic cached data for a particular data object * @param DataObject $object */ public function clearDynamicCacheFor(DataObject $object) { $dynamicCache = SimpleCache::get_cache('DynamicPublisherCache'); if ($dynamicCache) { $clearUrls = array($object->Link()); if ($object->hasMethod('pagesAffectedByChanges')) { $affectedUrls = $object->pagesAffectedByChanges(); if (count($affectedUrls)) { foreach ($affectedUrls as $afUrl) { $clearUrls[] = $afUrl; } } } $clearUrls = array_unique($clearUrls); $baseUrl = trim($object->SiteID ? $object->Site()->getUrl(): Director::absoluteBaseURL(), '/'); foreach ($clearUrls as $absolute) { if (strpos($absolute, '://') === false) { $absolute = $baseUrl . '/' . trim($absolute, '/'); } $parts = parse_url($absolute); $host = $parts['host']; $path = '/index'; $path = isset($parts['path']) && $parts['path'] != '/' ? $parts['path'] : $path; $key = trim($parts['host'] . $path, '/'); $dynamicCache->delete($key); } } } /** * Indicate whether we "don't" cache the given object * @param type $object * @return type */ public function dontCache($object) { $nocache = $object->extend('canCache'); if (count($nocache) && min($nocache) == 0) { return true; } if ($object->NeverCache) { return true; } if ($object->SiteID && class_exists('Multisites')) { $site = $object->Site(); if ($site && $site->ID && $site->DisableSiteCache) { return true; } } if (method_exists($object, 'canCache') && !$object->canCache()) { return true; } $hierarchy = ClassInfo::ancestry($object->ClassName); foreach ($this->excludeTypes as $excluded) { if (in_array($excluded, $hierarchy)) { return true; } } if ($this->optInCaching && !$object->CacheThis) { return true; } else if (!$this->optInCaching && $object->DontCacheThis) { return true; } } protected function publishUrls($urls, $keyPrefix = '', $domain = null) { if (defined('PROXY_CONFIG_FILE') && !isset(SimpleCache::$cache_configs['PublisherCache'])) { include_once BASE_PATH . '/' . PROXY_CONFIG_FILE; } $config = SiteConfig::current_site_config(); if ($config->DisableSiteCache) { return; } $urls = array_unique($urls); // Do we need to map these? // Detect a numerically indexed arrays if (is_numeric(join('', array_keys($urls)))) { $urls = $this->urlsToPaths($urls); } // This can be quite memory hungry and time-consuming // @todo - Make a more memory efficient publisher increase_time_limit_to(); increase_memory_limit_to(); $currentBaseURL = Director::baseURL(); $files = array(); $i = 0; $totalURLs = sizeof($urls); $cache = $this->getCache(); if (!defined('PROXY_CACHE_GENERATING')) { define('PROXY_CACHE_GENERATING', true); } foreach($urls as $url => $path) { // work around bug introduced in ss3 whereby top level /bathroom.html would be changed to ./bathroom.html $path = ltrim($path, './'); $url = rtrim($url, '/'); // TODO: Detect the scheme + host URL from the URL's absolute path // and set that as the base URL appropriately $baseUrlSrc = $this->staticBaseUrl ? $this->staticBaseUrl : $url; $urlBits = parse_url($baseUrlSrc); if (isset($urlBits['scheme']) && isset($urlBits['host'])) { // now see if there's a host mapping // we want to set the base URL correctly Config::inst()->update('Director', 'alternate_base_url', $urlBits['scheme'] . '://' . $urlBits['host'] . '/'); } $i++; if($url && !is_string($url)) { user_error("Bad url:" . var_export($url,true), E_USER_WARNING); continue; } Requirements::clear(); if (strrpos($url, '/home') == strlen($url) - 5) { $url = substr($url, 0, strlen($url) - 5); } if($url == "" || $url == 'home') { $url = "/"; } if(Director::is_relative_url($url)) { $url = Director::absoluteURL($url); } $stage = Versioned::current_stage(); Versioned::reading_stage('Live'); $GLOBALS[self::CACHE_PUBLISH] = 1; Config::inst()->update('SSViewer', 'theme_enabled', true); if (class_exists('Multisites')) { Multisites::inst()->resetCurrentSite(); } $response = Director::test(str_replace('+', ' ', $url)); Config::inst()->update('SSViewer', 'theme_enabled', false); unset($GLOBALS[self::CACHE_PUBLISH]); Versioned::reading_stage($stage); Requirements::clear(); singleton('DataObject')->flushCache(); $contentType = null; $content = null; // Generate file content if(is_object($response)) { if($response->getStatusCode() == '301' || $response->getStatusCode() == '302') { $absoluteURL = Director::absoluteURL($response->getHeader('Location')); } else if ($response->getStatusCode () >= 200 && $response->getStatusCode() < 300) { $content = $response->getBody(); $type = $response->getHeader('Content-type'); $contentType = $type ? $type : $contentType; } } else { $content = $response . ''; } if (!$content) { continue; } if (isset($urlBits['host'])) { $domain = $urlBits['host']; } if ($domain && !$keyPrefix) { $keyPrefix = $domain; } $path = trim($path, '/'); if ($path == 'home') { $path = ''; } $path = (strlen($path) ? $path : 'index'); $data = new stdClass; $data->Content = $content; $data->LastModified = date('Y-m-d H:i:s'); $cacheAge = SiteConfig::current_site_config()->CacheAge; if ($cacheAge) { $data->Age = $cacheAge; } else { $data->Age = HTTP::get_cache_age(); } if (!empty($contentType)) { $data->ContentType = $contentType; } $key = $keyPrefix . '/' . $path; $cache->store($key, $data); if ($domain && isset($PROXY_CACHE_HOSTMAP) && isset($PROXY_CACHE_HOSTMAP[$domain])) { $hosts = $PROXY_CACHE_HOSTMAP[$domain]; foreach ($hosts as $otherDomain) { $key = $otherDomain .'/' . $path; $storeData = clone $data; $storeData->Content = str_replace($domain, $otherDomain, $storeData->Content); $cache->store($key, $storeData); } } } Director::setBaseURL($currentBaseURL); } /** * Recache fragments of a data object * * @param DataObject $object */ public function recacheFragments($object) { $current = Config::inst()->get('SSViewer', 'theme_enabled'); if (!$current) { Config::inst()->update('SSViewer', 'theme_enabled', true); } if (method_exists($object, 'cacheFragments')) { $fragments = $object->cacheFragments(); $regenContext = method_exists($object, 'regenerationContext') ? $object->regenerationContext() : $object; $current = Versioned::current_stage(); Versioned::reading_stage('Live'); foreach ($fragments as $fragment) { $item = Injector::inst()->create('CachedFragment', $regenContext, $fragment); $item->regenerate(); } Versioned::reading_stage($current); } if (!$current) { Config::inst()->update('SSViewer', 'theme_enabled', false); } } /** * * @param type $page * @return array * The list of urls unpublished */ public function unpublishObject($page) { if($page->hasMethod('pagesAffectedByUnpublishing')) { $urls = $page->pagesAffectedByUnpublishing(); $urls = array_unique($urls); } else { $urls = array($page->Link()); } array_walk($urls, function (&$entry) { $entry = preg_replace('{(.+?)://(.+?)/}i', '', $entry); }); $siteHost = $host = Director::protocolAndHost(); if ($page->SiteID && class_exists('Multisites')) { // prefix is different $host = $page->Site()->getUrl(); } $mappedUrls = array(); // now create a list of multi-hosts foreach ($urls as $path) { if ($host != $siteHost) { $mappedUrls[] = $siteHost . '' . $path; } $mappedUrls[] = $host . '' . $path; } // immediately unpublish $this->unpublishUrls($mappedUrls); $repub = array(); if ($page->hasMethod('pagesAffectedByChanges')) { $repub = $page->pagesAffectedByChanges(); $repub = array_diff($repub, $urls); if (count($repub)) { $this->publishDataObject($page, $repub); } } } /** * Unpublish a list of URLs * * @param array $urls * The URLs to unpublish * @param string $keyPrefix * The 'prefix' of these URLs as stored in cache. In multisite systems this is generally the * subsite's primary domain, but may be something more complex if publishing the same content for * multiple domains */ function unpublishUrls($urls) { global $PROXY_CACHE_HOSTMAP; // Do we need to map these? // Detect a numerically indexed arrays if (is_numeric(join('', array_keys($urls)))) { $urls = $this->urlsToPaths($urls); } // This can be quite memory hungry and time-consuming // @todo - Make a more memory efficient publisher increase_time_limit_to(); increase_memory_limit_to(); $cache = $this->getCache(); foreach($urls as $url => $path) { $baseUrlSrc = $this->staticBaseUrl ? $this->staticBaseUrl : $url; $urlBits = parse_url($baseUrlSrc); if (!isset($urlBits['host'])) { $urlBits = parse_url(Director::absoluteBaseURL()); } $domain = isset($urlBits['host']) ? $urlBits['host'] : (isset($_SERVER['HOST_NAME']) ? $_SERVER['HOST_NAME'] : ''); $key = $domain . '/' . ltrim($path, '/'); $cache->expire($key); if ($domain && isset($PROXY_CACHE_HOSTMAP) && isset($PROXY_CACHE_HOSTMAP[$domain])) { $hosts = $PROXY_CACHE_HOSTMAP[$domain]; foreach ($hosts as $otherDomain) { $key = $otherDomain . '/' . ltrim($path, '/'); $cache->expire($key); } } } } public function publishPages($urls) { // we do the base URL first $this->publishUrls($urls); } /** * Transforms relative or absolute URLs to their static path equivalent. * This needs to be the same logic that's used to look up these paths through * framework/static-main.php. Does not include the {@link $destFolder} prefix. * * URL filtering will have already taken place for direct SiteTree links via SiteTree->generateURLSegment()). * For all other links (e.g. custom controller actions), we assume that they're pre-sanitized * to suit the filesystem needs, as its impossible to sanitize them without risking to break * the underlying naming assumptions in URL routing (e.g. controller method names). * * Examples (without $domain_based_caching): * - http://mysite.com/mywebroot/ => /index.html (assuming your webroot is in a subfolder) * - http://mysite.com/about-us => /about-us.html * - http://mysite.com/parent/child => /parent/child.html * * Examples (with $domain_based_caching): * - http://mysite.com/mywebroot/ => /mysite.com/index.html (assuming your webroot is in a subfolder) * - http://mysite.com/about-us => /mysite.com/about-us.html * - http://myothersite.com/about-us => /myothersite.com/about-us.html * - http://subdomain.mysite.com/parent/child => /subdomain.mysite.com/parent/child.html * * @param Array $urls Absolute or relative URLs * @return Array Map of original URLs to filesystem paths (relative to {@link $destFolder}). */ function urlsToPaths($urls) { $mappedUrls = array(); foreach($urls as $url) { // parse_url() is not multibyte safe, see https://bugs.php.net/bug.php?id=52923. // We assume that the URL hsa been correctly encoded either on storage (for SiteTree->URLSegment), // or through URL collection (for controller method names etc.). $urlParts = @parse_url($url); // Remove base folders from the URL if webroot is hosted in a subfolder (same as static-main.php) $path = isset($urlParts['path']) ? $urlParts['path'] : ''; if(mb_substr(mb_strtolower($path), 0, mb_strlen(BASE_URL)) == mb_strtolower(BASE_URL)) { $urlSegment = mb_substr($path, mb_strlen(BASE_URL)); } else { $urlSegment = $path; } // Normalize URLs $urlSegment = trim($urlSegment, '/'); $filename = $urlSegment ? "$urlSegment" : "index"; // @TODO Re-evaluate this for multisite support // if (self::$domain_based_caching) { // if (!$urlParts) continue; // seriously malformed url here... // $filename = $urlParts['host'] . '/' . $filename; // } $mappedUrls[$url] = (dirname($filename) == '/') ? '' : $filename; // (dirname($filename).'/')).basename($filename); } return $mappedUrls; } protected function getCache() { if (!$this->cache) { $this->cache = Injector::inst()->get('PublisherCache'); } return $this->cache; } protected function out($message) { if (Director::is_cli()) { echo "$message \n"; } else { echo "$message <br/>"; } } } |