Simplesitemap.php 24.3 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 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
  /**
   * @param array|string|true|null $variants
   *  array: Array of variants to be set.
   *  string: A particular variant will be set.
   *  null: Default variant will be set.
   *  true: All existing variants will be set.
   *
   * @return $this
   */
  public function setVariants($variants = NULL) {
    if (NULL === $variants) {
      $this->variants = [$this->getSetting('default_variant',
        SimplesitemapManager::DEFAULT_SITEMAP_VARIANT)];
    }
    elseif ($variants === TRUE) {
      $this->variants = array_keys(
        $this->manager->getSitemapVariants(NULL, FALSE));
    }
    else {
      $this->variants = is_array($variants) ? $variants : [$variants];
    }

    return $this;
  }

  /**
   * @return array
   */
  protected function getVariants() {
    if (NULL === $this->variants) {
      $this->setVariants();
    }

    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 ID provided, either a sitemap index is returned, or the
238 239
   *  whole sitemap variant, if the amount of links does not exceed the max
   *  links setting. If a sitemap ID 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':
337
        $this->queueWorker->batchGenerateSitemap($from, $this->getVariants());
338 339 340 341
        break;

      case 'cron':
      case 'backend':
342
        $this->queueWorker->generateSitemap($this->getVariants());
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.$entity_type_id.");
Pawel G's avatar
Pawel G committed
415
    foreach ($config_names as $config_name) {
416
      $this->configFactory->getEditable($config_name)->delete();
417
    }
418 419 420

    // Deleting entity overrides.
    $this->removeEntityInstanceSettings($entity_type_id);
Pawel G's avatar
Pawel G committed
421
    return $this;
422 423 424
  }

  /**
Pawel G's avatar
Pawel G committed
425
   * Sets sitemap settings for a non-bundle entity type (e.g. user) or a bundle
426 427 428
   * of an entity type (e.g. page).
   *
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
429
   *  Entity type id like 'node' the bundle belongs to.
430
   * @param string $bundle_name
Pawel G's avatar
Pawel G committed
431
   *  Name of the bundle. NULL if entity type has no bundles.
432
   * @param array $settings
Pawel G's avatar
Pawel G committed
433 434
   *  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
435 436
   *
   * @return $this
437 438
   *
   * @todo: enableEntityType automatically
439
   * @todo: variants
440
   */
441
  public function setBundleSettings($entity_type_id, $bundle_name = NULL, $settings = ['index' => TRUE]) {
442
    $bundle_name = NULL !== $bundle_name ? $bundle_name : $entity_type_id;
443

Pawel G's avatar
Pawel G committed
444 445 446
    if (!empty($old_settings = $this->getBundleSettings($entity_type_id, $bundle_name))) {
      $settings = array_merge($old_settings, $settings);
    }
447
    self::supplementDefaultSettings('entity', $settings);
448

449
    if ($settings != $old_settings) {
450

451 452 453 454 455 456 457
      // Save new bundle settings to configuration.
      $bundle_settings = $this->configFactory
        ->getEditable("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name");
      foreach ($settings as $setting_key => $setting) {
        $bundle_settings->set($setting_key, $setting);
      }
      $bundle_settings->save();
458

459 460 461 462 463
      // Delete entity overrides which are identical to new bundle settings.
      $sitemap_entity_types = $this->entityHelper->getSupportedEntityTypes();
      if (isset($sitemap_entity_types[$entity_type_id])) {
        $entity_type = $sitemap_entity_types[$entity_type_id];
        $keys = $entity_type->getKeys();
464

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

468 469 470 471 472 473 474 475 476 477 478 479
        $query = $this->entityTypeManager->getStorage($entity_type_id)->getQuery();
        if (!$this->entityHelper->entityTypeIsAtomic($entity_type_id)) {
          $query->condition($keys['bundle'], $bundle_name);
        }
        $entity_ids = $query->execute();

        $query = $this->db->select('simple_sitemap_entity_overrides', 'o')
          ->fields('o', ['id', 'inclusion_settings'])
          ->condition('o.entity_type', $entity_type_id);
        if (!empty($entity_ids)) {
          $query->condition('o.entity_id', $entity_ids, 'IN');
        }
480

481 482 483 484 485 486 487 488 489 490 491 492
        $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;
            }
          }
          if ($delete) {
            $delete_instances[] = $result->id;
493 494
          }
        }
