ce1198faf773c311d357785f7f0598d1ebfa326d
[yaffs-website] / web / modules / contrib / simple_sitemap / src / SitemapGenerator.php
1 <?php
2
3 namespace Drupal\simple_sitemap;
4
5 use XMLWriter;
6 use Drupal\simple_sitemap\Batch\Batch;
7 use Drupal\Core\Database\Connection;
8 use Drupal\Core\Extension\ModuleHandler;
9 use Drupal\Core\Language\LanguageManagerInterface;
10
11 /**
12  * Class SitemapGenerator
13  * @package Drupal\simple_sitemap
14  */
15 class SitemapGenerator {
16
17   const XML_VERSION = '1.0';
18   const ENCODING = 'UTF-8';
19   const XMLNS = 'http://www.sitemaps.org/schemas/sitemap/0.9';
20   const XMLNS_XHTML = 'http://www.w3.org/1999/xhtml';
21   const GENERATED_BY = 'Generated by the Simple XML sitemap Drupal module: https://drupal.org/project/simple_sitemap.';
22   const FIRST_CHUNK_INDEX = 1;
23
24   /**
25    * @var \Drupal\simple_sitemap\Batch\Batch
26    */
27   protected $batch;
28
29   /**
30    * @var \Drupal\simple_sitemap\EntityHelper
31    */
32   protected $entityHelper;
33
34   /**
35    * @var \Drupal\Core\Database\Connection
36    */
37   protected $db;
38
39
40   /**
41    * @var \Drupal\Core\Language\LanguageManagerInterface
42    */
43   protected $languageManager;
44
45   /**
46    * @var \Drupal\Core\Extension\ModuleHandler
47    */
48   protected $moduleHandler;
49
50   /**
51    * @var string
52    */
53   protected $generateFrom = 'form';
54
55   /**
56    * @var bool
57    */
58   protected $isHreflangSitemap;
59
60   /**
61    * @var \Drupal\simple_sitemap\Simplesitemap
62    */
63   protected $generator;
64
65   /**
66    * SitemapGenerator constructor.
67    * @param \Drupal\simple_sitemap\Batch\Batch $batch
68    * @param \Drupal\simple_sitemap\EntityHelper $entityHelper
69    * @param \Drupal\Core\Database\Connection $database
70    * @param \Drupal\Core\Extension\ModuleHandler $module_handler
71    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
72    */
73   public function __construct(
74     Batch $batch,
75     EntityHelper $entityHelper,
76     Connection $database,
77     ModuleHandler $module_handler,
78     LanguageManagerInterface $language_manager
79   ) {
80     $this->batch = $batch;
81     $this->entityHelper = $entityHelper;
82     $this->db = $database;
83     $this->moduleHandler = $module_handler;
84     $this->languageManager = $language_manager;
85     $this->setIsHreflangSitemap();
86   }
87
88   protected function setIsHreflangSitemap() {
89     $this->isHreflangSitemap = count($this->languageManager->getLanguages()) > 1;
90   }
91
92   /**
93    * @return bool
94    */
95   public function isHreflangSitemap() {
96     return $this->isHreflangSitemap;
97   }
98
99   /**
100    * @param \Drupal\simple_sitemap\Simplesitemap $generator
101    * @return $this
102    */
103   public function setGenerator(Simplesitemap $generator) {
104     $this->generator = $generator;
105     return $this;
106   }
107
108   /**
109    * @param string $from
110    * @return $this
111    */
112   public function setGenerateFrom($from) {
113     $this->generateFrom = $from;
114     return $this;
115   }
116
117   /**
118    * Adds all operations to the batch and starts it.
119    */
120   public function startGeneration() {
121     $this->batch->setBatchInfo([
122       'from' => $this->generateFrom,
123       'batch_process_limit' => !empty($this->generator->getSetting('batch_process_limit'))
124         ? $this->generator->getSetting('batch_process_limit') : NULL,
125       'max_links' => $this->generator->getSetting('max_links', 2000),
126       'skip_untranslated' => $this->generator->getSetting('skip_untranslated', FALSE),
127       'remove_duplicates' => $this->generator->getSetting('remove_duplicates', TRUE),
128       'entity_types' => $this->generator->getBundleSettings(),
129       'base_url' => $this->generator->getSetting('base_url', ''),
130     ]);
131     // Add custom link generating operation.
132     $this->batch->addOperation('generateCustomUrls', $this->getCustomUrlsData());
133
134     // Add entity link generating operations.
135     foreach ($this->getEntityTypeData() as $data) {
136       $this->batch->addOperation('generateBundleUrls', $data);
137     }
138     $this->batch->start();
139   }
140
141   /**
142    * Returns a batch-ready data array for custom link generation.
143    *
144    * @return array
145    *   Data to be processed.
146    */
147   protected function getCustomUrlsData() {
148     $paths = [];
149     foreach ($this->generator->getCustomLinks() as $i => $custom_path) {
150       $paths[$i]['path'] = $custom_path['path'];
151       $paths[$i]['priority'] = isset($custom_path['priority']) ? $custom_path['priority'] : NULL;
152       // todo: implement lastmod.
153       $paths[$i]['lastmod'] = NULL;
154     }
155     return $paths;
156   }
157
158   /**
159    * Collects entity metadata for entities that are set to be indexed
160    * and returns an array of batch-ready data sets for entity link generation.
161    *
162    * @return array
163    */
164   protected function getEntityTypeData() {
165     $data_sets = [];
166     $sitemap_entity_types = $this->entityHelper->getSitemapEntityTypes();
167     $entity_types = $this->generator->getBundleSettings();
168     foreach ($entity_types as $entity_type_name => $bundles) {
169       if (isset($sitemap_entity_types[$entity_type_name])) {
170         $keys = $sitemap_entity_types[$entity_type_name]->getKeys();
171
172         // Menu fix.
173         $keys['bundle'] = $entity_type_name == 'menu_link_content' ? 'menu_name' : $keys['bundle'];
174
175         foreach ($bundles as $bundle_name => $bundle_settings) {
176           if ($bundle_settings['index']) {
177             $data_sets[] = [
178               'bundle_settings' => $bundle_settings,
179               'bundle_name' => $bundle_name,
180               'entity_type_name' => $entity_type_name,
181               'keys' => $keys,
182             ];
183           }
184         }
185       }
186     }
187     return $data_sets;
188   }
189
190   /**
191    * Wrapper method which takes links along with their options, lets other
192    * modules alter the links and then generates and saves the sitemap.
193    *
194    * @param array $links
195    *   All links with their multilingual versions and settings.
196    * @param bool $remove_sitemap
197    *   Remove old sitemap from database before inserting the new one.
198    */
199   public function generateSitemap(array $links, $remove_sitemap = FALSE) {
200     // Invoke alter hook.
201     $this->moduleHandler->alter('simple_sitemap_links', $links);
202
203     $values = [
204       'id' => $remove_sitemap ? self::FIRST_CHUNK_INDEX
205         : $this->db->query('SELECT MAX(id) FROM {simple_sitemap}')
206           ->fetchField() + 1,
207       'sitemap_string' => $this->generateSitemapChunk($links),
208       'sitemap_created' => REQUEST_TIME,
209     ];
210     if ($remove_sitemap) {
211       $this->db->truncate('simple_sitemap')->execute();
212     }
213     $this->db->insert('simple_sitemap')->fields($values)->execute();
214   }
215
216   /**
217    * Generates and returns the sitemap index for all sitemap chunks.
218    *
219    * @param array $chunk_info
220    *   Array containing chunk creation timestamps keyed by chunk ID.
221    *
222    * @return string sitemap index
223    */
224   public function generateSitemapIndex(array $chunk_info) {
225     $writer = new XMLWriter();
226     $writer->openMemory();
227     $writer->setIndent(TRUE);
228     $writer->startDocument(self::XML_VERSION, self::ENCODING);
229     $writer->writeComment(self::GENERATED_BY);
230     $writer->startElement('sitemapindex');
231     $writer->writeAttribute('xmlns', self::XMLNS);
232
233     foreach ($chunk_info as $chunk_id => $chunk_data) {
234       $writer->startElement('sitemap');
235       $writer->writeElement('loc', $this->getCustomBaseUrl() . '/sitemaps/' . $chunk_id . '/' . 'sitemap.xml');
236       $writer->writeElement('lastmod', date_iso8601($chunk_data->sitemap_created));
237       $writer->endElement();
238     }
239     $writer->endElement();
240     $writer->endDocument();
241     return $writer->outputMemory();
242   }
243
244   public function getCustomBaseUrl() {
245     $customBaseUrl = $this->generator->getSetting('base_url', '');
246     return !empty($customBaseUrl) ? $customBaseUrl : $GLOBALS['base_url'];
247   }
248
249   /**
250    * Generates and returns a sitemap chunk.
251    *
252    * @param array $links
253    *   All links with their multilingual versions and settings.
254    *
255    * @return string
256    *   Sitemap chunk
257    */
258   protected function generateSitemapChunk(array $links) {
259     $writer = new XMLWriter();
260     $writer->openMemory();
261     $writer->setIndent(TRUE);
262     $writer->startDocument(self::XML_VERSION, self::ENCODING);
263     $writer->writeComment(self::GENERATED_BY);
264     $writer->startElement('urlset');
265     $writer->writeAttribute('xmlns', self::XMLNS);
266
267     if ($this->isHreflangSitemap()) {
268       $writer->writeAttribute('xmlns:xhtml', self::XMLNS_XHTML);
269     }
270
271     foreach ($links as $link) {
272
273       // Add each translation variant URL as location to the sitemap.
274       $writer->startElement('url');
275       $writer->writeElement('loc', $link['url']);
276
277       // If more than one language is enabled, add all translation variant URLs
278       // as alternate links to this location turning the sitemap into a hreflang
279       // sitemap.
280       if ($this->isHreflangSitemap()) {
281         foreach ($link['alternate_urls'] as $language_id => $alternate_url) {
282           $writer->startElement('xhtml:link');
283           $writer->writeAttribute('rel', 'alternate');
284           $writer->writeAttribute('hreflang', $language_id);
285           $writer->writeAttribute('href', $alternate_url);
286           $writer->endElement();
287         }
288       }
289
290       // Add lastmod if any.
291       if (isset($link['lastmod'])) {
292         $writer->writeElement('lastmod', $link['lastmod']);
293       }
294
295       //todo: Implement changefreq here.
296
297       // Add priority if any.
298       if (isset($link['priority'])) {
299         $writer->writeElement('priority', $link['priority']);
300       }
301
302       $writer->endElement();
303     }
304     $writer->endElement();
305     $writer->endDocument();
306     return $writer->outputMemory();
307   }
308
309 }