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

Pawel G's avatar
Pawel G committed
3
namespace Drupal\simple_sitemap;
4

Pawel G's avatar
Pawel G committed
5
use Drupal\Core\Database\Connection;
6
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
Pawel G's avatar
Pawel G committed
7
use Drupal\Core\Entity\EntityTypeManagerInterface;
8
use Drupal\Core\Extension\ModuleHandler;
9
use Drupal\simple_sitemap\Queue\QueueWorker;
Pawel G's avatar
Pawel G committed
10
use Drupal\Core\Path\PathValidator;
Pawel G's avatar
Pawel G committed
11 12
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Datetime\DateFormatter;
13
use Drupal\Component\Datetime\Time;
14 15
use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\DefaultSitemapGenerator;
use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorBase;
16

17
/**
Pawel G's avatar
Pawel G committed
18
 * Class Simplesitemap
Pawel G's avatar
Pawel G committed
19
 * @package Drupal\simple_sitemap
20 21
 */
class Simplesitemap {
Pawel G's avatar
Pawel G committed
22 23 24
  /**
   * @var \Drupal\simple_sitemap\EntityHelper
   */
25
  protected $entityHelper;
Pawel G's avatar
Pawel G committed
26

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

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

Pawel G's avatar
Pawel G committed
37 38 39
  /**
   * @var \Drupal\Core\Config\ConfigFactory
   */
40
  protected $configFactory;
Pawel G's avatar
Pawel G committed
41 42 43 44

  /**
   * @var \Drupal\Core\Database\Connection
   */
45
  protected $db;
Pawel G's avatar
Pawel G committed
46 47 48 49

  /**
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
50
  protected $entityTypeManager;
Pawel G's avatar
Pawel G committed
51

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

Pawel G's avatar
Pawel G committed
57 58 59
  /**
   * @var \Drupal\Core\Path\PathValidator
   */
60
  protected $pathValidator;
Pawel G's avatar
Pawel G committed
61

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

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

72
  /**
73
   * @var \Drupal\Core\Extension\ModuleHandler
74
   */
75
  protected $moduleHandler;
76 77

  /**
78
   * @var \Drupal\simple_sitemap\Queue\QueueWorker
79
   */
80
  protected $queueWorker;
81

82 83 84 85 86
  /**
   * @var array
   */
  protected $variants;

Pawel G's avatar
Pawel G committed
87 88 89
  /**
   * @var array
   */
Pawel G's avatar
Pawel G committed
90
  protected static $allowedLinkSettings = [
91
    'entity' => ['index', 'priority', 'changefreq', 'include_images'],
92 93 94
    'custom' => ['priority', 'changefreq'],
  ];

Pawel G's avatar
Pawel G committed
95 96 97 98
  /**
   * @var array
   */
  protected static $linkSettingDefaults = [
99
    'index' => FALSE,
100
    'priority' => '0.5',
101
    'changefreq' => '',
102
    'include_images' => FALSE,
103
  ];
104

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

148
  /**
149 150
   * Returns a specific sitemap setting or a default value if setting does not
   * exist.
Pawel G's avatar
Pawel G committed
151
   *
152
   * @param string $name
Pawel G's avatar
Pawel G committed
153
   *  Name of the setting, like 'max_links'.
154 155
   *
   * @param mixed $default
Pawel G's avatar
Pawel G committed
156
   *  Value to be returned if the setting does not exist in the configuration.
157 158
   *
   * @return mixed
Pawel G's avatar
Pawel G committed
159
   *  The current setting from configuration or a default value.
Pawel G's avatar
Pawel G committed
160
   */
161
  public function getSetting($name, $default = FALSE) {
162
    return $this->settings->getSetting($name, $default);
163 164 165 166 167 168
  }