495 496 497 498
        if (!empty($delete_instances)) {
          $this->db->delete('simple_sitemap_entity_overrides')
            ->condition('id', $delete_instances, 'IN')
            ->execute();
499
        }
500
      }
501 502
      else {
        //todo: log error
503
      }
504
    }
505

Pawel G's avatar
Pawel G committed
506
    return $this;
507 508
  }

509
  /**
510 511
   * Gets sitemap settings for an entity bundle, a non-bundle entity type or for
   * all entity types and their bundles.
512
   *
513 514 515
   * @param string|null $entity_type_id
   *  If set to null, sitemap settings for all entity types and their bundles
   *  are fetched.
516 517 518
   * @param string|null $bundle_name
   *
   * @return array|false
Pawel G's avatar
Pawel G committed
519 520
   *  Array of sitemap settings for an entity bundle, a non-bundle entity type
   *  or for all entity types and their bundles.
521
   *  False if entity type does not exist.
522 523
   *
   * @todo: variants
524
   */
525
  public function getBundleSettings($entity_type_id = NULL, $bundle_name = NULL) {
526
    if (NULL !== $entity_type_id) {
527 528

      // Get bundle settings saved in simple_sitemap.bundle_settings.*.* configuration.
529
      $bundle_name = NULL !== $bundle_name ? $bundle_name : $entity_type_id;
530
      $bundle_settings = $this->configFactory
531 532
        ->get("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name")
        ->get();
533 534 535 536 537

      // If not found and entity type is enabled, return default bundle settings.
      if (empty($bundle_settings)) {
        if ($this->entityTypeIsEnabled($entity_type_id)
          && isset($this->entityTypeBundleInfo->getBundleInfo($entity_type_id)[$bundle_name])) {
538
          self::supplementDefaultSettings('entity', $bundle_settings);
539 540 541 542 543
        }
        else {
          $bundle_settings = FALSE;
        }
      }
544
    }
545
    else {
546
      // Get all bundle settings saved in simple_sitemap.bundle_settings.*.* configuration.
Pawel G's avatar
Pawel G committed
547
      $config_names = $this->configFactory->listAll('simple_sitemap.bundle_settings.');
548
      $bundle_settings = [];
Pawel G's avatar
Pawel G committed
549
      foreach ($config_names as $config_name) {
550
        $config_name_parts = explode('.', $config_name);
551 552 553 554 555 556 557 558
        $bundle_settings[$config_name_parts[2]][$config_name_parts[3]] = $this->configFactory->get($config_name)->get();
      }

      // Supplement default bundle settings for all bundles not found in simple_sitemap.bundle_settings.*.* configuration.
      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])) {
559
              self::supplementDefaultSettings('entity', $bundle_settings[$type_id][$bundle]);
560 561 562
            }
          }
        }
563 564
      }
    }
565
    return $bundle_settings;
566 567 568
  }

  /**
569 570
   * Supplements all missing link setting with default values.
   *
571
   * @param string $type
Pawel G's avatar
Pawel G committed
572 573 574
   *  'entity'|'custom'
   * @param array &$settings
   * @param array $overrides
575
   */
Pawel G's avatar
Pawel G committed
576
  public static function supplementDefaultSettings($type, &$settings, $overrides = []) {
577
    foreach (self::$allowedLinkSettings[$type] as $allowed_link_setting) {
578
      if (!isset($settings[$allowed_link_setting])
Pawel G's avatar
Pawel G committed
579
        && isset(self::$linkSettingDefaults[$allowed_link_setting])) {
580 581 582
        $settings[$allowed_link_setting] = isset($overrides[$allowed_link_setting])
          ? $overrides[$allowed_link_setting]
          : self::$linkSettingDefaults[$allowed_link_setting];
583 584
      }
    }
585 586
  }

