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 6 7
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Path\PathValidator;
Pawel G's avatar
Pawel G committed
8 9
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Datetime\DateFormatter;
10
use Drupal\Component\Datetime\Time;
11 12 13
use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\DefaultSitemapGenerator;
use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorBase;
use Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorManager;
14
use Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager;
15

16
/**
Pawel G's avatar
Pawel G committed
17
 * Class Simplesitemap
Pawel G's avatar
Pawel G committed
18
 * @package Drupal\simple_sitemap
19 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

  /**
   * @var \Drupal\Core\Config\ConfigFactory
   */
30
  protected $configFactory;
Pawel G's avatar
Pawel G committed
31 32 33 34

  /**
   * @var \Drupal\Core\Database\Connection
   */
35
  protected $db;
Pawel G's avatar
Pawel G committed
36 37 38 39

  /**
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
40
  protected $entityTypeManager;
Pawel G's avatar
Pawel G committed
41 42 43 44

  /**
   * @var \Drupal\Core\Path\PathValidator
   */
45
  protected $pathValidator;
Pawel G's avatar
Pawel G committed
46

47 48 49 50 51
  /**
   * @var \Drupal\Core\Datetime\DateFormatter
   */
  protected $dateFormatter;

52 53 54 55 56
  /**
   * @var \Drupal\Component\Datetime\Time
   */
  protected $time;

57
  /**
58
   * @var \Drupal\simple_sitemap\Batch
59 60 61 62 63 64 65 66
   */
  protected $batch;

  /**
   * @var \Drupal\Core\Extension\ModuleHandler
   */
  protected $moduleHandler;

67 68 69 70 71
  /**
   * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager
   */
  protected $urlGeneratorManager;

72 73 74 75 76
  /**
   * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorManager
   */
  protected $sitemapGeneratorManager;

Pawel G's avatar
Pawel G committed
77 78 79
  /**
   * @var array
   */
Pawel G's avatar
Pawel G committed
80
  protected static $allowedLinkSettings = [
81
    'entity' => ['index', 'priority', 'changefreq', 'include_images'],
82 83 84
    'custom' => ['priority', 'changefreq'],
  ];

Pawel G's avatar
Pawel G committed
85 86 87 88
  /**
   * @var array
   */
  protected static $linkSettingDefaults = [
89
    'index' => 1,
90
    'priority' => 0.5,
91
    'changefreq' => '',
92
    'include_images' => 0,
93
  ];
94

95 96
  /**
   * Simplesitemap constructor.
97 98
   * @param \Drupal\simple_sitemap\EntityHelper $entity_helper
   * @param \Drupal\Core\Config\ConfigFactory $config_factory
Pawel G's avatar
Pawel G committed
99
   * @param \Drupal\Core\Database\Connection $database
100 101 102
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   * @param \Drupal\Core\Path\PathValidator $path_validator
   * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
103
   * @param \Drupal\Component\Datetime\Time $time
104
   * @param \Drupal\simple_sitemap\Batch $batch
105 106
   * @param \Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager $url_generator_manager
   * @param \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorManager $sitemap_generator_manager
107
   */
108
  public function __construct(
109 110
    EntityHelper $entity_helper,
    ConfigFactory $config_factory,
Pawel G's avatar
Pawel G committed
111
    Connection $database,
112 113 114
    EntityTypeManagerInterface $entity_type_manager,
    PathValidator $path_validator,
    DateFormatter $date_formatter,
115 116
    Time $time,
    Batch $batch,
117 118
    UrlGeneratorManager $url_generator_manager,
    SitemapGeneratorManager $sitemap_generator_manager
119
  ) {
120 121
    $this->entityHelper = $entity_helper;
    $this->configFactory = $config_factory;
122
    $this->db = $database;
123 124 125
    $this->entityTypeManager = $entity_type_manager;
    $this->pathValidator = $path_validator;
    $this->dateFormatter = $date_formatter;
126
    $this->time = $time;
127
    $this->batch = $batch;
128 129
    $this->urlGeneratorManager = $url_generator_manager;
    $this->sitemapGeneratorManager = $sitemap_generator_manager;
130 131
  }