  /**
   * Stores a specific sitemap setting in configuration.
   *
   * @param string $name
Pawel G's avatar
Pawel G committed
169
   *  Setting name, like 'max_links'.
170
   * @param mixed $setting
Pawel G's avatar
Pawel G committed
171
   *  The setting to be saved.
172 173 174 175
   *
   * @return $this
   */
  public function saveSetting($name, $setting) {
176
    $this->settings->saveSetting($name, $setting);
177 178 179
    return $this;
  }

Pawel G's avatar
Pawel G committed
180
  /**
181
   * @return \Drupal\simple_sitemap\Queue\QueueWorker
Pawel G's avatar
Pawel G committed
182
   */
183 184
  public function getQueueWorker() {
    return $this->queueWorker;
Pawel G's avatar
Pawel G committed
185 186 187
  }

  /**
188
   * @return \Drupal\simple_sitemap\SimplesitemapManager
Pawel G's avatar
Pawel G committed
189
   */
190 191
  public function getSitemapManager() {
    return $this->manager;
192 193
  }

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

    return $this;
  }

  /**
219
   * @param bool $default_get_all
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 231 232 233
  /**
   * Returns the whole sitemap, a requested sitemap chunk,
   * or the sitemap index file.
   *
234
   * @param int $delta
235 236
   *
   * @return string|false
Pawel G's avatar
Pawel G committed
237
   *  If no sitemap delta is provided, either a sitemap index is returned, or the
238
   *  whole sitemap variant, if the amount of links does not exceed the max
Pawel G's avatar
Pawel G committed
239
   *  links setting. If a sitemap delta is provided, a sitemap chunk is returned.
Pawel G's avatar
Pawel G committed
240
   *  Returns false if the sitemap 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])) {
Pawel G's avatar
Pawel G committed
248
        // Return sitemap index if one exists.
249 250
        return $this->fetchSitemapChunk($chunk_info[SitemapGeneratorBase::INDEX_DELTA]->id)
          ->sitemap_string;
251 252
      }
      else {
Pawel G's avatar
Pawel G 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 271
   *  An array containing all published sitemap chunk IDs, deltas and creation
   * timestamps keyed by their variant ID.
272
   */
273 274
  protected function fetchSitemapVariantInfo() {
    $result = $this->db->select('simple_sitemap', 's')
275
      ->fields('s', ['id', 'delta', 'sitemap_created', 'type'])
276 277 278
      ->condition('s.status', 1)
      ->condition('s.type', $this->getVariants(), 'IN')
      ->execute();
279

280 281 282
    return count($this->getVariants()) > 1
      ? $result->fetchAllAssoc('type')
      : $result->fetchAllAssoc('delta');
283 284
  }

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

299

300 301
  /**
   * @return $this
302
   * @throws \Drupal\Component\Plugin\Exception\PluginException
303
   *
304
   * @todo document
305
   */
306 307
  public function removeSitemap() {
    $variants = $this->getVariants();
308
    $saved_variants = $this->manager->getSitemapVariants();
Pawel G's avatar
Pawel G committed
309 310 311 312 313
    $this->moduleHandler->alter('simple_sitemap_variants', $saved_variants);
    $remove_variants = NULL !== $variants
      ? array_intersect_key($saved_variants, array_flip((array) $variants))
      : $saved_variants;

314
    if (!empty($remove_variants)) {
315
      $type_definitions = $this->manager->getSitemapTypes();
Pawel G's avatar
Pawel G committed
316
      $this->moduleHandler->alter('simple_sitemap_types', $type_definitions);
317
      foreach ($remove_variants as $variant_name => $variant_definition) {
318
        $this->manager->getSitemapGenerator($type_definitions[$variant_definition['type']]['sitemapGenerator'])
319
          ->setSitemapVariant($variant_name)
320
          ->remove();
321 322
      }
    }
Pawel G's avatar
Pawel G committed
323

324 325 326
    return $this;
  }

327

328 329
  /**
   * @param string $from
Pawel G's avatar
Pawel G committed
330 331
   * @return $this
   * @throws \Drupal\Component\Plugin\Exception\PluginException
332
   */