Pawel G's avatar
Pawel G committed
587 588 589
  /**
   * Overrides entity bundle/entity type sitemap settings for a single entity.
   *
Pawel G's avatar
Pawel G committed
590 591 592
   * @param string $entity_type_id
   * @param int $id
   * @param array $settings
Pawel G's avatar
Pawel G committed
593
   *
Pawel G's avatar
Pawel G committed
594
   * @return $this
595 596
   *
   * @todo: variants
Pawel G's avatar
Pawel G committed
597
   */
598
  public function setEntityInstanceSettings($entity_type_id, $id, $settings) {
599
    $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($id);
600 601 602
    $bundle_settings = $this->getBundleSettings(
      $entity_type_id, $this->entityHelper->getEntityInstanceBundleName($entity)
    );
603
    if (!empty($bundle_settings)) {
604 605 606 607

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

Pawel G's avatar
Pawel G committed
614 615
      // Save overrides for this entity if something is different.
      if ($override) {
616
        $this->db->merge('simple_sitemap_entity_overrides')
617
          ->keys([
618 619 620 621 622
            'entity_type' => $entity_type_id,
            'entity_id' => $id])
          ->fields([
            'entity_type' => $entity_type_id,
            'entity_id' => $id,
623
            'inclusion_settings' => serialize(array_merge($bundle_settings, $settings)),])
624
          ->execute();
625
      }
Pawel G's avatar
Pawel G committed
626 627
      // Else unset override.
      else {
628
        $this->removeEntityInstanceSettings($entity_type_id, $id);
629
      }
630 631 632
    }
    else {
      //todo: log error
633
    }
Pawel G's avatar
Pawel G committed
634
    return $this;
635 636
  }

Pawel G's avatar
Pawel G committed
637
  /**
638
   * Gets sitemap settings for an entity instance which overrides the sitemap
639
   * settings of its bundle, or bundle settings, if they are not overridden.
Pawel G's avatar
Pawel G committed
640
   *
Pawel G's avatar
Pawel G committed
641
   * @param string $entity_type_id
642
   * @param int $id
Pawel G's avatar
Pawel G committed
643
   *
644
   * @return array|false
645 646
   *
   * @todo: variants
Pawel G's avatar
Pawel G committed
647
   */
648 649 650 651 652 653 654 655 656
  public function getEntityInstanceSettings($entity_type_id, $id) {
    $results = $this->db->select('simple_sitemap_entity_overrides', 'o')
      ->fields('o', ['inclusion_settings'])
      ->condition('o.entity_type', $entity_type_id)
      ->condition('o.entity_id', $id)
      ->execute()
      ->fetchField();

    if (!empty($results)) {
657
      return unserialize($results);
658 659
    }
    else {
660 661 662 663 664 665
      $entity = $this->entityTypeManager->getStorage($entity_type_id)
        ->load($id);
      return $this->getBundleSettings(
        $entity_type_id,
        $this->entityHelper->getEntityInstanceBundleName($entity)
      );
666 667 668
    }
  }

669 670 671 672 673 674 675 676
  /**
   * 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
677 678
   *
   * @todo: variants
679 680 681 682
   */
  public function removeEntityInstanceSettings($entity_type_id, $entity_ids = NULL) {
    $query = $this->db->delete('simple_sitemap_entity_overrides')
      ->condition('entity_type', $entity_type_id);
683
    if (NULL !== $entity_ids) {
684 685 686 687 688 689 690
      $entity_ids = !is_array($entity_ids) ? [$entity_ids] : $entity_ids;
      $query->condition('entity_id', $entity_ids, 'IN');
    }
    $query->execute();
    return $this;
  }

Pawel G's avatar
Pawel G committed
691 692 693 694
  /**
   * Checks if an entity bundle (or a non-bundle entity type) is set to be
   * indexed in the sitemap settings.
   *
695 696
   * @param string $entity_type_id
   * @param string|null $bundle_name
Pawel G's avatar
Pawel G committed
697
   *
Pawel G's avatar
Pawel G committed
698 699
   * @return bool
   */
700
  public function bundleIsIndexed($entity_type_id, $bundle_name = NULL) {
701 702 703 704
    $settings = $this->getBundleSettings($entity_type_id, $bundle_name);
    return !empty($settings['index']);
  }

Pawel G's avatar
Pawel G committed
705 706 707
  /**
   * Checks if an entity type is enabled in the sitemap settings.
   *
708
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
709
   *
Pawel G's avatar
Pawel G committed
710 711
   * @return bool
   */