132
  /**
133 134
   * Returns a specific sitemap setting or a default value if setting does not
   * exist.
Pawel G's avatar
Pawel G committed
135
   *
136
   * @param string $name
Pawel G's avatar
Pawel G committed
137
   *  Name of the setting, like 'max_links'.
138 139
   *
   * @param mixed $default
Pawel G's avatar
Pawel G committed
140
   *  Value to be returned if the setting does not exist in the configuration.
141 142
   *
   * @return mixed
Pawel G's avatar
Pawel G committed
143
   *  The current setting from configuration or a default value.
Pawel G's avatar
Pawel G committed
144
   */
145 146 147 148 149 150 151 152 153 154 155
  public function getSetting($name, $default = FALSE) {
    $setting = $this->configFactory
      ->get('simple_sitemap.settings')
      ->get($name);
    return NULL !== $setting ? $setting : $default;
  }

  /**
   * Stores a specific sitemap setting in configuration.
   *
   * @param string $name
Pawel G's avatar
Pawel G committed
156
   *  Setting name, like 'max_links'.
157
   * @param mixed $setting
Pawel G's avatar
Pawel G committed
158
   *  The setting to be saved.
159 160 161 162
   *
   * @return $this
   */
  public function saveSetting($name, $setting) {
Pawel G's avatar
Pawel G committed
163
    $this->configFactory->getEditable('simple_sitemap.settings')
164 165 166 167 168 169 170 171
      ->set($name, $setting)->save();
    return $this;
  }

  /**
   * Returns the whole sitemap, a requested sitemap chunk,
   * or the sitemap index file.
   *
172 173 174
   * @param string $type
   *
   * @param int $delta
175 176
   *
   * @return string|false
Pawel G's avatar
Pawel G committed
177 178 179 180
   *  If no sitemap ID provided, either a sitemap index is returned, or the
   *  whole sitemap, if the amount of links does not exceed the max links
   *  setting. If a sitemap ID is provided, a sitemap chunk is returned.
   *  Returns false if the sitemap is not retrievable from the database.
181
   */
182 183
  public function getSitemap($type = SitemapGeneratorBase::DEFAULT_SITEMAP_TYPE, $delta = NULL) {
    $chunk_info = $this->fetchSitemapChunkInfo($type);
184

185
    if (empty($delta) || !isset($chunk_info[$delta])) {
186

187
      if (isset($chunk_info[SitemapGeneratorBase::INDEX_DELTA])) {
Pawel G's avatar
Pawel G committed
188
        // Return sitemap index if one exists.
189 190
        return $this->fetchSitemapChunk($chunk_info[SitemapGeneratorBase::INDEX_DELTA]->id)
          ->sitemap_string;
191 192
      }
      else {
Pawel G's avatar
Pawel G committed
193
        // Return sitemap chunk if there is only one chunk.
194 195
        return isset($chunk_info[SitemapGeneratorBase::FIRST_CHUNK_DELTA])
          ? $this->fetchSitemapChunk($chunk_info[SitemapGeneratorBase::FIRST_CHUNK_DELTA]->id)
196 197 198 199 200 201
            ->sitemap_string
          : FALSE;
      }
    }
    else {
      // Return specific sitemap chunk.
202
      return $this->fetchSitemapChunk($chunk_info[$delta]->id)->sitemap_string;
203 204 205 206 207 208
    }
  }

  /**
   * Fetches all sitemap chunk timestamps keyed by chunk ID.
   *
209 210
   * @param string|null $type
   *
211
   * @return array
Pawel G's avatar
Pawel G committed
212
   *  An array containing chunk creation timestamps keyed by chunk ID.
213
   */
214 215 216 217 218 219 220 221 222 223 224
  protected function fetchSitemapChunkInfo($type = NULL) {
    $query = $this->db->select('simple_sitemap', 's')
      ->fields('s', ['id', 'delta', 'sitemap_created', 'type']);

    if (NULL !== $type) {
      $query->condition('s.type', $type);
    }

    $result = $query->execute();

    return NULL === $type ? $result->fetchAllAssoc('type') : $result->fetchAllAssoc('delta');
225 226
  }