333
  public function generateSitemap($from = 'form') {
334 335 336
    switch($from) {
      case 'form':
      case 'drush':
Pawel G's avatar
Pawel G committed
337
        $this->queueWorker->batchGenerateSitemap($from);
338 339 340 341
        break;

      case 'cron':
      case 'backend':
Pawel G's avatar
Pawel G committed
342
        $this->queueWorker->generateSitemap($from);
343
        break;
344
    }
Pawel G's avatar
Pawel G committed
345 346

    return $this;
347
  }
348

349 350
  public function rebuildQueue() {
    $this->queueWorker->rebuildQueue($this->getVariants());
Pawel G's avatar
Pawel G committed
351 352

    return $this;
353 354 355 356 357
  }

  /**
   * Returns a 'time ago' string of last timestamp generation.
   *
358
   * @param string|null $variant
359 360
   *
   * @return string|array|false
Pawel G's avatar
Pawel G committed
361
   *  Formatted timestamp of last sitemap generation, otherwise FALSE.
362 363
   *
   * @todo: variants
364
   */
365 366 367 368 369 370 371
  public function getGeneratedAgo() {
    $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;
372 373
  }

374 375
  /**
   * Enables sitemap support for an entity type. Enabled entity types show
376 377
   * sitemap settings on their bundle setting forms. If an enabled entity type
   * features bundles (e.g. 'node'), it needs to be set up with
378 379 380
   * setBundleSettings() as well.
   *
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
381
   *  Entity type id like 'node'.
382
   *
Pawel G's avatar
Pawel G committed
383
   * @return $this
384 385
   */
  public function enableEntityType($entity_type_id) {
386 387 388 389
    $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);
390
    }
Pawel G's avatar
Pawel G committed
391
    return $this;
392 393 394 395 396 397 398 399
  }

  /**
   * 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
400
   *  Entity type id like 'node'.
401
   *
Pawel G's avatar
Pawel G committed
402
   * @return $this
403 404
   */
  public function disableEntityType($entity_type_id) {
405 406 407

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

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

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

Pawel G's avatar
Pawel G committed
425
    return $this;
426 427 428
  }

  /**
Pawel G's avatar
Pawel G committed
429
   * Sets sitemap settings for a non-bundle entity type (e.g. user) or a bundle
430 431 432
   * of an entity type (e.g. page).
   *
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
433
   *  Entity type id like 'node' the bundle belongs to.
434
   * @param string $bundle_name
Pawel G's avatar
Pawel G committed
435
   *  Name of the bundle. NULL if entity type has no bundles.
436
   * @param array $settings
Pawel G's avatar
Pawel G committed
437 438
   *  An array of sitemap settings for this bundle/entity type.
   *  Example: ['index' => TRUE, 'priority' => 0.5, 'changefreq' => 'never', 'include_images' => FALSE].
Pawel G's avatar
Pawel G committed
439 440
   *
   * @return $this
441 442
   *
   * @todo: enableEntityType automatically
443
   * @todo multiple variants
444
   */
445
  public function setBundleSettings($entity_type_id, $bundle_name = NULL, $settings = ['index' => TRUE]) {
446 447 448 449
    if (empty($variants = $this->getVariants(FALSE))) {
      return $this;
    }

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

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

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

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

467
      // Delete entity overrides which are identical to new bundle settings.
468 469 470 471 472 473 474 475
      $entity_ids = $this->getEntityInstanceIds($entity_type_id, $bundle_name);
      $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');
      }
476

477 478 479 480 481 482 483 484
      $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;
485 486
          }
        }
487 488
        if ($delete) {
          $delete_instances[] = $result->id;
489
        }
490
      }
491 492 493 494
      if (!empty($delete_instances)) {
        $this->db->delete('simple_sitemap_entity_overrides')
          ->condition('id', $delete_instances, 'IN')
          ->execute();
495
      }
496
    }
497

Pawel G's avatar
Pawel G committed
498
    return $this;
499 500
  }