712
  public function entityTypeIsEnabled($entity_type_id) {
713
    return in_array($entity_type_id, $this->getSetting('enabled_entity_types', []));
714 715
  }

Pawel G's avatar
Pawel G committed
716
  /**
717
   * Stores a custom path along with its sitemap settings to configuration.
Pawel G's avatar
Pawel G committed
718
   *
Pawel G's avatar
Pawel G committed
719 720
   * @param string $path
   * @param array $settings
Pawel G's avatar
Pawel G committed
721
   *
Pawel G's avatar
Pawel G committed
722
   * @return $this
723 724
   *
   * @todo Validate $settings and throw exceptions
725
   * @todo: variants
Pawel G's avatar
Pawel G committed
726
   */
727
  public function addCustomLink($path, $settings = []) {
728
    if (!(bool) $this->pathValidator->getUrlIfValidWithoutAccessCheck($path)) {
Pawel G's avatar
Pawel G committed
729 730 731
      // todo: log error.
      return $this;
    }
Pawel G's avatar
Pawel G committed
732
    if ($path[0] !== '/') {
Pawel G's avatar
Pawel G committed
733 734 735
      // todo: log error.
      return $this;
    }
Pawel G's avatar
Pawel G committed
736

737
    $custom_links = $this->getCustomLinks(FALSE);
Pawel G's avatar
Pawel G committed
738
    foreach ($custom_links as $key => $link) {
739
      if ($link['path'] === $path) {
740 741 742 743 744
        $link_key = $key;
        break;
      }
    }
    $link_key = isset($link_key) ? $link_key : count($custom_links);
745
    $custom_links[$link_key] = ['path' => $path] + $settings;
Pawel G's avatar
Pawel G committed
746
    $this->configFactory->getEditable('simple_sitemap.custom')
747
      ->set('links', $custom_links)->save();
748

Pawel G's avatar
Pawel G committed
749
    return $this;
750 751
  }

752 753 754
  /**
   * Returns an array of custom paths and their sitemap settings.
   *
755
   * @param bool $supplement_default_settings
756
   * @return array
757 758
   *
   * @todo: variants
759
   */
760
  public function getCustomLinks($supplement_default_settings = TRUE) {
761 762
    $custom_links = $this->configFactory
      ->get('simple_sitemap.custom')
763
      ->get('links');
764 765 766

    if ($supplement_default_settings) {
      foreach ($custom_links as $i => $link_settings) {
Pawel G's avatar
Pawel G committed
767 768
        self::supplementDefaultSettings('custom', $link_settings);
        $custom_links[$i] = $link_settings;
769 770 771
      }
    }

772
    return !empty($custom_links) ? $custom_links : [];
773 774
  }

Pawel G's avatar
Pawel G committed
775 776 777
  /**
   * Returns settings for a custom path added to the sitemap settings.
   *
Pawel G's avatar
Pawel G committed
778
   * @param string $path
Pawel G's avatar
Pawel G committed
779
   *
Pawel G's avatar
Pawel G committed
780
   * @return array|false
781 782
   *
   * @todo: variants
Pawel G's avatar
Pawel G committed
783
   */
784
  public function getCustomLink($path) {
785
    foreach ($this->getCustomLinks() as $key => $link) {
786
      if ($link['path'] === $path) {
787
        return $link;
788 789 790 791 792
      }
    }
    return FALSE;
  }

Pawel G's avatar
Pawel G committed
793
  /**
794
   * Removes all custom paths from the sitemap settings.
Pawel G's avatar
Pawel G committed
795
   *
Pawel G's avatar
Pawel G committed
796
   * @return $this
797 798
   *
   * @todo: variants
Pawel G's avatar
Pawel G committed
799
   */
800 801 802 803 804 805 806 807 808 809 810 811 812 813
  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;
          }
        }
814 815
      }
    }
816 817 818 819
    if (!empty($save)) {
      $this->configFactory->getEditable('simple_sitemap.custom')
        ->set('links', array_values($custom_links))->save();
    }
820

Pawel G's avatar
Pawel G committed
821
    return $this;
822
  }
823
}