227 228 229 230 231 232 233 234 235
  /**
   * Fetches a single sitemap chunk by ID.
   *
   * @param int $id
   *   The chunk ID.
   *
   * @return object
   *   A sitemap chunk object.
   */
236
  protected function fetchSitemapChunk($id) {
237 238 239 240 241
    return $this->db->query('SELECT * FROM {simple_sitemap} WHERE id = :id',
      [':id' => $id])->fetchObject();
  }

  /**
Pawel G's avatar
Pawel G committed
242
   * Generates the XML sitemap and saves it to the database.
243 244
   *
   * @param string $from
Pawel G's avatar
Pawel G committed
245 246
   *  Can be 'form', 'backend', 'drush' or 'nobatch'.
   *  This decides how the batch process is to be run.
Pawel G's avatar
Pawel G committed
247
   *
248
   * @param array|string|null $sitemap_types
249
   *
Pawel G's avatar
Pawel G committed
250
   * @return bool|\Drupal\simple_sitemap\Simplesitemap
251
   */
252 253
  public function generateSitemap($from = 'form', $sitemap_types = NULL) {
    $sitemap_types = NULL === $sitemap_types ? NULL : (array) $sitemap_types;
254

255
    $settings = [
256
      'base_url' => $this->getSetting('base_url', ''),
257
      'batch_process_limit' => $this->getSetting('batch_process_limit', 1500),
258 259 260 261
      'max_links' => $this->getSetting('max_links', 2000),
      'skip_untranslated' => $this->getSetting('skip_untranslated', FALSE),
      'remove_duplicates' => $this->getSetting('remove_duplicates', TRUE),
      'excluded_languages' => $this->getSetting('excluded_languages', []),
262
    ];
263

264 265 266 267
    $this->batch->setBatchMeta(['from' => $from]);

    $sitemap_generators = $this->sitemapGeneratorManager->getDefinitions();
    $url_generators = $this->urlGeneratorManager->getDefinitions();
Pawel G's avatar
Pawel G committed
268

269 270 271 272 273
    foreach ([&$sitemap_generators, &$url_generators] as &$plugin_group) {
      usort($plugin_group, function($a, $b) {
        return $a['weight'] - $b['weight'];
      });
    }
Pawel G's avatar
Pawel G committed
274

275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
    $operations_per_type = [];
    foreach ($url_generators as $url_generator) {
      if ($url_generator['enabled']) {
        foreach ($this->urlGeneratorManager->createInstance($url_generator['id'])->getDataSets() as $sitemap_type => $data_sets) {

          // Skipping unwanted sitemap types.
          if (NULL !== $sitemap_types && !in_array($sitemap_type, $sitemap_types)) {
            continue;
          }

          // Adding a remove_sitemap operation for all sitemap types.
          if (!isset($operations_per_type[$sitemap_type])) {
            $operations_per_type[$sitemap_type][] = [
              'operation' => 'removeSitemap',
              'arguments' => [
                'sitemap_generator' => $sitemap_type,
              ]
            ];
          }

          // Adding generate_sitemap operations for all data sets.
          foreach ($data_sets as $data_set) {
            if (!empty($data_set)) {
              $operations_per_type[$sitemap_type][] = [
                'operation' => 'generateSitemap',
                'arguments' => [
                  'url_generator' => $url_generator['id'],
                  'data_set' => $data_set,
                  'settings' => $settings,
                ],
              ];
306
            }
307 308
          }
        }
309
      }
310 311
    }

312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
    // Adding generate_index operations at the right position for all sitemap types.
    foreach ($operations_per_type as $sitemap_type => $operations) {
      $operations_per_type[$sitemap_type][] = [
        'operation' => 'generateIndex',
        'arguments' => [
          'sitemap_generator' => $sitemap_type,
          'settings' => $settings,
        ],
      ];
    }

    // todo Sort operations according to sitemap type weight.
    // todo Only add operation if sitemap type is enabled.

    // Adding operations to batch.
    if (!empty($operations_per_type)) {
      foreach ($operations_per_type as $sitemap_type => $operations) {
        foreach ($operations as $operation_data) {
          $this->batch->addOperation($operation_data['operation'], $operation_data['arguments']);
        }
      }
      $success = $this->batch->start();
    }

    return $from === 'nobatch' ? $this : (isset($success) ? $success : FALSE);
337 338
  }

