Simplesitemap.php 28.4 KB
Newer Older
1 2
<?php

gbyte.co's avatar
gbyte.co committed
3
namespace Drupal\simple_sitemap;
4

gbyte.co's avatar
gbyte.co committed
5
use Drupal\Core\Database\Connection;
6
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
gbyte.co's avatar
gbyte.co committed
7
use Drupal\Core\Entity\EntityTypeManagerInterface;
8
use Drupal\simple_sitemap\Queue\QueueWorker;
gbyte.co's avatar
gbyte.co committed
9
use Drupal\Core\Path\PathValidator;
gbyte.co's avatar
gbyte.co committed
10 11
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Datetime\DateFormatter;
12
use Drupal\Component\Datetime\Time;
13
use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorBase;
14

15
/**
gbyte.co's avatar
gbyte.co committed
16
 * Class Simplesitemap
gbyte.co's avatar
gbyte.co committed
17
 * @package Drupal\simple_sitemap
18 19
 */
class Simplesitemap {
gbyte.co's avatar
gbyte.co committed
20

gbyte.co's avatar
gbyte.co committed
21 22 23
  /**
   * @var \Drupal\simple_sitemap\EntityHelper
   */
24
  protected $entityHelper;
gbyte.co's avatar
gbyte.co committed
25

26 27 28 29 30 31 32 33 34 35
  /**
   * @var \Drupal\simple_sitemap\SimplesitemapSettings
   */
  protected $settings;

  /**
   * @var \Drupal\simple_sitemap\SimplesitemapManager
   */
  protected $manager;

gbyte.co's avatar
gbyte.co committed
36 37 38
  /**
   * @var \Drupal\Core\Config\ConfigFactory
   */
39
  protected $configFactory;
gbyte.co's avatar
gbyte.co committed
40 41 42 43

  /**
   * @var \Drupal\Core\Database\Connection
   */
44
  protected $db;
gbyte.co's avatar
gbyte.co committed
45 46 47 48

  /**
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
49
  protected $entityTypeManager;
gbyte.co's avatar
gbyte.co committed
50

51 52 53 54 55
  /**
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
   */
  protected $entityTypeBundleInfo;

gbyte.co's avatar
gbyte.co committed
56 57 58
  /**
   * @var \Drupal\Core\Path\PathValidator
   */
59
  protected $pathValidator;
gbyte.co's avatar
gbyte.co committed
60

61 62 63 64 65
  /**
   * @var \Drupal\Core\Datetime\DateFormatter
   */
  protected $dateFormatter;

66 67 68 69 70
  /**
   * @var \Drupal\Component\Datetime\Time
   */
  protected $time;

71
  /**
72
   * @var \Drupal\simple_sitemap\Queue\QueueWorker
73
   */
74
  protected $queueWorker;
75

76 77 78 79 80
  /**
   * @var array
   */
  protected $variants;

gbyte.co's avatar
gbyte.co committed
81 82 83
  /**
   * @var array
   */
gbyte.co's avatar
gbyte.co committed
84
  protected static $allowedLinkSettings = [
85
    'entity' => ['index', 'priority', 'changefreq', 'include_images'],
86 87 88
    'custom' => ['priority', 'changefreq'],
  ];

gbyte.co's avatar
gbyte.co committed
89 90 91 92
  /**
   * @var array
   */
  protected static $linkSettingDefaults = [
93
    'index' => FALSE,
94
    'priority' => '0.5',
95
    'changefreq' => '',
96
    'include_images' => FALSE,
97
  ];
98

99 100
  /**
   * Simplesitemap constructor.
101
   * @param \Drupal\simple_sitemap\EntityHelper $entity_helper
102 103
   * @param \Drupal\simple_sitemap\SimplesitemapSettings $settings
   * @param \Drupal\simple_sitemap\SimplesitemapManager $manager
104
   * @param \Drupal\Core\Config\ConfigFactory $config_factory
105
   * @param \Drupal\Core\Database\Connection $database
106
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
107
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
108 109
   * @param \Drupal\Core\Path\PathValidator $path_validator
   * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
110
   * @param \Drupal\Component\Datetime\Time $time
111
   * @param \Drupal\simple_sitemap\Queue\QueueWorker $queue_worker
112
   */
113
  public function __construct(
114
    EntityHelper $entity_helper,
115 116
    SimplesitemapSettings $settings,
    SimplesitemapManager $manager,
117
    ConfigFactory $config_factory,
gbyte.co's avatar
gbyte.co committed
118
    Connection $database,
119
    EntityTypeManagerInterface $entity_type_manager,
120
    EntityTypeBundleInfoInterface $entity_type_bundle_info,
121 122
    PathValidator $path_validator,
    DateFormatter $date_formatter,
123
    Time $time,
124
    QueueWorker $queue_worker
125
  ) {
126
    $this->entityHelper = $entity_helper;
127 128
    $this->settings = $settings;
    $this->manager = $manager;
129
    $this->configFactory = $config_factory;
130
    $this->db = $database;
131
    $this->entityTypeManager = $entity_type_manager;
132
    $this->entityTypeBundleInfo = $entity_type_bundle_info;
133 134
    $this->pathValidator = $path_validator;
    $this->dateFormatter = $date_formatter;
135
    $this->time = $time;
136
    $this->queueWorker = $queue_worker;
137 138
  }

139
  /**
140 141
   * Returns a specific sitemap setting or a default value if setting does not
   * exist.
gbyte.co's avatar
gbyte.co committed
142
   *
143
   * @param string $name
gbyte.co's avatar
gbyte.co committed
144
   *  Name of the setting, like 'max_links'.
145 146
   *
   * @param mixed $default
gbyte.co's avatar
gbyte.co committed
147
   *  Value to be returned if the setting does not exist in the configuration.
148 149
   *
   * @return mixed
gbyte.co's avatar
gbyte.co committed
150
   *  The current setting from configuration or a default value.
gbyte.co's avatar
gbyte.co committed
151
   */
152
  public function getSetting($name, $default = FALSE) {
153
    return $this->settings->getSetting($name, $default);
154 155 156 157 158 159
  }

