Pathologic was missing because of a .git folder inside.
[yaffs-website] / web / modules / contrib / simple_sitemap / src / Simplesitemap.php
1 <?php
2
3 namespace Drupal\simple_sitemap;
4
5 use Drupal\simple_sitemap\Form\FormHelper;
6 use Drupal\Core\Database\Connection;
7 use Drupal\Core\Entity\EntityTypeManagerInterface;
8 use Drupal\Core\Path\PathValidator;
9 use Drupal\Core\Entity\Query\QueryFactory;
10 use Drupal\Core\Config\ConfigFactory;
11 use Drupal\Core\Datetime\DateFormatter;
12
13 /**
14  * Class Simplesitemap
15  * @package Drupal\simple_sitemap
16  */
17 class Simplesitemap {
18
19   /**
20    * @var \Drupal\simple_sitemap\SitemapGenerator
21    */
22   protected $sitemapGenerator;
23
24   /**
25    * @var \Drupal\simple_sitemap\EntityHelper
26    */
27   protected $entityHelper;
28
29   /**
30    * @var \Drupal\Core\Config\ConfigFactory
31    */
32   protected $configFactory;
33
34   /**
35    * @var \Drupal\Core\Database\Connection
36    */
37   protected $db;
38
39   /**
40    * @var \Drupal\Core\Entity\Query\QueryFactory
41    */
42   protected $entityQuery;
43
44   /**
45    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
46    */
47   protected $entityTypeManager;
48
49   /**
50    * @var \Drupal\Core\Path\PathValidator
51    */
52   protected $pathValidator;
53
54   /**
55    * @var array
56    */
57   protected static $allowed_link_settings = [
58     'entity' => ['index', 'priority'],
59     'custom' => ['priority'],
60   ];
61
62   /**
63    * Simplesitemap constructor.
64    * @param \Drupal\simple_sitemap\SitemapGenerator $sitemapGenerator
65    * @param \Drupal\simple_sitemap\EntityHelper $entityHelper
66    * @param \Drupal\Core\Config\ConfigFactory $configFactory
67    * @param \Drupal\Core\Database\Connection $database
68    * @param \Drupal\Core\Entity\Query\QueryFactory $entityQuery
69    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
70    * @param \Drupal\Core\Path\PathValidator $pathValidator
71    * @param \Drupal\Core\Datetime\DateFormatter $dateFormatter
72    */
73   public function __construct(
74     SitemapGenerator $sitemapGenerator,
75     EntityHelper $entityHelper,
76     ConfigFactory $configFactory,
77     Connection $database,
78     QueryFactory $entityQuery,
79     EntityTypeManagerInterface $entityTypeManager,
80     PathValidator $pathValidator,
81     DateFormatter $dateFormatter
82   ) {
83     $this->sitemapGenerator = $sitemapGenerator;
84     $this->entityHelper = $entityHelper;
85     $this->configFactory = $configFactory;
86     $this->db = $database;
87     $this->entityQuery = $entityQuery;
88     $this->entityTypeManager = $entityTypeManager;
89     $this->pathValidator = $pathValidator;
90     $this->dateFormatter = $dateFormatter;
91   }
92
93   /**
94    * Returns a specific sitemap setting or a default value if setting does not
95    * exist.
96    *
97    * @param string $name
98    *   Name of the setting, like 'max_links'.
99    *
100    * @param mixed $default
101    *   Value to be returned if the setting does not exist in the configuration.
102    *
103    * @return mixed
104    *   The current setting from configuration or a default value.
105    */
106   public function getSetting($name, $default = FALSE) {
107     $setting = $this->configFactory
108       ->get('simple_sitemap.settings')
109       ->get($name);
110     return NULL !== $setting ? $setting : $default;
111   }
112
113   /**
114    * Stores a specific sitemap setting in configuration.
115    *
116    * @param string $name
117    *   Setting name, like 'max_links'.
118    * @param mixed $setting
119    *   The setting to be saved.
120    *
121    * @return $this
122    */
123   public function saveSetting($name, $setting) {
124     $this->configFactory->getEditable("simple_sitemap.settings")
125       ->set($name, $setting)->save();
126     return $this;
127   }
128
129   /**
130    * Returns the whole sitemap, a requested sitemap chunk,
131    * or the sitemap index file.
132    *
133    * @param int $chunk_id
134    *
135    * @return string|false
136    *   If no sitemap id provided, either a sitemap index is returned, or the
137    *   whole sitemap, if the amount of links does not exceed the max links
138    *   setting. If a sitemap id is provided, a sitemap chunk is returned. False
139    *   if sitemap is not retrievable from the database.
140    */
141   public function getSitemap($chunk_id = NULL) {
142     $chunk_info = $this->fetchSitemapChunkInfo();
143
144     if (NULL === $chunk_id || !isset($chunk_info[$chunk_id])) {
145
146       if (count($chunk_info) > 1) {
147         // Return sitemap index, if there are multiple sitemap chunks.
148         return $this->getSitemapIndex($chunk_info);
149       }
150       else {
151         // Return sitemap if there is only one chunk.
152         return count($chunk_info) === 1
153         && isset($chunk_info[SitemapGenerator::FIRST_CHUNK_INDEX])
154           ? $this->fetchSitemapChunk(SitemapGenerator::FIRST_CHUNK_INDEX)
155             ->sitemap_string
156           : FALSE;
157       }
158     }
159     else {
160       // Return specific sitemap chunk.
161       return $this->fetchSitemapChunk($chunk_id)->sitemap_string;
162     }
163   }
164
165   /**
166    * Fetches all sitemap chunk timestamps keyed by chunk ID.
167    *
168    * @return array
169    *   An array containing chunk creation timestamps keyed by chunk ID.
170    */
171   protected function fetchSitemapChunkInfo() {
172     return $this->db
173       ->query("SELECT id, sitemap_created FROM {simple_sitemap}")
174       ->fetchAllAssoc('id');
175   }
176
177   /**
178    * Fetches a single sitemap chunk by ID.
179    *
180    * @param int $id
181    *   The chunk ID.
182    *
183    * @return object
184    *   A sitemap chunk object.
185    */
186   private function fetchSitemapChunk($id) {
187     return $this->db->query('SELECT * FROM {simple_sitemap} WHERE id = :id',
188       [':id' => $id])->fetchObject();
189   }
190
191   /**
192    * Generates the sitemap for all languages and saves it to the db.
193    *
194    * @param string $from
195    *   Can be 'form', 'cron', 'drush' or 'nobatch'.
196    *   This decides how the batch process is to be run.
197    */
198   public function generateSitemap($from = 'form') {
199     $this->sitemapGenerator
200       ->setGenerator($this)
201       ->setGenerateFrom($from)
202       ->startGeneration();
203   }
204
205   /**
206    * Generates and returns the sitemap index as string.
207    *
208    * @param array $chunk_info
209    *   Array containing chunk creation timestamps keyed by chunk ID.
210    *
211    * @return string
212    *   The sitemap index.
213    */
214   protected function getSitemapIndex($chunk_info) {
215     return $this->sitemapGenerator
216       ->setGenerator($this)
217       ->generateSitemapIndex($chunk_info);
218   }
219
220   /**
221    * Returns a 'time ago' string of last timestamp generation.
222    *
223    * @return string|false
224    *   Formatted timestamp of last sitemap generation, otherwise FALSE.
225    */
226   public function getGeneratedAgo() {
227     $chunks = $this->fetchSitemapChunkInfo();
228     if (isset($chunks[SitemapGenerator::FIRST_CHUNK_INDEX]->sitemap_created)) {
229       return $this->dateFormatter
230         ->formatInterval(REQUEST_TIME - $chunks[SitemapGenerator::FIRST_CHUNK_INDEX]
231             ->sitemap_created);
232     }
233     return FALSE;
234   }
235
236   /**
237    * Enables sitemap support for an entity type. Enabled entity types show
238    * sitemap settings on their bundle setting forms. If an enabled entity type
239    * features bundles (e.g. 'node'), it needs to be set up with
240    * setBundleSettings() as well.
241    *
242    * @param string $entity_type_id
243    *   Entity type id like 'node'.
244    *
245    * @return $this
246    */
247   public function enableEntityType($entity_type_id) {
248     $enabled_entity_types = $this->getSetting('enabled_entity_types');
249     if (!in_array($entity_type_id, $enabled_entity_types)) {
250       $enabled_entity_types[] = $entity_type_id;
251       $this->saveSetting('enabled_entity_types', $enabled_entity_types);
252     }
253     return $this;
254   }
255
256   /**
257    * Disables sitemap support for an entity type. Disabling support for an
258    * entity type deletes its sitemap settings permanently and removes sitemap
259    * settings from entity forms.
260    *
261    * @param string $entity_type_id
262    *  Entity type id like 'node'.
263    *
264    * @return $this
265    */
266   public function disableEntityType($entity_type_id) {
267
268     // Updating settings.
269     $enabled_entity_types = $this->getSetting('enabled_entity_types');
270     if (FALSE !== ($key = array_search($entity_type_id, $enabled_entity_types))) {
271       unset ($enabled_entity_types[$key]);
272       $this->saveSetting('enabled_entity_types', array_values($enabled_entity_types));
273     }
274
275     // Deleting inclusion settings.
276     $config_names = $this->configFactory->listAll("simple_sitemap.bundle_settings.$entity_type_id");
277     foreach($config_names as $config_name) {
278       $this->configFactory->getEditable($config_name)->delete();
279     }
280
281     // Deleting entity overrides.
282     $this->removeEntityInstanceSettings($entity_type_id);
283     return $this;
284   }
285
286   /**
287    * Sets sitemap settings for a non-bundle entity type (e.g. user) or a bundle
288    * of an entity type (e.g. page).
289    *
290    * @param string $entity_type_id
291    *   Entity type id like 'node' the bundle belongs to.
292    * @param string $bundle_name
293    *   Name of the bundle. NULL if entity type has no bundles.
294    * @param array $settings
295    *   An array of sitemap settings for this bundle/entity type.
296    *   Example: ['index' => TRUE, 'priority' => 0.5].
297    *
298    * @return $this
299    */
300   public function setBundleSettings($entity_type_id, $bundle_name = NULL, $settings) {
301
302     $bundle_name = empty($bundle_name) ? $entity_type_id : $bundle_name;
303
304     foreach($settings as $setting_key => $setting) {
305       if ($setting_key == 'index') {
306         $setting = intval($setting);
307       }
308       $this->configFactory
309         ->getEditable("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name")
310         ->set($setting_key, $setting)
311         ->save();
312     }
313     //todo: Use addLinkSettings()?
314
315     // Delete entity overrides which are identical to new bundle setting.
316     $sitemap_entity_types = $this->entityHelper->getSitemapEntityTypes();
317     if (isset($sitemap_entity_types[$entity_type_id])) {
318       $entity_type = $sitemap_entity_types[$entity_type_id];
319       $keys = $entity_type->getKeys();
320
321       // Menu fix.
322       $keys['bundle'] = $entity_type_id == 'menu_link_content' ? 'menu_name' : $keys['bundle'];
323
324       $query = $this->entityQuery->get($entity_type_id);
325       if (!$this->entityHelper->entityTypeIsAtomic($entity_type_id)) {
326         $query->condition($keys['bundle'], $bundle_name);
327       }
328       $entity_ids = $query->execute();
329
330       $bundle_settings = $this->configFactory
331         ->get("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name");
332
333       $query = $this->db->select('simple_sitemap_entity_overrides', 'o')
334         ->fields('o', ['id', 'inclusion_settings'])
335         ->condition('o.entity_type', $entity_type_id);
336       if (!empty($entity_ids)) {
337         $query->condition('o.entity_id', $entity_ids, 'IN');
338       }
339
340       foreach($query->execute()->fetchAll() as $result) {
341         $delete = TRUE;
342         $instance_settings = unserialize($result->inclusion_settings);
343         foreach ($instance_settings as $setting_key => $instance_setting) {
344           if ($instance_setting != $bundle_settings->get($setting_key)) {
345             $delete = FALSE;
346             break;
347           }
348         }
349         if ($delete) {
350           $this->db->delete('simple_sitemap_entity_overrides')
351             ->condition('id', $result->id)
352             ->execute();
353         }
354       }
355     }
356     else {
357       //todo: log error
358     }
359     return $this;
360   }
361
362   /**
363    * Gets sitemap settings for an entity bundle, a non-bundle entity type or for
364    * all entity types and their bundles.
365    *
366    * @param string|null $entity_type_id
367    *  If set to null, sitemap settings for all entity types and their bundles
368    *  are fetched.
369    * @param string|null $bundle_name
370    *
371    * @return array|false
372    *  Array of sitemap settings for an entity bundle, a non-bundle entity type
373    *  or for all entity types and their bundles.
374    *  False if entity type does not exist.
375    */
376   public function getBundleSettings($entity_type_id = NULL, $bundle_name = NULL) {
377     if (NULL !== $entity_type_id) {
378       $bundle_name = empty($bundle_name) ? $entity_type_id : $bundle_name;
379       $settings = $this->configFactory
380         ->get("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name")
381         ->get();
382       $bundle_settings = !empty($settings) ? $settings : FALSE;
383     }
384     else {
385       $config_names = $this->configFactory->listAll("simple_sitemap.bundle_settings");
386       $bundle_settings = [];
387       foreach($config_names as $config_name) {
388         $config_name_parts = explode('.', $config_name);
389         $bundle_settings[$config_name_parts[2]][$config_name_parts[3]]
390           = $this->configFactory->get($config_name)->get();
391       }
392     }
393     return $bundle_settings;
394   }
395
396   /**
397    * Overrides entity bundle/entity type sitemap settings for a single entity.
398    *
399    * @param string $entity_type_id
400    * @param int $id
401    * @param array $settings
402    *
403    * @return $this
404    */
405   public function setEntityInstanceSettings($entity_type_id, $id, $settings) {
406
407     $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($id);
408     $bundle_name = $this->entityHelper->getEntityInstanceBundleName($entity);
409     $bundle_settings = $this->configFactory
410       ->get("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name")
411       ->get();
412
413     if (!empty($bundle_settings)) {
414
415       // Check if overrides are different from bundle setting before saving.
416       $override = FALSE;
417       foreach ($settings as $key => $setting) {
418         if ($setting != $bundle_settings[$key]) {
419           $override = TRUE;
420           break;
421         }
422       }
423       // Save overrides for this entity if something is different.
424       if ($override) {
425         $this->db->merge('simple_sitemap_entity_overrides')
426           ->key([
427             'entity_type' => $entity_type_id,
428             'entity_id' => $id])
429           ->fields([
430             'entity_type' => $entity_type_id,
431             'entity_id' => $id,
432             'inclusion_settings' => serialize($settings),
433           ])
434           ->execute();
435       }
436       // Else unset override.
437       else {
438         $this->removeEntityInstanceSettings($entity_type_id, $id);
439       }
440     }
441     else {
442       //todo: log error
443     }
444     return $this;
445   }
446
447   /**
448    * Gets sitemap settings for an entity instance which overrides the sitemap
449    * settings of its bundle, or bundle settings, if they are not overridden.
450    *
451    * @param string $entity_type_id
452    * @param int $id
453    *
454    * @return array|false
455    */
456   public function getEntityInstanceSettings($entity_type_id, $id) {
457     $results = $this->db->select('simple_sitemap_entity_overrides', 'o')
458       ->fields('o', ['inclusion_settings'])
459       ->condition('o.entity_type', $entity_type_id)
460       ->condition('o.entity_id', $id)
461       ->execute()
462       ->fetchField();
463
464     if (!empty($results)) {
465       return unserialize($results);
466     }
467     else {
468       $entity = $this->entityTypeManager->getStorage($entity_type_id)
469         ->load($id);
470       return $this->getBundleSettings(
471         $entity_type_id,
472         $this->entityHelper->getEntityInstanceBundleName($entity)
473       );
474     }
475   }
476
477   /**
478    * Removes sitemap settings for an entity that overrides the sitemap settings
479    * of its bundle.
480    *
481    * @param string $entity_type_id
482    * @param string|null $entity_ids
483    *
484    * @return $this
485    */
486   public function removeEntityInstanceSettings($entity_type_id, $entity_ids = NULL) {
487     $query = $this->db->delete('simple_sitemap_entity_overrides')
488       ->condition('entity_type', $entity_type_id);
489     if (NULL !== $entity_ids) {
490       $entity_ids = !is_array($entity_ids) ? [$entity_ids] : $entity_ids;
491       $query->condition('entity_id', $entity_ids, 'IN');
492     }
493     $query->execute();
494     return $this;
495   }
496
497   /**
498    * Checks if an entity bundle (or a non-bundle entity type) is set to be
499    * indexed in the sitemap settings.
500    *
501    * @param string $entity_type_id
502    * @param string|null $bundle_name
503    *
504    * @return bool
505    */
506   public function bundleIsIndexed($entity_type_id, $bundle_name = NULL) {
507     $settings = $this->getBundleSettings($entity_type_id, $bundle_name);
508     return !empty($settings['index']);
509   }
510
511   /**
512    * Checks if an entity type is enabled in the sitemap settings.
513    *
514    * @param string $entity_type_id
515    *
516    * @return bool
517    */
518   public function entityTypeIsEnabled($entity_type_id) {
519     return in_array($entity_type_id, $this->getSetting('enabled_entity_types', []));
520   }
521
522   /**
523    * Stores a custom path along with its sitemap settings to configuration.
524    *
525    * @param string $path
526    * @param array $settings
527    *
528    * @return $this
529    */
530   public function addCustomLink($path, $settings) {
531     if (!$this->pathValidator->isValid($path)) {
532       // todo: log error.
533       return $this;
534     }
535     if ($path[0] != '/') {
536       // todo: log error.
537       return $this;
538     }
539
540     $custom_links = $this->getCustomLinks();
541     foreach ($custom_links as $key => $link) {
542       if ($link['path'] == $path) {
543         $link_key = $key;
544         break;
545       }
546     }
547     $link_key = isset($link_key) ? $link_key : count($custom_links);
548     $custom_links[$link_key]['path'] = $path;
549     $this->addLinkSettings('custom', $settings, $custom_links[$link_key]); //todo: dirty
550     $this->configFactory->getEditable("simple_sitemap.custom")
551       ->set('links', $custom_links)->save();
552     return $this;
553   }
554
555   /**
556    *
557    */
558   protected function addLinkSettings($type, $settings, &$target) {
559     foreach ($settings as $setting_key => $setting) {
560       if (in_array($setting_key, self::$allowed_link_settings[$type])) {
561         switch ($setting_key) {
562           case 'priority':
563             if (!FormHelper::isValidPriority($setting)) {
564               // todo: log error.
565               continue;
566             }
567             break;
568
569           // todo: add index check.
570         }
571         $target[$setting_key] = $setting;
572       }
573     }
574   }
575
576   /**
577    * Returns an array of custom paths and their sitemap settings.
578    *
579    * @return array
580    */
581   public function getCustomLinks() {
582     $custom_links = $this->configFactory
583       ->get('simple_sitemap.custom')
584       ->get('links');
585     return $custom_links !== NULL ? $custom_links : [];
586   }
587
588   /**
589    * Returns settings for a custom path added to the sitemap settings.
590    *
591    * @param string $path
592    *
593    * @return array|false
594    */
595   public function getCustomLink($path) {
596     foreach ($this->getCustomLinks() as $key => $link) {
597       if ($link['path'] == $path) {
598         return $link;
599       }
600     }
601     return FALSE;
602   }
603
604   /**
605    * Removes a custom path from the sitemap settings.
606    *
607    * @param string $path
608    *
609    * @return $this
610    */
611   public function removeCustomLink($path) {
612     $custom_links = $this->getCustomLinks();
613     foreach ($custom_links as $key => $link) {
614       if ($link['path'] == $path) {
615         unset($custom_links[$key]);
616         $custom_links = array_values($custom_links);
617         $this->configFactory->getEditable("simple_sitemap.custom")
618           ->set('links', $custom_links)->save();
619         break;
620       }
621     }
622     return $this;
623   }
624
625   /**
626    * Removes all custom paths from the sitemap settings.
627    *
628    * @return $this
629    */
630   public function removeCustomLinks() {
631     $this->configFactory->getEditable("simple_sitemap.custom")
632       ->set('links', [])->save();
633     return $this;
634   }
635 }