339
  /**
Pawel G's avatar
Pawel G committed
340 341
   * @param null|array $sitemap_types
   *
342 343
   * @todo Add removeSitemap API method.
   */
Pawel G's avatar
Pawel G committed
344 345 346
  public function removeSitemap($sitemap_types = NULL) {

  }
347

348 349 350
  /**
   * Returns a 'time ago' string of last timestamp generation.
   *
351 352 353
   * @param string|null $type
   *
   * @return string|array|false
Pawel G's avatar
Pawel G committed
354
   *  Formatted timestamp of last sitemap generation, otherwise FALSE.
355
   */
356 357 358
  public function getGeneratedAgo($type = NULL) {
    $chunks = $this->fetchSitemapChunkInfo($type);
    if ($type !== NULL) {
359
      return isset($chunks[DefaultSitemapGenerator::FIRST_CHUNK_DELTA]->sitemap_created)
360
        ? $this->dateFormatter
361
          ->formatInterval($this->time->getRequestTime() - $chunks[DefaultSitemapGenerator::FIRST_CHUNK_DELTA]
362 363 364 365 366 367 368 369 370 371 372 373
              ->sitemap_created)
        : FALSE;
    }
    else {
      $time_strings = [];
//      foreach ($chunks as $sitemap_type => $type_chunks) {
//        $time_strings[$sitemap_type] = isset($type_chunks[DefaultSitemapGenerator::FIRST_DELTA_INDEX]->sitemap_created)
//          ? $type_chunks[DefaultSitemapGenerator::FIRST_DELTA_INDEX]->sitemap_created
//          : FALSE;
//    }
      // todo: Implement.
      return $time_strings;
374 375 376
    }
  }

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

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

    // Deleting inclusion settings.
417
    $config_names = $this->configFactory->listAll("simple_sitemap.bundle_settings.$entity_type_id.");
Pawel G's avatar
Pawel G committed
418
    foreach ($config_names as $config_name) {
419
      $this->configFactory->getEditable($config_name)->delete();
420
    }
421 422 423

    // Deleting entity overrides.
    $this->removeEntityInstanceSettings($entity_type_id);
Pawel G's avatar
Pawel G committed
424
    return $this;
425 426 427
  }

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

Pawel G's avatar
Pawel G committed
446 447 448 449 450 451
    if (!empty($old_settings = $this->getBundleSettings($entity_type_id, $bundle_name))) {
      $settings = array_merge($old_settings, $settings);
    }
    else {
      self::supplementDefaultSettings('entity', $settings);
    }
452 453 454

    $bundle_settings = $this->configFactory
      ->getEditable("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name");
Pawel G's avatar
Pawel G committed
455
    foreach ($settings as $setting_key => $setting) {
456
      if ($setting_key === 'index') {
457 458
        $setting = intval($setting);
      }
459
      $bundle_settings->set($setting_key, $setting);
460
    }
461
    $bundle_settings->save();
462 463

    // Delete entity overrides which are identical to new bundle setting.
Pawel G's avatar
Pawel G committed
464
    $sitemap_entity_types = $this->entityHelper->getSupportedEntityTypes();
465 466 467
    if (isset($sitemap_entity_types[$entity_type_id])) {
      $entity_type = $sitemap_entity_types[$entity_type_id];
      $keys = $entity_type->getKeys();
468 469

      // Menu fix.
Pawel G's avatar
Pawel G committed
470
      $keys['bundle'] = $entity_type_id === 'menu_link_content' ? 'menu_name' : $keys['bundle'];
471

472
      $query = $this->entityTypeManager->getStorage($entity_type_id)->getQuery();
473
      if (!$this->entityHelper->entityTypeIsAtomic($entity_type_id)) {
474
        $query->condition($keys['bundle'], $bundle_name);
475
      }
476 477 478 479 480 481 482 483 484
      $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');
      }

