Simplesitemap.php 24.2 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 137 138 139 140 141 142 143
   * @param string $name
   *   Name of the setting, like 'max_links'.
   *
   * @param mixed $default
   *   Value to be returned if the setting does not exist in the configuration.
   *
   * @return mixed
   *   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 156 157 158 159 160 161 162
  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
   *   Setting name, like 'max_links'.
   * @param mixed $setting
   *   The setting to be saved.
   *
   * @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 177 178 179 180 181
   *
   * @return string|false
   *   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. False
   *   if sitemap is not retrievable from the database.
   */
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])) {
188
        // Return sitemap index, if there are multiple sitemap chunks.
189 190
        return $this->fetchSitemapChunk($chunk_info[SitemapGeneratorBase::INDEX_DELTA]->id)
          ->sitemap_string;
191 192 193
      }
      else {
        // Return sitemap 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 212 213
   * @return array
   *   An array containing chunk creation timestamps keyed by chunk ID.
   */
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 db.
243 244
   *
   * @param string $from
Pawel G's avatar
Pawel G committed
245
   *   Can be 'form', 'backend', 'drush' or 'nobatch'.
246
   *   This decides how the batch process is to be run.
Pawel G's avatar
Pawel G committed
247
   *
248 249
   * @param array|null $sitemap_types
   *
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 340 341 342
  /**
   * @todo Add removeSitemap API method.
   */

343 344 345
  /**
   * Returns a 'time ago' string of last timestamp generation.
   *
346 347 348
   * @param string|null $type
   *
   * @return string|array|false
349 350
   *   Formatted timestamp of last sitemap generation, otherwise FALSE.
   */
351 352 353
  public function getGeneratedAgo($type = NULL) {
    $chunks = $this->fetchSitemapChunkInfo($type);
    if ($type !== NULL) {
354
      return isset($chunks[DefaultSitemapGenerator::FIRST_CHUNK_DELTA]->sitemap_created)
355
        ? $this->dateFormatter
356
          ->formatInterval($this->time->getRequestTime() - $chunks[DefaultSitemapGenerator::FIRST_CHUNK_DELTA]
357 358 359 360 361 362 363 364 365 366 367 368
              ->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;
369 370 371
    }
  }

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

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

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

    // Deleting inclusion settings.
412
    $config_names = $this->configFactory->listAll("simple_sitemap.bundle_settings.$entity_type_id.");
Pawel G's avatar
Pawel G committed
413
    foreach ($config_names as $config_name) {
414
      $this->configFactory->getEditable($config_name)->delete();
415
    }
416 417 418

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

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

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

    $bundle_settings = $this->configFactory
      ->getEditable("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name");
Pawel G's avatar
Pawel G committed
450
    foreach ($settings as $setting_key => $setting) {
451
      if ($setting_key === 'index') {
452 453
        $setting = intval($setting);
      }
454
      $bundle_settings->set($setting_key, $setting);
455
    }
456
    $bundle_settings->save();
457 458

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

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

467
      $query = $this->entityTypeManager->getStorage($entity_type_id)->getQuery();
468
      if (!$this->entityHelper->entityTypeIsAtomic($entity_type_id)) {
469
        $query->condition($keys['bundle'], $bundle_name);
470
      }
471 472 473 474 475 476 477 478 479
      $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
      $delete_instances = [];
Pawel G's avatar
Pawel G committed
481
      foreach ($query->execute()->fetchAll() as $result) {
482 483 484
        $delete = TRUE;
        $instance_settings = unserialize($result->inclusion_settings);
        foreach ($instance_settings as $setting_key => $instance_setting) {
485
          if ($instance_setting != $settings[$setting_key]) {
486 487 488 489 490
            $delete = FALSE;
            break;
          }
        }
        if ($delete) {
491
          $delete_instances[] = $result->id;
492
        }
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
    }
Pawel G's avatar
Pawel G committed
503
    return $this;
504 505
  }

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

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

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

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

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

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

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

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

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

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

    if ($supplement_default_settings) {
      foreach ($custom_links as $i => $link_settings) {
Pawel G's avatar
Pawel G committed
727 728
        self::supplementDefaultSettings('custom', $link_settings);
        $custom_links[$i] = $link_settings;
729 730 731
      }
    }

732
    return $custom_links !== NULL ? $custom_links : [];
733 734
  }

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

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

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