Simplesitemap.php 23.9 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

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

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

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

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

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

Pawel G's avatar
Pawel G committed
175
  /**
176
   * @return \Drupal\simple_sitemap\Queue\QueueWorker
Pawel G's avatar
Pawel G committed
177
   */
178 179
  public function getQueueWorker() {
    return $this->queueWorker;
Pawel G's avatar
Pawel G committed
180 181 182
  }

  /**
183
   * @return \Drupal\simple_sitemap\SimplesitemapManager
Pawel G's avatar
Pawel G committed
184
   */
185 186
  public function getSitemapManager() {
    return $this->manager;
187 188
  }

189 190 191 192
  /**
   * Returns the whole sitemap, a requested sitemap chunk,
   * or the sitemap index file.
   *
193
   * @param string $variant
194 195
   *
   * @param int $delta
196 197
   *
   * @return string|false
Pawel G's avatar
Pawel G committed
198
   *  If no sitemap ID provided, either a sitemap index is returned, or the
199 200
   *  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
201
   *  Returns false if the sitemap is not retrievable from the database.
202
   */
203
  public function getSitemap($variant = NULL, $delta = NULL) {
204 205 206 207
    $variant = NULL !== $variant
      ? $variant
      : $this->getSetting('default_variant', SimplesitemapManager::DEFAULT_SITEMAP_VARIANT);

Pawel G's avatar
Pawel G committed
208
    $chunk_info = $this->fetchSitemapVariantInfo($variant);
209

210
    if (empty($delta) || !isset($chunk_info[$delta])) {
211

212
      if (isset($chunk_info[SitemapGeneratorBase::INDEX_DELTA])) {
Pawel G's avatar
Pawel G committed
213
        // Return sitemap index if one exists.
214 215
        return $this->fetchSitemapChunk($chunk_info[SitemapGeneratorBase::INDEX_DELTA]->id)
          ->sitemap_string;
216 217
      }
      else {
Pawel G's avatar
Pawel G committed
218
        // Return sitemap chunk if there is only one chunk.
219 220
        return isset($chunk_info[SitemapGeneratorBase::FIRST_CHUNK_DELTA])
          ? $this->fetchSitemapChunk($chunk_info[SitemapGeneratorBase::FIRST_CHUNK_DELTA]->id)
221 222 223 224 225 226
            ->sitemap_string
          : FALSE;
      }
    }
    else {
      // Return specific sitemap chunk.
227
      return $this->fetchSitemapChunk($chunk_info[$delta]->id)->sitemap_string;
228 229 230 231
    }
  }

  /**
232
   * Fetches info about all published sitemap variants and their chunks.
233
   *
234
   * @param string|null $variant
235
   *
236
   * @return array
237 238
   *  An array containing all published sitemap chunk IDs, deltas and creation
   * timestamps keyed by their variant ID.
239
   */
Pawel G's avatar
Pawel G committed
240
  protected function fetchSitemapVariantInfo($variant = NULL) {
241
    $query = $this->db->select('simple_sitemap', 's')
242 243
      ->fields('s', ['id', 'delta', 'sitemap_created', 'type'])
      ->condition('s.status', 1);
244

245 246
    if (NULL !== $variant) {
      $query->condition('s.type', $variant);
247 248 249 250
    }

    $result = $query->execute();

251
    return NULL === $variant ? $result->fetchAllAssoc('type') : $result->fetchAllAssoc('delta');
252 253
  }

254 255 256 257 258 259 260 261 262
  /**
   * Fetches a single sitemap chunk by ID.
   *
   * @param int $id
   *   The chunk ID.
   *
   * @return object
   *   A sitemap chunk object.
   */
263
  protected function fetchSitemapChunk($id) {
264 265 266 267
    return $this->db->query('SELECT * FROM {simple_sitemap} WHERE id = :id',
      [':id' => $id])->fetchObject();
  }

268

269 270 271
  /**
   * @param null $variants
   * @return $this
272
   * @throws \Drupal\Component\Plugin\Exception\PluginException
273
   *
274
   * @todo document
275 276
   */
  public function removeSitemap($variants = NULL) {
277
    $saved_variants = $this->manager->getSitemapVariants();
Pawel G's avatar
Pawel G committed
278 279 280 281 282
    $this->moduleHandler->alter('simple_sitemap_variants', $saved_variants);
    $remove_variants = NULL !== $variants
      ? array_intersect_key($saved_variants, array_flip((array) $variants))
      : $saved_variants;

283
    if (!empty($remove_variants)) {
284
      $type_definitions = $this->manager->getSitemapTypes();
Pawel G's avatar
Pawel G committed
285
      $this->moduleHandler->alter('simple_sitemap_types', $type_definitions);
286
      foreach ($remove_variants as $variant_name => $variant_definition) {
287
        $this->manager->getSitemapGenerator($type_definitions[$variant_definition['type']]['sitemapGenerator'])
288
          ->setSitemapVariant($variant_name)
289
          ->remove();
290 291
      }
    }
Pawel G's avatar
Pawel G committed
292

293 294 295
    return $this;
  }