485
      $delete_instances = [];
Pawel G's avatar
Pawel G committed
486
      foreach ($query->execute()->fetchAll() as $result) {
487 488 489
        $delete = TRUE;
        $instance_settings = unserialize($result->inclusion_settings);
        foreach ($instance_settings as $setting_key => $instance_setting) {
490
          if ($instance_setting != $settings[$setting_key]) {
491 492 493 494 495
            $delete = FALSE;
            break;
          }
        }
        if ($delete) {
496
          $delete_instances[] = $result->id;
497
        }
498
      }
499 500 501 502 503
      if (!empty($delete_instances)) {
        $this->db->delete('simple_sitemap_entity_overrides')
          ->condition('id', $delete_instances, 'IN')
          ->execute();
      }
504
    }
505 506 507
    else {
      //todo: log error
    }
Pawel G's avatar
Pawel G committed
508
    return $this;
509 510
  }

511
  /**
512 513
   * Gets sitemap settings for an entity bundle, a non-bundle entity type or for
   * all entity types and their bundles.
514
   *
515 516 517
   * @param string|null $entity_type_id
   *  If set to null, sitemap settings for all entity types and their bundles
   *  are fetched.
518 519 520
   * @param string|null $bundle_name
   *
   * @return array|false
Pawel G's avatar
Pawel G committed
521 522
   *  Array of sitemap settings for an entity bundle, a non-bundle entity type
   *  or for all entity types and their bundles.
523
   *  False if entity type does not exist.
524
   */
525
  public function getBundleSettings($entity_type_id = NULL, $bundle_name = NULL) {
526
    if (NULL !== $entity_type_id) {
527
      $bundle_name = empty($bundle_name) ? $entity_type_id : $bundle_name;
528
      $bundle_settings = $this->configFactory
529 530
        ->get("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name")
        ->get();
531
      return !empty($bundle_settings) ? $bundle_settings : FALSE;
532
    }
533
    else {
Pawel G's avatar
Pawel G committed
534
      $config_names = $this->configFactory->listAll('simple_sitemap.bundle_settings.');
535
      $all_settings = [];
Pawel G's avatar
Pawel G committed
536
      foreach ($config_names as $config_name) {
537
        $config_name_parts = explode('.', $config_name);
538
        $all_settings[$config_name_parts[2]][$config_name_parts[3]] = $this->configFactory->get($config_name)->get();
539 540 541 542 543 544
      }
      return $all_settings;
    }
  }

  /**
545 546
   * Supplements all missing link setting with default values.
   *
547
   * @param string $type
Pawel G's avatar
Pawel G committed
548 549 550
   *  'entity'|'custom'
   * @param array &$settings
   * @param array $overrides
551
   */
Pawel G's avatar
Pawel G committed
552
  public static function supplementDefaultSettings($type, &$settings, $overrides = []) {
553
    foreach (self::$allowedLinkSettings[$type] as $allowed_link_setting) {
554
      if (!isset($settings[$allowed_link_setting])
Pawel G's avatar
Pawel G committed
555
        && isset(self::$linkSettingDefaults[$allowed_link_setting])) {
556 557 558
        $settings[$allowed_link_setting] = isset($overrides[$allowed_link_setting])
          ? $overrides[$allowed_link_setting]
          : self::$linkSettingDefaults[$allowed_link_setting];
559 560
      }
    }
561 562
  }

Pawel G's avatar
Pawel G committed
563 564 565
  /**
   * Overrides entity bundle/entity type sitemap settings for a single entity.
   *
Pawel G's avatar
Pawel G committed
566 567 568
   * @param string $entity_type_id
   * @param int $id
   * @param array $settings
Pawel G's avatar
Pawel G committed
569
   *
Pawel G's avatar
Pawel G committed
570 571
   * @return $this
   */
