QueueWorker.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <?php
  2. namespace Drupal\simple_sitemap\Queue;
  3. use Drupal\Component\Utility\Timer;
  4. use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorBase;
  5. use Drupal\simple_sitemap\SimplesitemapSettings;
  6. use Drupal\simple_sitemap\SimplesitemapManager;
  7. use Drupal\Core\State\StateInterface;
  8. use Drupal\simple_sitemap\Logger;
  9. class QueueWorker {
  10. use BatchTrait;
  11. const REBUILD_QUEUE_CHUNK_ITEM_SIZE = 5000;
  12. /**
  13. * @var \Drupal\simple_sitemap\SimplesitemapSettings
  14. */
  15. protected $settings;
  16. /**
  17. * @var \Drupal\simple_sitemap\SimplesitemapManager
  18. */
  19. protected $manager;
  20. /**
  21. * @var \Drupal\Core\State\StateInterface
  22. */
  23. protected $state;
  24. /**
  25. * @var \Drupal\simple_sitemap\Queue\SimplesitemapQueue
  26. */
  27. protected $queue;
  28. /**
  29. * @var \Drupal\simple_sitemap\Logger
  30. */
  31. protected $logger;
  32. /**
  33. * @var string|null
  34. */
  35. protected $variantProcessedNow;
  36. /**
  37. * @var string|null
  38. */
  39. protected $generatorProcessedNow;
  40. /**
  41. * @var array
  42. */
  43. protected $results = [];
  44. /**
  45. * @var array
  46. */
  47. protected $processedPaths = [];
  48. /**
  49. * @var array
  50. */
  51. protected $generatorSettings;
  52. /**
  53. * @var int|null
  54. */
  55. protected $maxLinks;
  56. /**
  57. * @var int|null
  58. */
  59. protected $elementsRemaining;
  60. /**
  61. * @var int|null
  62. */
  63. protected $elementsTotal;
  64. /**
  65. * QueueWorker constructor.
  66. * @param \Drupal\simple_sitemap\SimplesitemapSettings $settings
  67. * @param \Drupal\simple_sitemap\SimplesitemapManager $manager
  68. * @param \Drupal\Core\State\StateInterface $state
  69. * @param \Drupal\simple_sitemap\Queue\SimplesitemapQueue $element_queue
  70. * @param \Drupal\simple_sitemap\Logger $logger
  71. */
  72. public function __construct(SimplesitemapSettings $settings,
  73. SimplesitemapManager $manager,
  74. StateInterface $state,
  75. SimplesitemapQueue $element_queue,
  76. Logger $logger) {
  77. $this->settings = $settings;
  78. $this->manager = $manager;
  79. $this->state = $state;
  80. $this->queue = $element_queue;
  81. $this->logger = $logger;
  82. }
  83. /**
  84. * @return $this
  85. */
  86. public function deleteQueue() {
  87. $this->queue->deleteQueue();
  88. SitemapGeneratorBase::purgeSitemapVariants(NULL, 'unpublished');
  89. $this->variantProcessedNow = NULL;
  90. $this->generatorProcessedNow = NULL;
  91. $this->results = [];
  92. $this->processedPaths = [];
  93. $this->state->set('simple_sitemap.queue_items_initial_amount', 0);
  94. $this->state->delete('simple_sitemap.queue_stashed_results');
  95. $this->elementsTotal = NULL;
  96. $this->elementsRemaining = NULL;
  97. return $this;
  98. }
  99. /**
  100. * @param array|null $variants
  101. * @return $this
  102. * @throws \Drupal\Component\Plugin\Exception\PluginException
  103. */
  104. public function rebuildQueue($variants = NULL) {
  105. $all_data_sets = [];
  106. $sitemap_variants = $this->manager->getSitemapVariants();
  107. $type_definitions = $this->manager->getSitemapTypes();
  108. $this->deleteQueue();
  109. foreach ($sitemap_variants as $variant_name => $variant_definition) {
  110. // Skipping unwanted sitemap variants.
  111. if (NULL !== $variants && !in_array($variant_name, (array) $variants)) {
  112. continue;
  113. }
  114. $type = $variant_definition['type'];
  115. // Adding generate_sitemap operations for all data sets.
  116. foreach ($type_definitions[$type]['urlGenerators'] as $url_generator_id) {
  117. $data_sets = $this->manager->getUrlGenerator($url_generator_id)
  118. ->setSitemapVariant($variant_name)
  119. ->getDataSets();
  120. if (!empty($data_sets)) {
  121. $sitemap_variants[$variant_name]['data'] = TRUE;
  122. foreach ($data_sets as $data_set) {
  123. $all_data_sets[] = [
  124. 'data' => $data_set,
  125. 'sitemap_variant' => $variant_name,
  126. 'url_generator' => $url_generator_id,
  127. 'sitemap_generator' => $type_definitions[$type]['sitemapGenerator'],
  128. ];
  129. if (count($all_data_sets) === self::REBUILD_QUEUE_CHUNK_ITEM_SIZE) {
  130. $this->queueElements($all_data_sets);
  131. $all_data_sets = [];
  132. }
  133. }
  134. }
  135. }
  136. }
  137. if (!empty($all_data_sets)) {
  138. $this->queueElements($all_data_sets);
  139. }
  140. $this->getQueuedElementCount(TRUE);
  141. // todo: May not be clean to remove sitemap variants data when queuing elements.
  142. // todo: Add test.
  143. // Remove all sitemap variant instances where no results have been queued.
  144. $this->manager->removeSitemap(array_keys(array_filter($sitemap_variants, function($e) { return empty($e['data']); })));
  145. return $this;
  146. }
  147. protected function queueElements($elements) {
  148. $this->queue->createItems($elements);
  149. $this->state->set('simple_sitemap.queue_items_initial_amount', ($this->state->get('simple_sitemap.queue_items_initial_amount') + count($elements)));
  150. }
  151. /**
  152. * @param string $from
  153. * @return $this
  154. * @throws \Drupal\Component\Plugin\Exception\PluginException
  155. */
  156. public function generateSitemap($from = 'form') {
  157. $this->generatorSettings = [
  158. 'base_url' => $this->settings->getSetting('base_url', ''),
  159. 'default_variant' => $this->settings->getSetting('default_variant', NULL),
  160. 'skip_untranslated' => $this->settings->getSetting('skip_untranslated', FALSE),
  161. 'remove_duplicates' => $this->settings->getSetting('remove_duplicates', TRUE),
  162. 'excluded_languages' => $this->settings->getSetting('excluded_languages', []),
  163. ];
  164. $this->maxLinks = $this->settings->getSetting('max_links');
  165. $max_execution_time = $this->settings->getSetting('generate_duration', 10000);
  166. Timer::start('simple_sitemap_generator');
  167. $this->unstashResults();
  168. if (!$this->generationInProgress()) {
  169. $this->rebuildQueue();
  170. }
  171. while ($element = $this->queue->claimItem()) {
  172. if (!empty($max_execution_time) && Timer::read('simple_sitemap_generator') >= $max_execution_time) {
  173. break;
  174. }
  175. try {
  176. if ($element->data['sitemap_variant'] !== $this->variantProcessedNow) {
  177. if (NULL !== $this->variantProcessedNow) {
  178. $this->generateVariantChunksFromResults(TRUE);
  179. $this->publishCurrentVariant();
  180. }
  181. $this->variantProcessedNow = $element->data['sitemap_variant'];
  182. $this->generatorProcessedNow = $element->data['sitemap_generator'];
  183. $this->processedPaths = [];
  184. }
  185. $this->generateResultsFromElement($element);
  186. if (!empty($this->maxLinks) && count($this->results) >= $this->maxLinks) {
  187. $this->generateVariantChunksFromResults();
  188. }
  189. }
  190. catch (\Exception $e) {
  191. watchdog_exception('simple_sitemap', $e);
  192. }
  193. $this->queue->deleteItem($element); //todo May want to use deleteItems() instead.
  194. $this->elementsRemaining--;
  195. }
  196. if ($this->getQueuedElementCount() === 0) {
  197. $this->generateVariantChunksFromResults(TRUE);
  198. $this->publishCurrentVariant();
  199. }
  200. else {
  201. $this->stashResults();
  202. }
  203. return $this;
  204. }
  205. /**
  206. * @param $element
  207. * @throws \Drupal\Component\Plugin\Exception\PluginException
  208. */
  209. protected function generateResultsFromElement($element) {
  210. $results = $this->manager->getUrlGenerator($element->data['url_generator'])
  211. ->setSitemapVariant($this->variantProcessedNow)
  212. ->setSettings($this->generatorSettings)
  213. ->generate($element->data['data']);
  214. $this->removeDuplicates($results);
  215. $this->results = array_merge($this->results, $results);
  216. }
  217. /**
  218. * @param array $results
  219. */
  220. protected function removeDuplicates(&$results) {
  221. if ($this->generatorSettings['remove_duplicates'] && !empty($results)) {
  222. $result = $results[key($results)];
  223. if (!empty($result['meta']['path'])) {
  224. if (in_array($result['meta']['path'], $this->processedPaths)) {
  225. $results = [];
  226. }
  227. else {
  228. $this->processedPaths[] = $result['meta']['path'];
  229. }
  230. }
  231. }
  232. }
  233. /**
  234. * @param bool $complete
  235. * @throws \Drupal\Component\Plugin\Exception\PluginException
  236. */
  237. protected function generateVariantChunksFromResults($complete = FALSE) {
  238. if (!empty($this->results)) {
  239. $generator = $this->manager->getSitemapGenerator($this->generatorProcessedNow)
  240. ->setSitemapVariant($this->variantProcessedNow)
  241. ->setSettings($this->generatorSettings);
  242. if (empty($this->maxLinks) || $complete) {
  243. $generator->generate($this->results);
  244. $this->results = [];
  245. }
  246. else {
  247. foreach (array_chunk($this->results, $this->maxLinks, TRUE) as $chunk_links) {
  248. if (count($chunk_links) === $this->maxLinks || $complete) {
  249. $generator->generate($chunk_links);
  250. $this->results = array_diff_key($this->results, $chunk_links);
  251. }
  252. }
  253. }
  254. };
  255. }
  256. protected function publishCurrentVariant() {
  257. if ($this->variantProcessedNow !== NULL) {
  258. $this->manager->getSitemapGenerator($this->generatorProcessedNow)
  259. ->setSitemapVariant($this->variantProcessedNow)
  260. ->setSettings($this->generatorSettings)
  261. ->generateIndex()
  262. ->publish();
  263. }
  264. }
  265. protected function stashResults() {
  266. $this->state->set('simple_sitemap.queue_stashed_results', [
  267. 'variant' => $this->variantProcessedNow,
  268. 'generator' => $this->generatorProcessedNow,
  269. 'results' => $this->results,
  270. 'processed_paths' => $this->processedPaths,
  271. ]);
  272. $this->results = [];
  273. $this->processedPaths = [];
  274. $this->generatorProcessedNow = NULL;
  275. $this->variantProcessedNow = NULL;
  276. }
  277. protected function unstashResults() {
  278. if (NULL !== $results = $this->state->get('simple_sitemap.queue_stashed_results')) {
  279. $this->state->delete('simple_sitemap.queue_stashed_results');
  280. $this->results = !empty($results['results']) ? $results['results'] : [];
  281. $this->processedPaths = !empty($results['processed_paths']) ? $results['processed_paths'] : [];
  282. $this->variantProcessedNow = $results['variant'];
  283. $this->generatorProcessedNow = $results['generator'];
  284. }
  285. }
  286. public function getInitialElementCount() {
  287. if (NULL === $this->elementsTotal) {
  288. $this->elementsTotal = (int) $this->state->get('simple_sitemap.queue_items_initial_amount', 0);
  289. }
  290. return $this->elementsTotal;
  291. }
  292. /**
  293. * @param bool $force_recount
  294. * @return int
  295. */
  296. public function getQueuedElementCount($force_recount = FALSE) {
  297. if ($force_recount || NULL === $this->elementsRemaining) {
  298. $this->elementsRemaining = $this->queue->numberOfItems();
  299. }
  300. return $this->elementsRemaining;
  301. }
  302. /**
  303. * @return int
  304. */
  305. public function getStashedResultCount() {
  306. return count($this->state->get('simple_sitemap.queue_stashed_results', ['results' => []])['results']);
  307. }
  308. /**
  309. * @return int
  310. */
  311. public function getProcessedElementCount() {
  312. $initial = $this->getInitialElementCount();
  313. $remaining = $this->getQueuedElementCount();
  314. return $initial > $remaining ? ($initial - $remaining) : 0;
  315. }
  316. /**
  317. * @return bool
  318. */
  319. public function generationInProgress() {
  320. return 0 < ($this->getQueuedElementCount() + $this->getStashedResultCount());
  321. }
  322. }