  /**
   * Stores a specific sitemap setting in configuration.
   *
   * @param string $name
gbyte.co's avatar
gbyte.co committed
160
   *  Setting name, like 'max_links'.
161
   *
162
   * @param mixed $setting
gbyte.co's avatar
gbyte.co committed
163
   *  The setting to be saved.
164 165 166 167
   *
   * @return $this
   */
  public function saveSetting($name, $setting) {
168
    $this->settings->saveSetting($name, $setting);
169

170 171 172
    return $this;
  }

gbyte.co's avatar
gbyte.co committed
173
  /**
174
   * @return \Drupal\simple_sitemap\Queue\QueueWorker
gbyte.co's avatar
gbyte.co committed
175
   */
176 177
  public function getQueueWorker() {
    return $this->queueWorker;
gbyte.co's avatar
gbyte.co committed
178 179 180
  }

  /**
181
   * @return \Drupal\simple_sitemap\SimplesitemapManager
gbyte.co's avatar
gbyte.co committed
182
   */
183 184
  public function getSitemapManager() {
    return $this->manager;
185 186
  }

187 188 189
  /**
   * @param array|string|true|null $variants
   *  array: Array of variants to be set.
190
   *  string: A particular variant to be set.
191 192 193 194 195 196 197
   *  null: Default variant will be set.
   *  true: All existing variants will be set.
   *
   * @return $this
   */
  public function setVariants($variants = NULL) {
    if (NULL === $variants) {
198 199 200
      $this->variants = !empty($default_variant = $this->getSetting('default_variant', ''))
        ? [$default_variant]
        : [];
201 202 203 204 205 206
    }
    elseif ($variants === TRUE) {
      $this->variants = array_keys(
        $this->manager->getSitemapVariants(NULL, FALSE));
    }
    else {
gbyte.co's avatar
gbyte.co committed
207
      $this->variants = (array) $variants;
208 209 210 211 212 213
    }

    return $this;
  }