572
  public function setEntityInstanceSettings($entity_type_id, $id, $settings) {
573
    $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($id);
574 575 576
    $bundle_settings = $this->getBundleSettings(
      $entity_type_id, $this->entityHelper->getEntityInstanceBundleName($entity)
    );
577
    if (!empty($bundle_settings)) {
578 579 580 581

      // Check if overrides are different from bundle setting before saving.
      $override = FALSE;
      foreach ($settings as $key => $setting) {
582
        if (!isset($bundle_settings[$key]) || $setting != $bundle_settings[$key]) {
583 584 585 586
          $override = TRUE;
          break;
        }
      }
Pawel G's avatar
Pawel G committed
587 588
      // Save overrides for this entity if something is different.
      if ($override) {
589 590 591 592 593 594 595
        $this->db->merge('simple_sitemap_entity_overrides')
          ->key([
            'entity_type' => $entity_type_id,
            'entity_id' => $id])
          ->fields([
            'entity_type' => $entity_type_id,
            'entity_id' => $id,
596
            'inclusion_settings' => serialize(array_merge($bundle_settings, $settings)),])
597
          ->execute();
598
      }
Pawel G's avatar
Pawel G committed
599 600
      // Else unset override.
      else {
601
        $this->removeEntityInstanceSettings($entity_type_id, $id);
602
      }
603 604 605
    }
    else {
      //todo: log error
606
    }
Pawel G's avatar
Pawel G committed
607
    return $this;
608 609
  }

Pawel G's avatar
Pawel G committed
610
  /**
611
   * Gets sitemap settings for an entity instance which overrides the sitemap
612
   * settings of its bundle, or bundle settings, if they are not overridden.
Pawel G's avatar
Pawel G committed
613
   *
Pawel G's avatar
Pawel G committed
614
   * @param string $entity_type_id
615
   * @param int $id
Pawel G's avatar
Pawel G committed
616
   *
617
   * @return array|false
Pawel G's avatar
Pawel G committed
618
   */
619 620 621 622 623 624 625 626 627
  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)) {
628
      return unserialize($results);
629 630
    }
    else {
631 632 633 634 635 636
      $entity = $this->entityTypeManager->getStorage($entity_type_id)
        ->load($id);
      return $this->getBundleSettings(
        $entity_type_id,
        $this->entityHelper->getEntityInstanceBundleName($entity)
      );
637 638 639
    }
  }