501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
  /**
   * @todo Possibly move to EntityHelper
   */
  protected function getEntityInstanceIds($entity_type_id, $bundle_name = NULL) {
    $sitemap_entity_types = $this->entityHelper->getSupportedEntityTypes();
    if (!isset($sitemap_entity_types[$entity_type_id])) {
      return [];
    }

    $entity_query = $this->entityTypeManager->getStorage($entity_type_id)->getQuery();
    if (!$this->entityHelper->entityTypeIsAtomic($entity_type_id) && NULL !== $bundle_name) {
      $keys = $sitemap_entity_types[$entity_type_id]->getKeys();

      // Menu fix.
      $keys['bundle'] = $entity_type_id === 'menu_link_content' ? 'menu_name' : $keys['bundle'];

      $entity_query->condition($keys['bundle'], $bundle_name);
    }

    return $entity_query->execute();
  }

523
  /**
524 525
   * Gets sitemap settings for an entity bundle, a non-bundle entity type or for
   * all entity types and their bundles.
526
   *
527 528 529
   * @param string|null $entity_type_id
   *  If set to null, sitemap settings for all entity types and their bundles
   *  are fetched.
530 531 532
   * @param string|null $bundle_name
   *
   * @return array|false
Pawel G's avatar
Pawel G committed
533 534
   *  Array of sitemap settings for an entity bundle, a non-bundle entity type
   *  or for all entity types and their bundles.
535
   *  False if entity type does not exist.
536
   */
537
  public function getBundleSettings($entity_type_id = NULL, $bundle_name = NULL, $supplement_defaults = TRUE, $multipleVariants = FALSE) {
538

539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
    $all_bundle_settings = [];

    foreach ($variants = $this->getVariants(FALSE) as $variant) {
      if (NULL !== $entity_type_id) {
        $bundle_name = NULL !== $bundle_name ? $bundle_name : $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) {
          if ($this->entityTypeIsEnabled($entity_type_id)
            && isset($this->entityTypeBundleInfo->getBundleInfo($entity_type_id)[$bundle_name])) {
            self::supplementDefaultSettings('entity', $bundle_settings);
          }
          else {
            $bundle_settings = NULL;
          }
558
        }
559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
      }
      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) {
            if ($this->entityTypeIsEnabled($type_id)) {
              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]);
                }
              }
            }
          }
        }
      }
      if ($multipleVariants) {
        if (!empty($bundle_settings)) {
          $all_bundle_settings[$variant] = $bundle_settings;
584 585
        }
      }
586 587 588
      else {
        return $bundle_settings;
      }
589
    }
590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609

    return $all_bundle_settings;
  }

  /**
   * @param string|null $entity_type_id
   * @param string|null $bundle_name
   * @return $this
   */
  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();
610 611
      }

612 613 614 615 616 617 618 619
      $ids = $this->getEntityInstanceIds($entity_type_id, $bundle_name);
      $this->removeEntityInstanceSettings($entity_type_id, (empty($ids) ? NULL : $ids));
    }
    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();
620
        }
621
        $this->removeEntityInstanceSettings();
622 623
      }
    }
624 625

    return $this;
626 627 628
  }

  /**
629 630
   * Supplements all missing link setting with default values.
   *
631
   * @param string $type
Pawel G's avatar
Pawel G committed
632 633 634
   *  'entity'|'custom'
   * @param array &$settings
   * @param array $overrides
635
   */
Pawel G's avatar
Pawel G committed
636
  public static function supplementDefaultSettings($type, &$settings, $overrides = []) {
637
    foreach (self::$allowedLinkSettings[$type] as $allowed_link_setting) {
638
      if (!isset($settings[$allowed_link_setting])
Pawel G's avatar
Pawel G committed
639
        && isset(self::$linkSettingDefaults[$allowed_link_setting])) {
640 641 642
        $settings[$allowed_link_setting] = isset($overrides[$allowed_link_setting])
          ? $overrides[$allowed_link_setting]
          : self::$linkSettingDefaults[$allowed_link_setting];
643 644
      }
    }
645 646
  }

Pawel G's avatar
Pawel G committed
647 648 649
  /**
   * Overrides entity bundle/entity type sitemap settings for a single entity.
   *
Pawel G's avatar
Pawel G committed
650 651 652
   * @param string $entity_type_id
   * @param int $id
   * @param array $settings
Pawel G's avatar
Pawel G committed
653
   *
Pawel G's avatar
Pawel G committed
654 655
   * @return $this
   */