  /**
214 215
   * Gets the currently set variants, the default variant, or all variants.
   *
216
   * @param bool $default_get_all
217 218 219
   *  If true and no variants are set, all variants are returned. If false and
   *  no variants are set, only the default variant is returned.
   *
220 221
   * @return array
   */
222
  protected function getVariants($default_get_all = TRUE) {
223
    if (NULL === $this->variants) {
224
      $this->setVariants($default_get_all ? TRUE : NULL);
225 226 227 228 229
    }

    return $this->variants;
  }

230
  /**
gbyte.co's avatar
gbyte.co committed
231
   * Returns a sitemap variant, its index, or its requested chunk.
232
   *
gbyte.co's avatar
gbyte.co committed
233
   * @param int|null $delta
234
   *  Optional delta of the chunk.
235 236
   *
   * @return string|false
gbyte.co's avatar
gbyte.co committed
237 238 239 240
   *  If no chunk delta is provided, either the sitemap variant is returned,
   *  or its index in case of a chunked sitemap.
   *  If a chunk delta is provided, the relevant chunk is returned.
   *  Returns false if the sitemap variant is not retrievable from the database.
241
   */
242 243
  public function getSitemap($delta = NULL) {
    $chunk_info = $this->fetchSitemapVariantInfo();
244

245
    if (empty($delta) || !isset($chunk_info[$delta])) {
246

247
      if (isset($chunk_info[SitemapGeneratorBase::INDEX_DELTA])) {
gbyte.co's avatar
gbyte.co committed
248
        // Return sitemap index if one exists.
249 250
        return $this->fetchSitemapChunk($chunk_info[SitemapGeneratorBase::INDEX_DELTA]->id)
          ->sitemap_string;
251 252
      }
      else {
gbyte.co's avatar
gbyte.co committed
253
        // Return sitemap chunk if there is only one chunk.
254 255
        return isset($chunk_info[SitemapGeneratorBase::FIRST_CHUNK_DELTA])
          ? $this->fetchSitemapChunk($chunk_info[SitemapGeneratorBase::FIRST_CHUNK_DELTA]->id)
256 257 258 259 260 261
            ->sitemap_string
          : FALSE;
      }
    }
    else {
      // Return specific sitemap chunk.
262
      return $this->fetchSitemapChunk($chunk_info[$delta]->id)->sitemap_string;
263 264 265 266
    }
  }

  /**
267
   * Fetches info about all published sitemap variants and their chunks.
268 269
   *
   * @return array
270
   *  An array containing all published sitemap chunk IDs, deltas and creation
271 272
   *  timestamps keyed by the currently set variants, or in case of only one
   *  variant set the above keyed by sitemap delta.
273
   */
274
  protected function fetchSitemapVariantInfo() {
275 276 277 278 279 280 281 282 283 284 285 286 287 288
    if (!empty($this->getVariants())) {
      $result = $this->db->select('simple_sitemap', 's')
        ->fields('s', ['id', 'delta', 'sitemap_created', 'type'])
        ->condition('s.status', 1)
        ->condition('s.type', $this->getVariants(), 'IN')
        ->execute();

      return count($this->getVariants()) > 1
        ? $result->fetchAllAssoc('type')
        : $result->fetchAllAssoc('delta');
    }
    else {
      return [];
    }
289 290
  }

291 292 293 294 295 296 297 298 299
  /**
   * Fetches a single sitemap chunk by ID.
   *
   * @param int $id
   *   The chunk ID.
   *
   * @return object
   *   A sitemap chunk object.
   */
300
  protected function fetchSitemapChunk($id) {
301 302 303 304
    return $this->db->query('SELECT * FROM {simple_sitemap} WHERE id = :id',
      [':id' => $id])->fetchObject();
  }

305
  /**
306 307
   * Removes sitemap instances for the currently set variants.
   *
308
   * @return $this
309
   * @throws \Drupal\Component\Plugin\Exception\PluginException
310
   */
311
  public function removeSitemap() {
312
    $this->manager->removeSitemap($this->getVariants(FALSE));
313

314 315 316
    return $this;
  }

317
  /**
318 319
   * Generates all sitemaps.
   *
320
   * @param string $from
321 322
   *  Can be 'form', 'drush', 'cron' and 'backend'.
   *
gbyte.co's avatar
gbyte.co committed
323
   * @return $this
324
   *
gbyte.co's avatar
gbyte.co committed
325
   * @throws \Drupal\Component\Plugin\Exception\PluginException
326
   *
327 328
   * @todo Respect $this->variants and generate for specific variants.
   * @todo Implement lock functionality.
329
   */
330
  public function generateSitemap($from = 'form') {
331 332 333
    switch($from) {
      case 'form':
      case 'drush':
gbyte.co's avatar
gbyte.co committed
334
        $this->queueWorker->batchGenerateSitemap($from);
335 336 337 338
        break;

      case 'cron':
      case 'backend':
gbyte.co's avatar
gbyte.co committed
339
        $this->queueWorker->generateSitemap($from);
340
        break;
341
    }
gbyte.co's avatar
gbyte.co committed
342 343

    return $this;
344
  }
345

346 347 348 349 350 351
  /**
   * Rebuilds the queue for the currently set variants.
   *
   * @return $this
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
352 353
  public function rebuildQueue() {
    $this->queueWorker->rebuildQueue($this->getVariants());
gbyte.co's avatar
gbyte.co committed
354 355

    return $this;
356 357 358 359 360
  }

  /**
   * Returns a 'time ago' string of last timestamp generation.
   *
361
   * @param string|null $variant
362 363
   *
   * @return string|array|false
gbyte.co's avatar
gbyte.co committed
364
   *  Formatted timestamp of last sitemap generation, otherwise FALSE.
365
   */
366
/*  public function getGeneratedAgo() {
367 368 369 370 371 372
    $chunks = $this->fetchSitemapVariantInfo();
    return isset($chunks[DefaultSitemapGenerator::FIRST_CHUNK_DELTA]->sitemap_created)
      ? $this->dateFormatter
        ->formatInterval($this->time->getRequestTime() - $chunks[DefaultSitemapGenerator::FIRST_CHUNK_DELTA]
            ->sitemap_created)
      : FALSE;
373
  }*/
374

375 376
  /**
   * Enables sitemap support for an entity type. Enabled entity types show
377 378
   * sitemap settings on their bundle setting forms. If an enabled entity type
   * features bundles (e.g. 'node'), it needs to be set up with
379 380 381
   * setBundleSettings() as well.
   *
   * @param string $entity_type_id
gbyte.co's avatar
gbyte.co committed
382
   *  Entity type id like 'node'.
383
   *
gbyte.co's avatar
gbyte.co committed
384
   * @return $this
385 386
   */
  public function enableEntityType($entity_type_id) {
387 388 389 390
    $enabled_entity_types = $this->getSetting('enabled_entity_types');
    if (!in_array($entity_type_id, $enabled_entity_types)) {
      $enabled_entity_types[] = $entity_type_id;
      $this->saveSetting('enabled_entity_types', $enabled_entity_types);
391
    }
392

gbyte.co's avatar
gbyte.co committed
393
    return $this;
394 395 396 397 398 399 400 401 402
  }