296

297 298
  /**
   * @param string $from
299
   * @param null $variants
Pawel G's avatar
Pawel G committed
300 301
   * @return $this
   * @throws \Drupal\Component\Plugin\Exception\PluginException
302
   */
303
  public function generateSitemap($from = 'form', $variants = NULL) {
304 305 306 307 308 309 310 311 312 313
    switch($from) {
      case 'form':
      case 'drush':
        $this->queueWorker->batchGenerateSitemap($from, $variants);
        break;

      case 'cron':
      case 'backend':
        $this->queueWorker->generateSitemap($variants);
        break;
314
    }
Pawel G's avatar
Pawel G committed
315 316

    return $this;
317
  }
318

319 320
  public function rebuildQueue($variants = NULL) {
    $this->queueWorker->rebuildQueue($variants);
Pawel G's avatar
Pawel G committed
321 322

    return $this;
323 324 325 326 327
  }

  /**
   * Returns a 'time ago' string of last timestamp generation.
   *
328
   * @param string|null $variant
329 330
   *
   * @return string|array|false
Pawel G's avatar
Pawel G committed
331
   *  Formatted timestamp of last sitemap generation, otherwise FALSE.
332
   */
333
  public function getGeneratedAgo($variant = NULL) {
Pawel G's avatar
Pawel G committed
334
    $chunks = $this->fetchSitemapVariantInfo($variant);
335
    if ($variant !== NULL) {
336
      return isset($chunks[DefaultSitemapGenerator::FIRST_CHUNK_DELTA]->sitemap_created)
337
        ? $this->dateFormatter
338
          ->formatInterval($this->time->getRequestTime() - $chunks[DefaultSitemapGenerator::FIRST_CHUNK_DELTA]
339 340 341 342 343 344 345 346 347 348 349 350
              ->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;
351 352 353
    }
  }

354 355
  /**
   * Enables sitemap support for an entity type. Enabled entity types show
356 357
   * sitemap settings on their bundle setting forms. If an enabled entity type
   * features bundles (e.g. 'node'), it needs to be set up with
358 359 360
   * setBundleSettings() as well.
   *
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
361
   *  Entity type id like 'node'.
362
   *
Pawel G's avatar
Pawel G committed
363
   * @return $this
364 365
   */
  public function enableEntityType($entity_type_id) {
366 367 368 369
    $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);
370
    }
Pawel G's avatar
Pawel G committed
371
    return $this;
372 373 374 375 376 377 378 379
  }

  /**
   * 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
380
   *  Entity type id like 'node'.
381
   *
Pawel G's avatar
Pawel G committed
382
   * @return $this
383 384
   */
  public function disableEntityType($entity_type_id) {
385 386 387

    // Updating settings.
    $enabled_entity_types = $this->getSetting('enabled_entity_types');
388
    if (FALSE !== ($key = array_search($entity_type_id, $enabled_entity_types))) {
389
      unset ($enabled_entity_types[$key]);
390
      $this->saveSetting('enabled_entity_types', array_values($enabled_entity_types));
391 392 393
    }

    // Deleting inclusion settings.
394
    $config_names = $this->configFactory->listAll("simple_sitemap.bundle_settings.$entity_type_id.");
Pawel G's avatar
Pawel G committed
395
    foreach ($config_names as $config_name) {
396
      $this->configFactory->getEditable($config_name)->delete();
397
    }
398 399 400

    // Deleting entity overrides.
    $this->removeEntityInstanceSettings($entity_type_id);
Pawel G's avatar
Pawel G committed
401
    return $this;
402 403 404
  }

  /**
Pawel G's avatar
Pawel G committed
405
   * Sets sitemap settings for a non-bundle entity type (e.g. user) or a bundle
406 407 408
   * of an entity type (e.g. page).
   *
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
409
   *  Entity type id like 'node' the bundle belongs to.
410
   * @param string $bundle_name
Pawel G's avatar
Pawel G committed
411
   *  Name of the bundle. NULL if entity type has no bundles.
412
   * @param array $settings
Pawel G's avatar
Pawel G committed
413 414
   *  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
415 416
   *
   * @return $this
417 418
   *
   * @todo: enableEntityType automatically
419
   */
