public function onAfterWrite()
 {
     $stage_mode_mapping = array("Stage" => "stage", "Live" => "live");
     foreach ($stage_mode_mapping as $stage => $mode) {
         $service = new CacheableNavigationService($mode, $this->owner);
         $service->refreshCachedConfig();
     }
 }
 /**
  * Ensure with and without filters, we get expected output
  */
 public function testGetChildren()
 {
     $config = $this->objFromFixture('SiteConfig', 'default');
     $parent = $this->objFromFixture('SiteTree', 'sitetreetest-page-1');
     $children = $parent->Children()->toArray();
     $models = array_merge(array($parent), $children);
     // Fake a rebuild task - Populate the object-cache with our fixture data
     foreach ($models as $model) {
         $service = new CacheableNavigationService('Live', $config, $model);
         $service->refreshCachedPage();
     }
     // Fetch the cache
     $objCache = $service->getObjectCache();
     $siteMap = $objCache->get_site_map();
     // Default: No custom filters passed - return everything
     $children = $siteMap[1]->getChildren();
     $this->assertEquals(2, $children->count());
     // Default: No custom filters passed - return everything
     $children = $siteMap[1]->getChildren(true, null);
     $this->assertEquals(2, $children->count());
     // Default: No custom filters passed - return everything
     $children = $siteMap[1]->getChildren(true, array());
     $this->assertEquals(2, $children->count());
     // Default: No custom filters passed - return everything
     $children = $siteMap[1]->getChildren(false, array());
     $this->assertEquals(2, $children->count());
     // Don't filter and still no filters passed
     $children = $siteMap[1]->getChildren(false, null);
     $this->assertEquals(2, $children->count());
     // Don't filter and filters passed
     $children = $siteMap[1]->getChildren(false, array('Title' => 'This is a child page 2'));
     $this->assertEquals(1, $children->count());
     // Do filter and filters passed
     $children = $siteMap[1]->getChildren(true, array('Title' => 'This is a child page 1'));
     $this->assertEquals(2, $children->count());
     // Do filter and filters passed
     $children = $siteMap[1]->getChildren(true, array('Title' => 'This is a child page 2'));
     $this->assertEquals(2, $children->count());
     // Do filter and filters passed
     $children = $siteMap[1]->getChildren(false, array('Title' => 'This is a child page 2'));
     $this->assertEquals(1, $children->count());
     // Do filter and "bad" filter passed
     $children = $siteMap[1]->getChildren(false, array('Title' => 'This is a child page null'));
     $this->assertEquals(0, $children->count());
     $children = $siteMap[1]->getChildren(true, array('Dummy' => null));
     $this->assertEquals(2, $children->count());
     $children = $siteMap[1]->getChildren(false, array('Dummy' => null));
     $this->assertEquals(2, $children->count());
 }
 /**
  * 
  * Create a {@link ChunkedCachableRefreshJob} for each "chunk" of N pages
  * to refresh the caches of. Once run, $chunk is truncated and passed back its
  * original reference.
  * 
  * @param CacheableNavigationService $service
  * @param array $chunk
  * @param string $stage
  * @param number $subsiteID
  * @return number $jobDescriptorID (Return value not used)
  */
 public function queue(CacheableNavigationService $service, $chunk, $stage, $subsiteID)
 {
     // We only need to do this during queueing
     $service->clearInternalCache();
     $job = new CachableChunkedRefreshJob($service, $chunk, $stage, $subsiteID);
     $jobDescriptorID = singleton('QueuedJobService')->queueJob($job);
     return $jobDescriptorID;
 }
 /**
  * 
  */
 public function testCompleteBuild()
 {
     $model = $this->objFromFixture('SiteTree', 'servicetest-page-1');
     $service = new CacheableNavigationService('Live', null, $model);
     // Cache should be empty
     $cachable = $service->getCacheableFrontEnd()->load($service->getIdentifier());
     $this->assertFalse($cachable->get_completed());
     // Populate the cache with a page
     $service->refreshCachedPage();
     // Set it to complete and re-test
     $service->completeBuild();
     $cachable = $service->getCacheableFrontEnd()->load($service->getIdentifier());
     $this->assertTrue($cachable->get_completed());
 }
 /**
  * 
  * Template function for debugging. Allows you to see at-a-glance, 
  * the fields, functions and child nodes held in the Object-Cache about 
  * the current object.
  * 
  * Usage:
  * 
  * <code>
  *  <% with $CachedData %>
  *  $Debug(98)
  *  <% end_with %>
  * <code>
  * 
  * @return string
  */
 public function Debug($id = null)
 {
     if (!Director::isDev() || !isset($_REQUEST['showcache'])) {
         return;
     }
     if ($id) {
         $mode = strtolower($_REQUEST['showcache']);
         $conf = SiteConfig::current_site_config();
         $cacheService = new CacheableNavigationService($mode, $conf);
         $objectCache = $cacheService->getObjectCache();
         $cachedSiteTree = $objectCache->get_site_map();
         if (isset($cachedSiteTree[$id])) {
             $object = $cachedSiteTree[$id];
         } else {
             return false;
         }
     } else {
         $object = $this;
     }
     $message = "<h2>Object-Cache fields & functions for: " . get_class($object) . "</h2>";
     $message .= "<ul>";
     $message .= "\t<li><strong>Cached specifics:</strong>";
     $message .= "\t\t<ul>";
     $message .= "\t\t\t<li>ID: " . $object->ID . "</li>";
     $message .= "\t\t\t<li>Title: " . $object->Title . "</li>";
     $message .= "\t\t\t<li>ClassName: " . $object->ClassName . "</li>";
     $message .= "\t\t\t<li>Child count: " . $object->getChildren()->count() . "</li>";
     $message .= "\t\t</ul>";
     $message .= "\t</li>";
     $message .= "</ul>";
     $message .= "<ul>";
     $message .= "\t<li><strong>Cached Fields:</strong>";
     $message .= "\t\t<ul>";
     foreach ($object->get_cacheable_fields() as $field) {
         $message .= "\t\t\t<li>" . $field . ': ' . $object->{$field} . "</li>";
     }
     $message .= "\t\t</ul>";
     $message .= "\t</li>";
     $message .= "\t<li><strong>Cached Functions:</strong>";
     $message .= "\t\t<ul>";
     foreach ($object->get_cacheable_functions() as $function) {
         $message .= "\t\t\t<li>" . $function . '</li>';
     }
     $message .= "\t\t</ul>";
     $message .= "\t</li>";
     $message .= "</ul>";
     $message .= "<h2>Child nodes of this object:</h2>";
     $message .= '<ol>';
     foreach ($object->getChildren() as $child) {
         $message .= "\t<li>" . $child->Title . ' (#' . $child->ID . ')</li>';
     }
     $message .= '</ol>';
     return $message;
 }
 /**
  * 
  * Get the total no. objects in the cache. Due to the way {@link CacheableNavigation} 
  * works, this is restricted to "SiteTree-ish" objects only at this point.
  * 
  * @param string $stage
  * @param string $className
  * @return int $total
  */
 private function getTotalCachedObjects($stage, $className = 'CacheableSiteTree')
 {
     $conf = SiteConfig::current_site_config();
     $service = new CacheableNavigationService($stage, $conf);
     $cache = $service->getObjectCache();
     $cachedIDs = $this->_cachedIDs;
     if ($cache && ($siteMap = $cache->get_site_map())) {
         // For reasons unknown, items appear in object-cache with NULL properties
         $cachedSiteTree = ArrayList::create($siteMap)->filterByCallback(function ($item) use(&$cachedIDs) {
             try {
                 if (!is_null($item->ParentID)) {
                     // push the item ID to the _cachedIDs array for comparison later
                     $cachedIDs[] = $item->ID;
                     return true;
                 }
                 return false;
             } catch (Exception $e) {
                 echo $e->getMessage();
             }
         });
         $this->_cachedIDs = $cachedIDs;
         return $cachedSiteTree->count();
     }
     return 0;
 }
 /**
  * 
  * @param array $modes
  * @param boolean $forceRemoval Whether to unset() children in {@link CacheableSiteTree::removeChild()}.
  * @return void
  */
 public function removePageCache($modes, $forceRemoval = true)
 {
     // Increase memory to max-allowable
     CacheableConfig::configure_memory_limit();
     $siteConfig = $this->owner->getSiteConfig();
     if (!$siteConfig->exists()) {
         $siteConfig = SiteConfig::current_site_config();
     }
     foreach ($modes as $stage => $mode) {
         $service = new CacheableNavigationService($mode, $siteConfig, $this->owner);
         $cache_frontend = $service->getCacheableFrontEnd();
         $id = $service->getIdentifier();
         $cached = $cache_frontend->load($id);
         if ($cached) {
             $cached_site_config = $cached->get_site_config();
             if (!$cached_site_config) {
                 $service->refreshCachedConfig();
             }
             $service->removeCachedPage($forceRemoval);
         }
     }
 }
 /**
  * 
  * Physically runs the task which - dependent on QueuedJobs being installed and
  * not skipped via script params - will queue-up chunks of pages to be cached,
  * or just attempt to cache call objects at once.
  * 
  * @param SS_HTTPRequest $request
  * @return void
  */
 public function run($request)
 {
     // Increase memory to max-allowable
     CacheableConfig::configure_memory_limit();
     $startTime = time();
     $skipQueue = $request->getVar('SkipQueue');
     $currentStage = Versioned::current_stage();
     /*
      * Restrict cache rebuild to the given stage - useful for debugging or
      * "Poor Man's" chunking.
      */
     if ($paramStage = $request->getVar('Stage')) {
         $stage_mode_mapping = array(ucfirst($paramStage) => strtolower($paramStage));
         // All stages
     } else {
         $stage_mode_mapping = array("Stage" => "stage", "Live" => "live");
     }
     $canQueue = interface_exists('QueuedJob');
     $siteConfigs = DataObject::get('SiteConfig');
     foreach ($stage_mode_mapping as $stage => $mode) {
         Versioned::set_reading_mode('Stage.' . $stage);
         if (class_exists('Subsite')) {
             Subsite::disable_subsite_filter(true);
             Config::inst()->update("CacheableSiteConfig", 'cacheable_fields', array('SubsiteID'));
             Config::inst()->update("CacheableSiteTree", 'cacheable_fields', array('SubsiteID'));
         }
         foreach ($siteConfigs as $config) {
             $service = new CacheableNavigationService($mode, $config);
             if ($service->refreshCachedConfig()) {
                 echo 'Caching: SiteConfig object ' . trim($config->ID) . ' (' . $config->Title . ') with mode: ' . $mode . self::new_line(2);
             } else {
                 echo 'Caching fails: SiteConfig object ' . trim($config->ID) . ' (' . $config->Title . ') with mode: ' . $mode . self::new_line(2);
             }
             if (class_exists('Subsite')) {
                 $pages = DataObject::get("Page", "SubsiteID = '" . $config->SubsiteID . "'");
             } else {
                 $pages = DataObject::get("Page");
             }
             $pageCount = $pages->count();
             /*
              * 
              * Queueing should only occur if:
              * - QueuedJob module is available
              * - SkipQueue param is not set
              * - Total no. pages is greater than the chunk divisor
              */
             $lowPageCount = self::$chunk_divisor > $pageCount;
             $doQueue = $canQueue && !$skipQueue && !$lowPageCount;
             if ($pageCount) {
                 $i = 0;
                 $chunkNum = 0;
                 $chunk = array();
                 foreach ($pages as $page) {
                     $i++;
                     // If QueuedJobs exists and isn't user-disabled: Chunk
                     if ($doQueue) {
                         // Start building a chunk of pages to be refreshed
                         $chunk[] = $page;
                         $chunkSize = count($chunk);
                         /*
                          * Conditions of chunking:
                          * - Initial chunks are chunk-size == self::$chunk_divisor
                          * - Remaining object count equals no. objects in current $chunk
                          */
                         $doChunking = $this->chunkForQueue($pageCount, $chunkSize, $i);
                         if ($doChunking) {
                             $chunkNum++;
                             $this->queue($service, $chunk, $stage, $config->SubsiteID);
                             echo "Queued chunk #" . $chunkNum . ' (' . $chunkSize . ' objects).' . self::new_line();
                             $chunk = array();
                         }
                         // Default to non-chunking if no queuedjobs or script instructed to skip queuing
                     } else {
                         $percentComplete = $this->percentageComplete($i, $pageCount);
                         $service->set_model($page);
                         if ($service->refreshCachedPage(true)) {
                             echo 'Caching: ' . trim($page->Title) . ' (' . $percentComplete . ') ' . self::new_line();
                         } else {
                             echo 'Caching fails: ' . trim($page->Title) . ' (' . $percentComplete . ') ' . self::new_line();
                         }
                     }
                     $page->flushCache();
                 }
             }
             $service->completeBuild();
             // Completion message
             $msg = self::new_line() . $pageCount . ' ' . $stage . ' pages in subsite ' . $config->ID;
             if ($doQueue) {
                 $msg .= ' queued for caching.' . self::new_line();
             } else {
                 $msg .= ' objects cached.' . self::new_line();
             }
             echo $msg . self::new_line();
         }
         if (class_exists('Subsite')) {
             Subsite::disable_subsite_filter(false);
         }
     }
     Versioned::set_reading_mode($currentStage);
     $endTime = time();
     $totalTime = $endTime - $startTime;
     $this->showConfig($totalTime, $request, $lowPageCount);
 }