  /**
   * Disables sitemap support for an entity type. Disabling support for an
   * entity type deletes its sitemap settings permanently and removes sitemap
   * settings from entity forms.
   *
   * @param string $entity_type_id
   *
gbyte.co's avatar
gbyte.co committed
403
   * @return $this
404 405
   */
  public function disableEntityType($entity_type_id) {
406 407 408

    // Updating settings.
    $enabled_entity_types = $this->getSetting('enabled_entity_types');
409
    if (FALSE !== ($key = array_search($entity_type_id, $enabled_entity_types))) {
410
      unset ($enabled_entity_types[$key]);
411
      $this->saveSetting('enabled_entity_types', array_values($enabled_entity_types));
412 413 414
    }

    // Deleting inclusion settings.
415
    $config_names = $this->configFactory->listAll('simple_sitemap.bundle_settings.');
gbyte.co's avatar
gbyte.co committed
416
    foreach ($config_names as $config_name) {
417 418 419 420
      $config_name_parts = explode('.', $config_name);
      if ($config_name_parts[3] === $entity_type_id) {
        $this->configFactory->getEditable($config_name)->delete();
      }
421
    }
422 423

    // Deleting entity overrides.
424 425
    $this->setVariants(TRUE)->removeEntityInstanceSettings($entity_type_id);

gbyte.co's avatar
gbyte.co committed
426
    return $this;
427 428 429
  }