420
  public function setBundleSettings($entity_type_id, $bundle_name = NULL, $settings = ['index' => TRUE]) {
421
    $bundle_name = NULL !== $bundle_name ? $bundle_name : $entity_type_id;
422

Pawel G's avatar
Pawel G committed
423 424 425
    if (!empty($old_settings = $this->getBundleSettings($entity_type_id, $bundle_name))) {
      $settings = array_merge($old_settings, $settings);
    }
426
    self::supplementDefaultSettings('entity', $settings);
427

428
    if ($settings != $old_settings) {
429

430 431 432 433 434 435 436
      // 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();
437

438 439 440 441 442
      // 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();
443

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

447 448 449 450 451 452 453 454 455 456 457 458
        $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');
        }
459

460 461 462 463 464 465 466 467 468 469 470 471
        $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;
472 473
          }
        }
474 475 476 477
        if (!empty($delete_instances)) {
          $this->db->delete('simple_sitemap_entity_overrides')
            ->condition('id', $delete_instances, 'IN')
            ->execute();
478
        }
479
      }
480 481
      else {
        //todo: log error
482
      }
483
    }
484

Pawel G's avatar
Pawel G committed
485
    return $this;
486 487
  }

488
  /**
489 490
   * Gets sitemap settings for an entity bundle, a non-bundle entity type or for
   * all entity types and their bundles.
491
   *
492 493 494
   * @param string|null $entity_type_id
   *  If set to null, sitemap settings for all entity types and their bundles
   *  are fetched.
495 496 497
   * @param string|null $bundle_name
   *
   * @return array|false
Pawel G's avatar
Pawel G committed
498 499
   *  Array of sitemap settings for an entity bundle, a non-bundle entity type
   *  or for all entity types and their bundles.
500
   *  False if entity type does not exist.
501
   */
502
  public function getBundleSettings($entity_type_id = NULL, $bundle_name = NULL) {
503
    if (NULL !== $entity_type_id) {
504 505

      // Get bundle settings saved in simple_sitemap.bundle_settings.*.* configuration.
506
      $bundle_name = NULL !== $bundle_name ? $bundle_name : $entity_type_id;
507
      $bundle_settings = $this->configFactory
508 509
        ->get("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name")
        ->get();
510 511 512 513 514

      // 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])) {
515
          self::supplementDefaultSettings('entity', $bundle_settings);
516 517 518 519 520
        }
        else {
          $bundle_settings = FALSE;
        }
      }
521
    }
522
    else {
523
      // Get all bundle settings saved in simple_sitemap.bundle_settings.*.* configuration.
Pawel G's avatar
Pawel G committed
524
      $config_names = $this->configFactory->listAll('simple_sitemap.bundle_settings.');
525
      $bundle_settings = [];
Pawel G's avatar
Pawel G committed
526
      foreach ($config_names as $config_name) {
527
        $config_name_parts = explode('.', $config_name);
528 529 530 531 532 533 534 535
        $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])) {
536
              self::supplementDefaultSettings('entity', $bundle_settings[$type_id][$bundle]);
537 538 539
            }
          }
        }
540 541
      }
    }
542
    return $bundle_settings;
543 544 545
  }

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

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

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

Pawel G's avatar
Pawel G committed
589 590
      // Save overrides for this entity if something is different.
      if ($override) {
591
        $this->db->merge('simple_sitemap_entity_overrides')
592
          ->keys([
593 594 595 596 597
            'entity_type' => $entity_type_id,
            'entity_id' => $id])
          ->fields([
            'entity_type' => $entity_type_id,
            'entity_id' => $id,
598
            'inclusion_settings' => serialize(array_merge($bundle_settings, $settings)),])
599
          ->execute();
600
      }
Pawel G's avatar
Pawel G committed
601 602
      // Else unset override.
      else {
603
        $this->removeEntityInstanceSettings($entity_type_id, $id);
604
      }
605 606 607
    }
    else {
      //todo: log error
608
    }
Pawel G's avatar
Pawel G committed
609
    return $this;
610 611
  }

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

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

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

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

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

Pawel G's avatar
Pawel G committed
719
    return $this;
720 721
  }

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

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

740
    return !empty($custom_links) ? $custom_links : [];
741 742
  }

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

Pawel G's avatar
Pawel G committed
759
  /**
760
   * Removes all custom paths from the sitemap settings.
Pawel G's avatar
Pawel G committed
761
   *
Pawel G's avatar
Pawel G committed
762 763
   * @return $this
   */
764 765 766 767 768 769 770 771 772 773 774 775 776 777
  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;
          }
        }
778 779
      }
    }
780 781 782 783
    if (!empty($save)) {
      $this->configFactory->getEditable('simple_sitemap.custom')
        ->set('links', array_values($custom_links))->save();
    }
784

Pawel G's avatar
Pawel G committed
785
    return $this;
786
  }
787
}