<?php
class XenForo_Deferred_Sitemap extends XenForo_Deferred_Abstract
{
protected $_contentTypes;
protected $_setId;
protected $_fileCounter;
protected $_fileLength;
protected $_fileEntryCount;
protected $_file;
protected $_totalEntryCount;
/**
* @var XenForo_Model_Sitemap
*/
protected $_sitemapModel;
public function execute(array $deferred, array $data, $targetRunTime, &$status)
{
$data = array_merge(array(
'content_types' => null,
'current_type' => null,
'last_id' => 0,
'set_id' => null,
'file_counter' => 1,
'file_length' => 0,
'file_entry_count' => 0,
'total_entry_count' => 0,
'finalize_file' => 1
), $data);
/** @var XenForo_Model_Sitemap $siteMapModel */
$siteMapModel = XenForo_Model::create('XenForo_Model_Sitemap');
$this->_sitemapModel = $siteMapModel; // workaround IDE completion quirk
$allContentTypes = $this->_sitemapModel->getSitemapContentTypes();
$this->_contentTypes = $data['content_types'];
if (!is_array($this->_contentTypes))
{
$this->_contentTypes = array_keys($allContentTypes);
}
$this->_fileCounter = $data['file_counter'];
$this->_fileLength = $data['file_length'];
$this->_fileEntryCount = $data['file_entry_count'];
$this->_totalEntryCount = $data['total_entry_count'];
$this->_setId = $data['set_id'];
if (!$this->_setId)
{
$this->_setId = XenForo_Application::$time;
}
$contentType = $this->_getContentType($data['current_type'], $allContentTypes);
if (!$contentType)
{
$finalizeFile = $this->_finalizeSitemap($data['finalize_file'], $targetRunTime);
if ($finalizeFile === false)
{
return false;
}
$data['finalize_file'] = $finalizeFile;
$actionPhrase = new XenForo_Phrase('rebuilding');
$typePhrase = new XenForo_Phrase(new XenForo_Phrase('sitemap'));
$text = new XenForo_Phrase(new XenForo_Phrase('finalizing'));
$status = sprintf('%s... %s (%s)', $actionPhrase, $typePhrase, "$text $data[finalize_file]");
$newLast = $data['last_id'];
}
else
{
if ($contentType != $data['current_type'])
{
$data['last_id'] = 0;
}
$handlerClass = XenForo_Application::resolveDynamicClass($allContentTypes[$contentType]);
$handler = new $handlerClass();
$newLast = $this->_buildSitemap($handler, $data['last_id'], $targetRunTime);
$this->_sitemapModel->insertPendingSitemap($this->_setId, $this->_fileCounter, $this->_totalEntryCount);
$actionPhrase = new XenForo_Phrase('rebuilding');
$typePhrase = new XenForo_Phrase(new XenForo_Phrase('sitemap'));
$text = new XenForo_Phrase($handler->getPhraseKey($contentType));
$status = sprintf('%s... %s (%s)', $actionPhrase, $typePhrase, "$text $data[last_id]");
}
return array(
'content_types' => $this->_contentTypes,
'current_type' => $newLast ? $contentType : null,
'last_id' => $newLast,
'set_id' => $this->_setId,
'file_counter' => $this->_fileCounter,
'file_length' => $this->_fileLength,
'file_entry_count' => $this->_fileEntryCount,
'total_entry_count' => $this->_totalEntryCount,
'finalize_file' => $data['finalize_file']
);
}
protected function _getContentType($currentType, array $allContentTypes)
{
while (!$currentType || !isset($allContentTypes[$currentType]))
{
if (!$this->_contentTypes)
{
return false;
}
$currentType = array_shift($this->_contentTypes);
}
return $currentType;
}
protected function _buildSitemap(XenForo_SitemapHandler_Abstract $handler, $lastId, $targetRunTime)
{
$start = microtime(true);
$viewingUser = XenForo_Model::create('XenForo_Model_User')->getVisitingGuestUser();
$viewingUser['permissions'] = XenForo_Permission::unserializePermissions($viewingUser['global_permission_cache']);
if (!$handler->basePermissionCheck($viewingUser))
{
return false;
}
$records = $handler->getRecords($lastId, 2000, $viewingUser);
if (!$records)
{
return false;
}
$isInterruptable = $handler->isInterruptable();
$newLast = false;
foreach ($records AS $key => $record)
{
$newLast = $key;
if ($handler->isIncluded($record, $viewingUser))
{
$result = $handler->getData($record);
if ($result)
{
if (isset($result['loc']))
{
$this->_writeResult($result);
}
else
{
foreach ($result AS $row)
{
$this->_writeResult($row);
}
}
}
}
if ($isInterruptable && $targetRunTime && microtime(true) - $start > $targetRunTime)
{
break;
}
}
if ($this->_file)
{
$this->_closeFile();
}
return $isInterruptable ? $newLast : false;
}
protected function _writeResult(array $result)
{
if ($this->_fileEntryCount >= 50000)
{
$this->_completeFile();
}
$this->_fileEntryCount++;
$this->_totalEntryCount++;
$content = $this->_sitemapModel->buildSitemapEntry($result);
$this->_writeSitemapString("\t" . trim($content) . "\n");
}
protected function _openFile()
{
if (!$this->_file)
{
$fileName = $this->_sitemapModel->getSitemapFileName($this->_setId, $this->_fileCounter);
$this->_file = fopen($fileName, 'a');
flock($this->_file, LOCK_EX);
}
}
protected function _closeFile()
{
if ($this->_file)
{
fflush($this->_file);
flock($this->_file, LOCK_UN);
fclose($this->_file);
$this->_file = null;
}
}
protected function _writeSitemapString($content, $allowComplete = true)
{
if (!$this->_file)
{
$this->_openFile();
}
if ($this->_fileLength == 0)
{
$preamble = $this->_sitemapModel->getSitemapPreamble();
fwrite($this->_file, $preamble);
$this->_fileLength += strlen($preamble);
}
fwrite($this->_file, $content);
$this->_fileLength += strlen($content);
if ($this->_fileLength > 10000000 && $allowComplete)
{
$this->_completeFile();
}
}
protected function _completeFile()
{
if ($this->_fileLength == 0)
{
return;
}
$this->_writeSitemapString($this->_sitemapModel->getSitemapSuffix(), false);
$this->_closeFile();
$this->_fileCounter++;
$this->_fileLength = 0;
$this->_fileEntryCount = 0;
}
protected function _finalizeSitemap($finalizeFile, $targetRunTime)
{
$this->_completeFile();
$fileCount = $this->_fileCounter - 1;
$canCompress = function_exists('gzopen');
if ($finalizeFile <= $fileCount && $canCompress)
{
// gzip a file at a time
$success = $this->_sitemapModel->compressSitemapFile($this->_setId, $finalizeFile);
if (!$success && $finalizeFile == 1)
{
// if we failed on the first file, just bail out
$canCompress = false;
}
else
{
return $finalizeFile + 1;
}
}
// final clean up, rotation and search engine ping
$this->_sitemapModel->completeSitemap($this->_setId, $canCompress, $fileCount, $this->_totalEntryCount);
$this->_sitemapModel->cleanUpOldSitemaps($this->_setId);
$this->_sitemapModel->deleteOldSitemapLogs();
if (XenForo_Application::getOptions()->sitemapAutoSubmit && $this->_totalEntryCount > 1)
{
// an entry count of 1 really just means the main URL, so it's almost certainly
// a totally private board
$this->_sitemapModel->sendSitemapPing();
}
return false;
}
public function canCancel()
{
return true;
}
}