  /**
430 431
   * Sets settings for bundle or non-bundle entity types. This is done for the
   * currently set variant.
432 433
   * Please note, this method takes only the first set
   * variant into account. See todo.
434
   *
435 436
   * @param $entity_type_id
   * @param null $bundle_name
437
   * @param array $settings
gbyte.co's avatar
gbyte.co committed
438 439
   *
   * @return $this
440
   *
441 442 443
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   *
444
   * @todo multiple variants
445
   */
446
  public function setBundleSettings($entity_type_id, $bundle_name = NULL, $settings = ['index' => TRUE]) {
447 448 449 450
    if (empty($variants = $this->getVariants(FALSE))) {
      return $this;
    }

451
    $bundle_name = NULL !== $bundle_name ? $bundle_name : $entity_type_id;
452

gbyte.co's avatar
gbyte.co committed
453 454 455
    if (!empty($old_settings = $this->getBundleSettings($entity_type_id, $bundle_name))) {
      $settings = array_merge($old_settings, $settings);
    }
456
    self::supplementDefaultSettings('entity', $settings);
457

458
    if ($settings != $old_settings) {
459

460 461
      // Save new bundle settings to configuration.
      $bundle_settings = $this->configFactory
462
        ->getEditable("simple_sitemap.bundle_settings.$variants[0].$entity_type_id.$bundle_name");
463 464 465 466
      foreach ($settings as $setting_key => $setting) {
        $bundle_settings->set($setting_key, $setting);
      }
      $bundle_settings->save();
467

468
      // Delete entity overrides which are identical to new bundle settings.
469
      $entity_ids = $this->entityHelper->getEntityInstanceIds($entity_type_id, $bundle_name);
470 471 472 473 474 475 476
      $query = $this->db->select('simple_sitemap_entity_overrides', 'o')
        ->fields('o', ['id', 'inclusion_settings'])
        ->condition('o.entity_type', $entity_type_id)
        ->condition('o.type', $variants[0]);
      if (!empty($entity_ids)) {
        $query->condition('o.entity_id', $entity_ids, 'IN');
      }
477

478 479 480 481 482 483 484 485
      $delete_instances = [];
      foreach ($query->execute()->fetchAll() as $result) {
        $delete = TRUE;
        $instance_settings = unserialize($result->inclusion_settings);
        foreach ($instance_settings as $setting_key => $instance_setting) {
          if ($instance_setting != $settings[$setting_key]) {
            $delete = FALSE;
            break;
486 487
          }
        }
488 489
        if ($delete) {
          $delete_instances[] = $result->id;
490
        }
491
      }
492 493 494 495
      if (!empty($delete_instances)) {
        $this->db->delete('simple_sitemap_entity_overrides')
          ->condition('id', $delete_instances, 'IN')
          ->execute();
496
      }
497
    }
498

gbyte.co's avatar
gbyte.co committed
499
    return $this;
500 501
  }

502
  /**
503 504
   * Gets settings for bundle or non-bundle entity types. This is done for the
   * currently set variants.
505
   *
506
   * @param string|null $entity_type_id
507 508
   *  Limit the result set to a specific entity type.
   *
509
   * @param string|null $bundle_name
510
   *  Limit the result set to a specific bundle name.
511
   *
gbyte.co's avatar
gbyte.co committed
512
   * @param bool $supplement_defaults
513
   *  Supplements the result set with default bundle settings.
gbyte.co's avatar
gbyte.co committed
514 515 516 517 518
   *
   * @param bool $multiple_variants
   *  If true, returns an array of results keyed by variant name, otherwise it
   *  returns the result set for the first variant only.
   *
519
   * @return array|false
520 521
   *  Array of settings or array of settings keyed by variant name. False if
   *  entity type does not exist.
522
   */
523
  public function getBundleSettings($entity_type_id = NULL, $bundle_name = NULL, $supplement_defaults = TRUE, $multiple_variants = FALSE) {
524

525
    $bundle_name = NULL !== $bundle_name ? $bundle_name : $entity_type_id;
526 527 528 529 530 531 532 533 534 535
    $all_bundle_settings = [];

    foreach ($variants = $this->getVariants(FALSE) as $variant) {
      if (NULL !== $entity_type_id) {
        $bundle_settings = $this->configFactory
          ->get("simple_sitemap.bundle_settings.$variant.$entity_type_id.$bundle_name")
          ->get();

        // If not found and entity type is enabled, return default bundle settings.
        if (empty($bundle_settings) && $supplement_defaults) {
536
          if (isset($this->entityTypeBundleInfo->getBundleInfo($entity_type_id)[$bundle_name])) {
537 538 539 540 541
            self::supplementDefaultSettings('entity', $bundle_settings);
          }
          else {
            $bundle_settings = NULL;
          }
542
        }
543 544 545 546 547 548 549 550 551 552 553 554
      }
      else {
        $config_names = $this->configFactory->listAll("simple_sitemap.bundle_settings.$variant.");
        $bundle_settings = [];
        foreach ($config_names as $config_name) {
          $config_name_parts = explode('.', $config_name);
          $bundle_settings[$config_name_parts[3]][$config_name_parts[4]] = $this->configFactory->get($config_name)->get();
        }

        // Supplement default bundle settings for all bundles not found in simple_sitemap.bundle_settings.*.* configuration.
        if ($supplement_defaults) {
          foreach ($this->entityHelper->getSupportedEntityTypes() as $type_id => $type_definition) {
555 556 557
            foreach($this->entityTypeBundleInfo->getBundleInfo($type_id) as $bundle => $bundle_definition) {
              if (!isset($bundle_settings[$type_id][$bundle])) {
                self::supplementDefaultSettings('entity', $bundle_settings[$type_id][$bundle]);
558 559 560 561 562
              }
            }
          }
        }
      }
563

564
      if ($multiple_variants) {
565
        $all_bundle_settings[$variant] = $bundle_settings;
566
      }
567 568 569
      else {
        return $bundle_settings;
      }
570
    }
571 572 573 574 575

    return $all_bundle_settings;
  }