640 641 642 643 644 645 646 647 648 649 650 651
  /**
   * 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
   */
  public function removeEntityInstanceSettings($entity_type_id, $entity_ids = NULL) {
    $query = $this->db->delete('simple_sitemap_entity_overrides')
      ->condition('entity_type', $entity_type_id);
652
    if (NULL !== $entity_ids) {
653 654 655 656 657 658 659
      $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
660 661 662 663
  /**
   * Checks if an entity bundle (or a non-bundle entity type) is set to be
   * indexed in the sitemap settings.
   *
664 665
   * @param string $entity_type_id
   * @param string|null $bundle_name
Pawel G's avatar
Pawel G committed
666
   *
Pawel G's avatar
Pawel G committed
667 668
   * @return bool
   */
669
  public function bundleIsIndexed($entity_type_id, $bundle_name = NULL) {
670 671 672 673
    $settings = $this->getBundleSettings($entity_type_id, $bundle_name);
    return !empty($settings['index']);
  }

Pawel G's avatar
Pawel G committed
674 675 676
  /**
   * Checks if an entity type is enabled in the sitemap settings.
   *
677
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
678
   *
Pawel G's avatar
Pawel G committed
679 680
   * @return bool
   */
681
  public function entityTypeIsEnabled($entity_type_id) {
682
    return in_array($entity_type_id, $this->getSetting('enabled_entity_types', []));
683 684
  }

Pawel G's avatar
Pawel G committed
685
  /**
686
   * Stores a custom path along with its sitemap settings to configuration.
Pawel G's avatar
Pawel G committed
687
   *
Pawel G's avatar
Pawel G committed
688 689
   * @param string $path
   * @param array $settings
Pawel G's avatar
Pawel G committed
690
   *
Pawel G's avatar
Pawel G committed
691
   * @return $this
692 693
   *
   * @todo Validate $settings and throw exceptions
Pawel G's avatar
Pawel G committed
694
   */
695
  public function addCustomLink($path, $settings = []) {
Pawel G's avatar
Pawel G committed
696 697 698 699
    if (!$this->pathValidator->isValid($path)) {
      // todo: log error.
      return $this;
    }
Pawel G's avatar
Pawel G committed
700
    if ($path[0] !== '/') {
Pawel G's avatar
Pawel G committed
701 702 703
      // todo: log error.
      return $this;
    }
Pawel G's avatar
Pawel G committed
704

705
    $custom_links = $this->getCustomLinks(FALSE);
Pawel G's avatar
Pawel G committed
706
    foreach ($custom_links as $key => $link) {
707
      if ($link['path'] === $path) {
708 709 710 711 712
        $link_key = $key;
        break;
      }
    }
    $link_key = isset($link_key) ? $link_key : count($custom_links);
713
    $custom_links[$link_key] = ['path' => $path] + $settings;
Pawel G's avatar
Pawel G committed
714
    $this->configFactory->getEditable('simple_sitemap.custom')
715
      ->set('links', $custom_links)->save();
Pawel G's avatar
Pawel G committed
716
    return $this;
717 718
  }

719 720 721
  /**
   * Returns an array of custom paths and their sitemap settings.
   *
722
   * @param bool $supplement_default_settings
723 724
   * @return array
   */
725
  public function getCustomLinks($supplement_default_settings = TRUE) {
726 727
    $custom_links = $this->configFactory
      ->get('simple_sitemap.custom')
728
      ->get('links');
729 730 731

    if ($supplement_default_settings) {
      foreach ($custom_links as $i => $link_settings) {
Pawel G's avatar
Pawel G committed
732 733
        self::supplementDefaultSettings('custom', $link_settings);
        $custom_links[$i] = $link_settings;
734 735 736
      }
    }

737
    return $custom_links !== NULL ? $custom_links : [];
738 739
  }

Pawel G's avatar
Pawel G committed
740 741 742
  /**
   * Returns settings for a custom path added to the sitemap settings.
   *
Pawel G's avatar
Pawel G committed
743
   * @param string $path
Pawel G's avatar
Pawel G committed
744
   *
Pawel G's avatar
Pawel G committed
745
   * @return array|false
Pawel G's avatar
Pawel G committed
746
   */
747
  public function getCustomLink($path) {
748
    foreach ($this->getCustomLinks() as $key => $link) {
749
      if ($link['path'] === $path) {
750
        return $link;
751 752 753 754 755
      }
    }
    return FALSE;
  }

Pawel G's avatar
Pawel G committed
756 757 758
  /**
   * Removes a custom path from the sitemap settings.
   *
Pawel G's avatar
Pawel G committed
759
   * @param string $path
Pawel G's avatar
Pawel G committed
760
   *
Pawel G's avatar
Pawel G committed
761 762
   * @return $this
   */
763
  public function removeCustomLink($path) {
764
    $custom_links = $this->getCustomLinks(FALSE);
Pawel G's avatar
Pawel G committed
765
    foreach ($custom_links as $key => $link) {
766
      if ($link['path'] === $path) {
767 768
        unset($custom_links[$key]);
        $custom_links = array_values($custom_links);
Pawel G's avatar
Pawel G committed
769
        $this->configFactory->getEditable('simple_sitemap.custom')
770
          ->set('links', $custom_links)->save();
771
        break;
772 773
      }
    }
Pawel G's avatar
Pawel G committed
774
    return $this;
775 776
  }

Pawel G's avatar
Pawel G committed
777 778
  /**
   * Removes all custom paths from the sitemap settings.
Pawel G's avatar
Pawel G committed
779 780
   *
   * @return $this
Pawel G's avatar
Pawel G committed
781
   */
782
  public function removeCustomLinks() {
Pawel G's avatar
Pawel G committed
783
    $this->configFactory->getEditable('simple_sitemap.custom')
784
      ->set('links', [])->save();
Pawel G's avatar
Pawel G committed
785
    return $this;
786
  }
787
}