656
  public function setEntityInstanceSettings($entity_type_id, $id, $settings) {
657 658 659 660
    if (empty($variants = $this->getVariants(FALSE))) {
      return $this;
    }

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

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

    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;
          }
677
        }
678

679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
        // 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);
        }
697
      }
698
    }
699

Pawel G's avatar
Pawel G committed
700
    return $this;
701 702
  }

Pawel G's avatar
Pawel G committed
703
  /**
704
   * Gets sitemap settings for an entity instance which overrides the sitemap
705
   * settings of its bundle, or bundle settings, if they are not overridden.
Pawel G's avatar
Pawel G committed
706
   *
Pawel G's avatar
Pawel G committed
707
   * @param string $entity_type_id
708
   * @param int $id
Pawel G's avatar
Pawel G committed
709
   *
710
   * @return array|false
711
   *
712
   * @todo multiple variants
Pawel G's avatar
Pawel G committed
713
   */
714
  public function getEntityInstanceSettings($entity_type_id, $id) {
715 716 717 718
    if (empty($variants = $this->getVariants(FALSE))) {
      return FALSE;
    }

719 720
    $results = $this->db->select('simple_sitemap_entity_overrides', 'o')
      ->fields('o', ['inclusion_settings'])
721
      ->condition('o.type', $variants[0])
722 723 724 725 726 727
      ->condition('o.entity_type', $entity_type_id)
      ->condition('o.entity_id', $id)
      ->execute()
      ->fetchField();

    if (!empty($results)) {
728
      return unserialize($results);
729 730
    }
    else {
731 732 733 734 735 736
      $entity = $this->entityTypeManager->getStorage($entity_type_id)
        ->load($id);
      return $this->getBundleSettings(
        $entity_type_id,
        $this->entityHelper->getEntityInstanceBundleName($entity)
      );
737 738 739
    }
  }

740 741 742 743 744 745 746 747 748
  /**
   * Removes sitemap settings for an entity that overrides the sitemap settings
   * of its bundle.
   *
   * @param string $entity_type_id
   * @param string|null $entity_ids
   *
   * @return $this
   */
749 750 751 752 753
  public function removeEntityInstanceSettings($entity_type_id = NULL, $entity_ids = NULL) {
    if (empty($variants = $this->getVariants())) {
      return $this;
    }

754
    $query = $this->db->delete('simple_sitemap_entity_overrides')
755 756 757 758 759 760 761 762
      ->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');
      }
763
    }
764

765
    $query->execute();
766

767 768 769
    return $this;
  }

Pawel G's avatar
Pawel G committed
770 771 772 773
  /**
   * Checks if an entity bundle (or a non-bundle entity type) is set to be
   * indexed in the sitemap settings.
   *
774 775
   * @param string $entity_type_id
   * @param string|null $bundle_name
Pawel G's avatar
Pawel G committed
776
   *
Pawel G's avatar
Pawel G committed
777 778
   * @return bool
   */
779
  public function bundleIsIndexed($entity_type_id, $bundle_name = NULL) {
780 781 782 783
    $settings = $this->getBundleSettings($entity_type_id, $bundle_name);
    return !empty($settings['index']);
  }

Pawel G's avatar
Pawel G committed
784 785 786
  /**
   * Checks if an entity type is enabled in the sitemap settings.
   *
787
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
788
   *
Pawel G's avatar
Pawel G committed
789 790
   * @return bool
   */
791
  public function entityTypeIsEnabled($entity_type_id) {
792
    return in_array($entity_type_id, $this->getSetting('enabled_entity_types', []));
793 794
  }