  /**
576 577 578
   * Removes settings for bundle or a non-bundle entity types. This is done for
   * the currently set variants.
   *
579
   * @param string|null $entity_type_id
580 581
   *  Limit the removal to a specific entity type.
   *
582
   * @param string|null $bundle_name
583 584
   *  Limit the removal to a specific bundle name.
   *
585
   * @return $this
586 587 588
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
589 590 591 592 593 594 595 596 597 598 599 600
   */
  public function removeBundleSettings($entity_type_id = NULL, $bundle_name = NULL) {
    if (empty($variants = $this->getVariants(FALSE))) {
      return $this;
    }

    if (NULL !== $entity_type_id) {
      $bundle_name = NULL !== $bundle_name ? $bundle_name : $entity_type_id;

      foreach ($variants as $variant) {
        $this->configFactory
          ->getEditable("simple_sitemap.bundle_settings.$variant.$entity_type_id.$bundle_name")->delete();
601 602
      }

603 604 605 606 607
      $this->removeEntityInstanceSettings($entity_type_id, (
        empty($ids)
          ? NULL
          : $this->entityHelper->getEntityInstanceIds($entity_type_id, $bundle_name)
      ));
608 609 610 611 612 613
    }
    else {
      foreach ($variants as $variant) {
        $config_names = $this->configFactory->listAll("simple_sitemap.bundle_settings.$variant.");
        foreach ($config_names as $config_name) {
          $this->configFactory->getEditable($config_name)->delete();
614
        }
615
        $this->removeEntityInstanceSettings();
616 617
      }
    }
618 619

    return $this;
620 621 622
  }

  /**
623 624
   * Supplements all missing link setting with default values.
   *
625
   * @param string $type
626 627
   *  Can be 'entity' or 'custom'.
   *
gbyte.co's avatar
gbyte.co committed
628 629
   * @param array &$settings
   * @param array $overrides
630
   */
gbyte.co's avatar
gbyte.co committed
631
  public static function supplementDefaultSettings($type, &$settings, $overrides = []) {
632
    foreach (self::$allowedLinkSettings[$type] as $allowed_link_setting) {
633
      if (!isset($settings[$allowed_link_setting])
gbyte.co's avatar
gbyte.co committed
634
        && isset(self::$linkSettingDefaults[$allowed_link_setting])) {
635 636 637
        $settings[$allowed_link_setting] = isset($overrides[$allowed_link_setting])
          ? $overrides[$allowed_link_setting]
          : self::$linkSettingDefaults[$allowed_link_setting];
638 639
      }
    }
640 641
  }

642
  /**
643 644
   * Overrides sitemap settings for a single entity for the currently set
   * variants.
645
   *
gbyte.co's avatar
gbyte.co committed
646
   * @param string $entity_type_id
647
   * @param string $id
gbyte.co's avatar
gbyte.co committed
648
   * @param array $settings
gbyte.co's avatar
gbyte.co committed
649
   *
650
   * @return $this
651 652 653
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
654
   */
655
  public function setEntityInstanceSettings($entity_type_id, $id, $settings) {
656 657 658 659
    if (empty($variants = $this->getVariants(FALSE))) {
      return $this;
    }

660
    $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($id);
661 662 663

    $all_bundle_settings = $this->getBundleSettings(
      $entity_type_id, $this->entityHelper->getEntityInstanceBundleName($entity), TRUE, TRUE
664
    );
665 666 667 668 669 670 671 672 673 674 675

    foreach ($all_bundle_settings as $variant => $bundle_settings) {
      if (!empty($bundle_settings)) {

        // Check if overrides are different from bundle setting before saving.
        $override = FALSE;
        foreach ($settings as $key => $setting) {
          if (!isset($bundle_settings[$key]) || $setting != $bundle_settings[$key]) {
            $override = TRUE;
            break;
          }
676
        }
677

678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695
        // Save overrides for this entity if something is different.
        if ($override) {
          $this->db->merge('simple_sitemap_entity_overrides')
            ->keys([
              'type' => $variant,
              'entity_type' => $entity_type_id,
              'entity_id' => $id])
            ->fields([
              'type' => $variant,
              'entity_type' => $entity_type_id,
              'entity_id' => $id,
              'inclusion_settings' => serialize(array_merge($bundle_settings, $settings))])
            ->execute();
        }
        // Else unset override.
        else {
          $this->removeEntityInstanceSettings($entity_type_id, $id);
        }
696
      }
697
    }
698

gbyte.co's avatar
gbyte.co committed
699
    return $this;
700 701
  }

702
  /**
703 704 705
   * Gets sitemap settings for an entity instance which overrides bundle
   * settings, or gets bundle settings, if they are not overridden. This is
   * done for the currently set variant.
706 707
   * Please note, this method takes only the first set
   * variant into account. See todo.
708
   *
gbyte.co's avatar
gbyte.co committed
709
   * @param string $entity_type_id
710
   * @param string $id
gbyte.co's avatar
gbyte.co committed
711
   *
712
   * @return array|false
713
   *  Array of entity instance settings or the settings of its bundle. False if
gbyte.co's avatar
gbyte.co committed
714
   *  entity type or variant does not exist.
715 716 717
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
718
   *
719
   * @todo multiple variants
720
   * @todo: May want to use Simplesitemap::supplementDefaultSettings('entity', $settings) inside here instead of calling it everywhere this method is called.
721
   */
722
  public function getEntityInstanceSettings($entity_type_id, $id) {
723 724 725 726
    if (empty($variants = $this->getVariants(FALSE))) {
      return FALSE;
    }

727 728
    $results = $this->db->select('simple_sitemap_entity_overrides', 'o')
      ->fields('o', ['inclusion_settings'])
729
      ->condition('o.type', $variants[0])
730 731 732 733 734 735
      ->condition('o.entity_type', $entity_type_id)
      ->condition('o.entity_id', $id)
      ->execute()
      ->fetchField();

    if (!empty($results)) {
736
      return unserialize($results);
737 738
    }
    else {
739 740
      return $this->getBundleSettings(
        $entity_type_id,
741 742 743
        $this->entityHelper->getEntityInstanceBundleName(
          $this->entityTypeManager->getStorage($entity_type_id)->load($id)
        )
744
      );
745 746 747
    }
  }

748
  /**
749 750 751 752 753
   * Removes sitemap settings for entities that override bundle settings. This
   * is done for the currently set variants.
   *
   * @param string|null $entity_type_id
   *  Limits the removal to a certain entity type.
754 755
   *
   * @param string|null $entity_ids
756
   *  Limits the removal to entities with certain IDs.
757 758 759
   *
   * @return $this
   */
760
  public function removeEntityInstanceSettings($entity_type_id = NULL, $entity_ids = NULL) {
761
    if (empty($variants = $this->getVariants(FALSE))) {
762 763 764
      return $this;
    }

765
    $query = $this->db->delete('simple_sitemap_entity_overrides')
766 767 768 769 770 771 772 773
      ->condition('type', $variants, 'IN');

    if (NULL !== $entity_type_id) {
      $query->condition('entity_type', $entity_type_id);

      if (NULL !== $entity_ids) {
        $query->condition('entity_id', (array) $entity_ids, 'IN');
      }
774
    }
775

776
    $query->execute();
777

778 779 780
    return $this;
  }

781 782
  /**
   * Checks if an entity bundle (or a non-bundle entity type) is set to be
783
   * indexed for any of the currently set variants.
784
   *
785 786
   * @param string $entity_type_id
   * @param string|null $bundle_name
gbyte.co's avatar
gbyte.co committed
787
   *
788 789
   * @return bool
   */
790
  public function bundleIsIndexed($entity_type_id, $bundle_name = NULL) {
791 792 793 794 795
    foreach ($this->getBundleSettings($entity_type_id, $bundle_name, FALSE, TRUE) as $settings) {
      if (!empty($settings['index'])) {
        return TRUE;
      }
    }
796

797
    return FALSE;
798 799
  }

800 801 802
  /**
   * Checks if an entity type is enabled in the sitemap settings.
   *
803
   * @param string $entity_type_id
gbyte.co's avatar
gbyte.co committed
804
   *
805 806
   * @return bool
   */
807
  public function entityTypeIsEnabled($entity_type_id) {
808
    return in_array($entity_type_id, $this->getSetting('enabled_entity_types', []));
809 810
  }

811
  /**
812 813
   * Stores a custom path along with its settings to configuration for the
   * currently set variants.
814
   *
gbyte.co's avatar
gbyte.co committed
815
   * @param string $path
816
   *
gbyte.co's avatar
gbyte.co committed
817
   * @param array $settings
818
   *  Settings that are not provided are supplemented by defaults.
gbyte.co's avatar
gbyte.co committed
819
   *
gbyte.co's avatar
gbyte.co committed
820
   * @return $this
821 822
   *
   * @todo Validate $settings and throw exceptions
823
   */
824
  public function addCustomLink($path, $settings = []) {
825 826 827 828
    if (empty($variants = $this->getVariants(FALSE))) {
      return $this;
    }

829
    if (!(bool) $this->pathValidator->getUrlIfValidWithoutAccessCheck($path)) {
gbyte.co's avatar
gbyte.co committed
830 831 832
      // todo: log error.
      return $this;
    }
gbyte.co's avatar
gbyte.co committed
833
    if ($path[0] !== '/') {
gbyte.co's avatar
gbyte.co committed
834 835 836
      // todo: log error.
      return $this;
    }
gbyte.co's avatar
gbyte.co committed
837

838 839 840 841 842 843 844 845 846 847 848 849 850
    $variant_links = $this->getCustomLinks(NULL, FALSE, TRUE);
    foreach ($variants as $variant) {
      $links = [];
      $link_key = 0;
      if (isset($variant_links[$variant])) {
        $links = $variant_links[$variant];
        $link_key = count($links);
        foreach ($links as $key => $link) {
          if ($link['path'] === $path) {
            $link_key = $key;
            break;
          }
        }
851
      }
852 853 854 855

      $links[$link_key] = ['path' => $path] + $settings;
      $this->configFactory->getEditable("simple_sitemap.custom_links.$variant")
        ->set('links', $links)->save();
856
    }
857

gbyte.co's avatar
gbyte.co committed
858
    return $this;
859 860
  }

861
  /**
862 863 864 865
   * Gets custom link settings for the currently set variants.
   *
   * @param string|null $path
   *  Limits the result set by an internal path.
866
   *
867
   * @param bool $supplement_defaults
868 869 870 871 872 873 874
   *  Supplements the result set with default custom link settings.
   *
   * @param bool $multiple_variants
   *  If true, returns an array of results keyed by variant name, otherwise it
   *  returns the result set for the first variant only.
   *
   * @return array|mixed|null
875
   */
876 877 878 879 880 881
  public function getCustomLinks($path = NULL, $supplement_defaults = TRUE, $multiple_variants = FALSE) {
    $all_custom_links = [];
    foreach ($variants = $this->getVariants(FALSE) as $variant) {
      $custom_links = $this->configFactory
        ->get("simple_sitemap.custom_links.$variant")
        ->get('links');
882

883 884 885 886 887 888 889 890
      $custom_links = !empty($custom_links) ? $custom_links : [];

      if (!empty($custom_links) && $path !== NULL) {
        foreach ($custom_links as $key => $link) {
          if ($link['path'] !== $path) {
            unset($custom_links[$key]);
          }
        }
891 892
      }

893 894 895 896 897 898
      if (!empty($custom_links) && $supplement_defaults) {
        foreach ($custom_links as $i => $link_settings) {
          self::supplementDefaultSettings('custom', $link_settings);
          $custom_links[$i] = $link_settings;
        }
      }
899

900 901 902 903 904 905 906 907 908 909 910 911
      $custom_links = $path !== NULL && !empty($custom_links)
        ? array_values($custom_links)[0]
        : array_values($custom_links);


      if (!empty($custom_links)) {
        if ($multiple_variants) {
          $all_custom_links[$variant] = $custom_links;
        }
        else {
          return $custom_links;
        }
912 913
      }
    }
914 915

    return $all_custom_links;
916 917
  }

918
  /**
919 920 921 922
   * Removes custom links from currently set variants.
   *
   * @param array|null $paths
   *  Limits the removal to certain paths.
gbyte.co's avatar
gbyte.co committed
923
   *
924 925
   * @return $this
   */
926
  public function removeCustomLinks($paths = NULL) {
927 928 929 930
    if (empty($variants = $this->getVariants(FALSE))) {
      return $this;
    }

931
    if (NULL === $paths) {
932 933 934 935
      foreach ($variants as $variant) {
        $this->configFactory
          ->getEditable("simple_sitemap.custom_links.$variant")->delete();
      }
936 937
    }
    else {
938 939 940 941 942 943 944 945 946 947 948
      $variant_links = $this->getCustomLinks(NULL, FALSE, TRUE);
      foreach ($variant_links as $variant => $links) {
        $custom_links = $links;
        $save = FALSE;
        foreach ((array) $paths  as $path) {
          foreach ($custom_links as $key => $link) {
            if ($link['path'] === $path) {
              unset($custom_links[$key]);
              $save = TRUE;
              break 2;
            }
949 950
          }
        }
951 952 953 954
        if ($save) {
          $this->configFactory->getEditable("simple_sitemap.custom_links.$variant")
            ->set('links', array_values($custom_links))->save();
        }
955 956 957
      }
    }

gbyte.co's avatar
gbyte.co committed
958
    return $this;
959
  }
960
}