Pawel G's avatar
Pawel G committed
795
  /**
796
   * Stores a custom path along with its sitemap settings to configuration.
Pawel G's avatar
Pawel G committed
797
   *
Pawel G's avatar
Pawel G committed
798 799
   * @param string $path
   * @param array $settings
Pawel G's avatar
Pawel G committed
800
   *
Pawel G's avatar
Pawel G committed
801
   * @return $this
802 803
   *
   * @todo Validate $settings and throw exceptions
804
   * @todo: variants
Pawel G's avatar
Pawel G committed
805
   */
806
  public function addCustomLink($path, $settings = []) {
807
    if (!(bool) $this->pathValidator->getUrlIfValidWithoutAccessCheck($path)) {
Pawel G's avatar
Pawel G committed
808 809 810
      // todo: log error.
      return $this;
    }
Pawel G's avatar
Pawel G committed
811
    if ($path[0] !== '/') {
Pawel G's avatar
Pawel G committed
812 813 814
      // todo: log error.
      return $this;
    }
Pawel G's avatar
Pawel G committed
815

816
    $custom_links = $this->getCustomLinks(FALSE);
Pawel G's avatar
Pawel G committed
817
    foreach ($custom_links as $key => $link) {
818
      if ($link['path'] === $path) {
819 820 821 822 823
        $link_key = $key;
        break;
      }
    }
    $link_key = isset($link_key) ? $link_key : count($custom_links);
824
    $custom_links[$link_key] = ['path' => $path] + $settings;
Pawel G's avatar
Pawel G committed
825
    $this->configFactory->getEditable('simple_sitemap.custom')
826
      ->set('links', $custom_links)->save();
827

Pawel G's avatar
Pawel G committed
828
    return $this;
829 830
  }

831 832 833
  /**
   * Returns an array of custom paths and their sitemap settings.
   *
834
   * @param bool $supplement_default_settings
835
   * @return array
836 837
   *
   * @todo: variants
838
   */
839
  public function getCustomLinks($supplement_default_settings = TRUE) {
840 841
    $custom_links = $this->configFactory
      ->get('simple_sitemap.custom')
842
      ->get('links');
843 844 845

    if ($supplement_default_settings) {
      foreach ($custom_links as $i => $link_settings) {
Pawel G's avatar
Pawel G committed
846 847
        self::supplementDefaultSettings('custom', $link_settings);
        $custom_links[$i] = $link_settings;
848 849 850
      }
    }

851
    return !empty($custom_links) ? $custom_links : [];
852 853
  }

Pawel G's avatar
Pawel G committed
854 855 856
  /**
   * Returns settings for a custom path added to the sitemap settings.
   *
Pawel G's avatar
Pawel G committed
857
   * @param string $path
Pawel G's avatar
Pawel G committed
858
   *
Pawel G's avatar
Pawel G committed
859
   * @return array|false
860 861
   *
   * @todo: variants
Pawel G's avatar
Pawel G committed
862
   */
863
  public function getCustomLink($path) {
864
    foreach ($this->getCustomLinks() as $key => $link) {
865
      if ($link['path'] === $path) {
866
        return $link;
867 868 869 870 871
      }
    }
    return FALSE;
  }

Pawel G's avatar
Pawel G committed
872
  /**
873
   * Removes all custom paths from the sitemap settings.
Pawel G's avatar
Pawel G committed
874
   *
Pawel G's avatar
Pawel G committed
875
   * @return $this
876 877
   *
   * @todo: variants
Pawel G's avatar
Pawel G committed
878
   */
879 880 881 882 883 884 885 886 887 888 889 890 891 892
  public function removeCustomLinks($paths = NULL) {
    if (NULL === $paths) {
      $custom_links = [];
      $save = TRUE;
    }
    else {
      $custom_links = $this->getCustomLinks(FALSE);
      foreach ((array) $paths as $path) {
        foreach ($custom_links as $key => $link) {
          if ($link['path'] === $path) {
            unset($custom_links[$key]);
            $save = TRUE;
          }
        }
893 894
      }
    }
895 896 897 898
    if (!empty($save)) {
      $this->configFactory->getEditable('simple_sitemap.custom')
        ->set('links', array_values($custom_links))->save();
    }
899

Pawel G's avatar
Pawel G committed
900
    return $this;
901
  }
902
}