Simplesitemap.php 26.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 6
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
7
use Drupal\Core\Extension\ModuleHandler;
Pawel G's avatar
Pawel G committed
8
use Drupal\Core\Path\PathValidator;
Pawel G's avatar
Pawel G committed
9 10
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Datetime\DateFormatter;
11
use Drupal\Component\Datetime\Time;
12 13 14
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;
15
use Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager;
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 22
 */
class Simplesitemap {

Pawel G's avatar
Pawel G committed
23 24 25
  const DEFAULT_SITEMAP_TYPE = 'default_hreflang';
  const DEFAULT_SITEMAP_VARIANT = 'default';

Pawel G's avatar
Pawel G committed
26 27 28
  /**
   * @var \Drupal\simple_sitemap\EntityHelper
   */
29
  protected $entityHelper;
Pawel G's avatar
Pawel G committed
30 31 32 33

  /**
   * @var \Drupal\Core\Config\ConfigFactory
   */
34
  protected $configFactory;
Pawel G's avatar
Pawel G committed
35 36 37 38

  /**
   * @var \Drupal\Core\Database\Connection
   */
39
  protected $db;
Pawel G's avatar
Pawel G committed
40 41 42 43

  /**
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
44
  protected $entityTypeManager;
Pawel G's avatar
Pawel G committed
45 46 47 48

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

51 52 53 54 55
  /**
   * @var \Drupal\Core\Datetime\DateFormatter
   */
  protected $dateFormatter;

56 57 58 59 60
  /**
   * @var \Drupal\Component\Datetime\Time
   */
  protected $time;

61
  /**
62
   * @var \Drupal\Core\Extension\ModuleHandler
63
   */
64
  protected $moduleHandler;
65 66

  /**
67
   * @var \Drupal\simple_sitemap\Batch
68
   */
69
  protected $batch;
70

71 72 73 74 75
  /**
   * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager
   */
  protected $urlGeneratorManager;

76 77 78 79 80
  /**
   * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorManager
   */
  protected $sitemapGeneratorManager;

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

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

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

139
  /**
140 141
   * Returns a specific sitemap setting or a default value if setting does not
   * exist.
Pawel G's avatar
Pawel G committed
142
   *
143
   * @param string $name
Pawel G's avatar
Pawel G committed
144
   *  Name of the setting, like 'max_links'.
145 146
   *
   * @param mixed $default
Pawel G's avatar
Pawel G committed
147
   *  Value to be returned if the setting does not exist in the configuration.
148 149
   *
   * @return mixed
Pawel G's avatar
Pawel G committed
150
   *  The current setting from configuration or a default value.
Pawel G's avatar
Pawel G committed
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
Pawel G's avatar
Pawel G committed
163
   *  Setting name, like 'max_links'.
164
   * @param mixed $setting
Pawel G's avatar
Pawel G committed
165
   *  The setting to be saved.
166 167 168 169
   *
   * @return $this
   */
  public function saveSetting($name, $setting) {
Pawel G's avatar
Pawel G committed
170
    $this->configFactory->getEditable('simple_sitemap.settings')
171 172 173 174 175 176 177 178
      ->set($name, $setting)->save();
    return $this;
  }

  /**
   * Returns the whole sitemap, a requested sitemap chunk,
   * or the sitemap index file.
   *
179
   * @param string $variant
180 181
   *
   * @param int $delta
182 183
   *
   * @return string|false
Pawel G's avatar
Pawel G committed
184
   *  If no sitemap ID provided, either a sitemap index is returned, or the
185 186
   *  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
187
   *  Returns false if the sitemap is not retrievable from the database.
188
   */
Pawel G's avatar
Pawel G committed
189 190
  public function getSitemap($variant = self::DEFAULT_SITEMAP_VARIANT, $delta = NULL) {
    $chunk_info = $this->fetchSitemapVariantInfo($variant);
191

192
    if (empty($delta) || !isset($chunk_info[$delta])) {
193

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

  /**
Pawel G's avatar
Pawel G committed
214
   * Fetches info about all sitemap variants and their chunks.
215
   *
216
   * @param string|null $variant
217
   *
218
   * @return array
Pawel G's avatar
Pawel G committed
219 220
   *  An array containing all sitemap chunk IDs, deltas and creation timestamps
   * keyed by their variant ID.
221
   */
Pawel G's avatar
Pawel G committed
222
  protected function fetchSitemapVariantInfo($variant = NULL) {
223 224 225
    $query = $this->db->select('simple_sitemap', 's')
      ->fields('s', ['id', 'delta', 'sitemap_created', 'type']);

226 227
    if (NULL !== $variant) {
      $query->condition('s.type', $variant);
228 229 230 231
    }

    $result = $query->execute();

232
    return NULL === $variant ? $result->fetchAllAssoc('type') : $result->fetchAllAssoc('delta');
233 234
  }

235 236 237 238 239 240 241 242 243
  /**
   * Fetches a single sitemap chunk by ID.
   *
   * @param int $id
   *   The chunk ID.
   *
   * @return object
   *   A sitemap chunk object.
   */
244
  protected function fetchSitemapChunk($id) {
245 246 247 248
    return $this->db->query('SELECT * FROM {simple_sitemap} WHERE id = :id',
      [':id' => $id])->fetchObject();
  }

249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
  /**
   * @return array
   *
   * @todo document
   */
  public function getSitemapTypeDefinitions() {
    $type_definitions = [];
    foreach ($this->configFactory->listAll('simple_sitemap.types.') as $config_name) {
      $type_definitions[explode('.', $config_name)[2]] = $this->configFactory->get($config_name)->get();
    }

    return $type_definitions;
  }

  /**
   * @param $name
   * @param $definition
   *
   * @todo document
   */
  public function setSitemapTypeDefinition($name, $definition) {
    $type = $this->configFactory->getEditable("simple_sitemap.types.$name");
    foreach ($definition as $key => $value) {
      if (in_array($key, ['label', 'description', 'sitemap_generator', 'url_generators'])) {
        $type->set($key, $value);
      }
      else {
        //todo exception
      }
    }
    $type->save();
  }

  /**
   * @param $name
   *
   * @todo document
   */
  public function removeSitemapTypeDefinition($name) {
Pawel G's avatar
Pawel G committed
288
    if ($name !== self::DEFAULT_SITEMAP_TYPE) {
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
      $this->configFactory->getEditable("simple_sitemap.types.$name")->delete();
    }
    else {
      //todo: exception
    }
  }

  /**
   * @return array
   *
   * @todo document
   * @todo translate label
   */
  public function getSitemapVariants() {
    return $this->configFactory->get('simple_sitemap.variants')->get('variants');
  }

  /**
   * @param $name
   * @param $definition
   * @return $this
   *
   * @todo document
   * @todo exceptions
   */
  public function addSitemapVariant($name, $definition) {
    if (empty($definition['label'])) {
Pawel G's avatar
Pawel G committed
316
      $definition['label'] = $name;
317 318 319
    }

    if (empty($definition['type'])) {
Pawel G's avatar
Pawel G committed
320
      $definition['type'] = self::DEFAULT_SITEMAP_TYPE;
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
    }

    $variants = array_merge($this->getSitemapVariants(), [$name => ['label' => $definition['label'], 'type' => $definition['type']]]);
    $this->configFactory->getEditable('simple_sitemap.variants')
      ->set('variants', $variants)->save();

    return $this;
  }

  /**
   * @param $name
   * @return $this
   *
   * @todo document
   */
  public function removeSitemapVariant($name) {
    $variants = $this->getSitemapVariants();
Pawel G's avatar
Pawel G committed
338 339 340 341 342
    if (isset($variants[$name])) {
      unset($variants[$name]);
      $this->configFactory->getEditable('simple_sitemap.variants')
        ->set('variants', $variants)->save();
    }
343 344 345 346

    return $this;
  }

347
  /**
Pawel G's avatar
Pawel G committed
348
   * Generates the XML sitemap and saves it to the database.
349 350
   *
   * @param string $from
Pawel G's avatar
Pawel G committed
351 352
   *  Can be 'form', 'backend', 'drush' or 'nobatch'.
   *  This decides how the batch process is to be run.
Pawel G's avatar
Pawel G committed
353
   *
354
   * @param array|string|null $variants
355
   *
Pawel G's avatar
Pawel G committed
356
   * @return bool|\Drupal\simple_sitemap\Simplesitemap
357
   */
358 359
  public function generateSitemap($from = 'form', $variants = NULL) {
    $variants = NULL === $variants ? NULL : (array) $variants;
360

361
    $settings = [
362
      'base_url' => $this->getSetting('base_url', ''),
363
      'batch_process_limit' => $this->getSetting('batch_process_limit', 1500),
364 365 366 367
      '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', []),
368
    ];
369

370 371
    $this->batch->setBatchMeta(['from' => $from]);

372
    $operations = [];
Pawel G's avatar
Pawel G committed
373

374 375
    $type_definitions = $this->getSitemapTypeDefinitions();
    $this->moduleHandler->alter('simple_sitemap_types', $type_definitions);
Pawel G's avatar
Pawel G committed
376

377 378
    $sitemap_variants = $this->getSitemapVariants();
    $this->moduleHandler->alter('simple_sitemap_variants', $sitemap_variants);
379

380 381 382 383 384 385
    foreach ($sitemap_variants as $variant_name => $variant_definition) {

      // Skipping unwanted sitemap variants.
      if (NULL !== $variants && !in_array($variant_name, $variants)) {
        continue;
      }
386

387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
      $type = $variant_definition['type'];

      // Adding a remove_sitemap operation for all sitemap variants.
      $operations[] = [
        'operation' => 'removeSitemap',
        'arguments' => [
          'sitemap_generator' => $type_definitions[$type]['sitemap_generator'],
          'variant' => $variant_name,
        ]
      ];

      // Adding generate_sitemap operations for all data sets.
      foreach ($type_definitions[$type]['url_generators'] as $url_generator_id) {
        foreach ($this->urlGeneratorManager->createInstance($url_generator_id)
                   ->setSitemapVariant($variant_name)
                   ->getDataSets() as $data_set) {
          if (!empty($data_set)) {
            $operations[] = [
              'operation' => 'generateSitemap',
406
              'arguments' => [
407 408 409 410 411 412
                'url_generator' => $url_generator_id,
                'data_set' => $data_set,
                'variant' => $variant_name,
                'sitemap_generator' => $type_definitions[$type]['sitemap_generator'],
                'settings' => $settings,
              ],
413 414
            ];
          }
415
        }
416
      }
417

418 419
      // Adding generate_index operations for all sitemap variants.
      $operations[] = [
420 421
        'operation' => 'generateIndex',
        'arguments' => [
422 423
          'sitemap_generator' => $type_definitions[$type]['sitemap_generator'],
          'variant' => $variant_name,
424 425 426 427 428
          'settings' => $settings,
        ],
      ];
    }

429 430 431 432
    // Adding operations to and starting batch.
    if (!empty($operations)) {
      foreach ($operations as $operation_data) {
        $this->batch->addOperation($operation_data['operation'], $operation_data['arguments']);
433 434 435 436 437
      }
      $success = $this->batch->start();
    }

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

440
  /**
441
   * @param null|array $variant
Pawel G's avatar
Pawel G committed
442
   *
443 444
   * @todo Add removeSitemap API method.
   */
445
  public function removeSitemap($variant = NULL) {
Pawel G's avatar
Pawel G committed
446 447

  }
448

449 450 451
  /**
   * Returns a 'time ago' string of last timestamp generation.
   *
452
   * @param string|null $variant
453 454
   *
   * @return string|array|false
Pawel G's avatar
Pawel G committed
455
   *  Formatted timestamp of last sitemap generation, otherwise FALSE.
456
   */
457
  public function getGeneratedAgo($variant = NULL) {
Pawel G's avatar
Pawel G committed
458
    $chunks = $this->fetchSitemapVariantInfo($variant);
459
    if ($variant !== NULL) {
460
      return isset($chunks[DefaultSitemapGenerator::FIRST_CHUNK_DELTA]->sitemap_created)
461
        ? $this->dateFormatter
462
          ->formatInterval($this->time->getRequestTime() - $chunks[DefaultSitemapGenerator::FIRST_CHUNK_DELTA]
463 464 465 466 467 468 469 470 471 472 473 474
              ->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;
475 476 477
    }
  }

478 479
  /**
   * Enables sitemap support for an entity type. Enabled entity types show
480 481
   * sitemap settings on their bundle setting forms. If an enabled entity type
   * features bundles (e.g. 'node'), it needs to be set up with
482 483 484
   * setBundleSettings() as well.
   *
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
485
   *  Entity type id like 'node'.
486
   *
Pawel G's avatar
Pawel G committed
487
   * @return $this
488 489
   */
  public function enableEntityType($entity_type_id) {
490 491 492 493
    $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);
494
    }
Pawel G's avatar
Pawel G committed
495
    return $this;
496 497 498 499 500 501 502 503
  }

  /**
   * 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
504
   *  Entity type id like 'node'.
505
   *
Pawel G's avatar
Pawel G committed
506
   * @return $this
507 508
   */
  public function disableEntityType($entity_type_id) {
509 510 511

    // Updating settings.
    $enabled_entity_types = $this->getSetting('enabled_entity_types');
512
    if (FALSE !== ($key = array_search($entity_type_id, $enabled_entity_types))) {
513
      unset ($enabled_entity_types[$key]);
514
      $this->saveSetting('enabled_entity_types', array_values($enabled_entity_types));
515 516 517
    }

    // Deleting inclusion settings.
518
    $config_names = $this->configFactory->listAll("simple_sitemap.bundle_settings.$entity_type_id.");
Pawel G's avatar
Pawel G committed
519
    foreach ($config_names as $config_name) {
520
      $this->configFactory->getEditable($config_name)->delete();
521
    }
522 523 524

    // Deleting entity overrides.
    $this->removeEntityInstanceSettings($entity_type_id);
Pawel G's avatar
Pawel G committed
525
    return $this;
526 527 528
  }

  /**
Pawel G's avatar
Pawel G committed
529
   * Sets sitemap settings for a non-bundle entity type (e.g. user) or a bundle
530 531 532
   * of an entity type (e.g. page).
   *
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
533
   *  Entity type id like 'node' the bundle belongs to.
534
   * @param string $bundle_name
Pawel G's avatar
Pawel G committed
535
   *  Name of the bundle. NULL if entity type has no bundles.
536
   * @param array $settings
Pawel G's avatar
Pawel G committed
537 538
   *  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
539 540
   *
   * @return $this
541 542
   *
   * @todo: enableEntityType automatically
543
   */
544
  public function setBundleSettings($entity_type_id, $bundle_name = NULL, $settings = []) {
545
    $bundle_name = NULL !== $bundle_name ? $bundle_name : $entity_type_id;
546

Pawel G's avatar
Pawel G committed
547 548 549 550 551 552
    if (!empty($old_settings = $this->getBundleSettings($entity_type_id, $bundle_name))) {
      $settings = array_merge($old_settings, $settings);
    }
    else {
      self::supplementDefaultSettings('entity', $settings);
    }
553 554 555

    $bundle_settings = $this->configFactory
      ->getEditable("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name");
Pawel G's avatar
Pawel G committed
556
    foreach ($settings as $setting_key => $setting) {
557
      if ($setting_key === 'index') {
Pawel G's avatar
Pawel G committed
558
        $setting = (int) $setting;
559
      }
560
      $bundle_settings->set($setting_key, $setting);
561
    }
562
    $bundle_settings->save();
563 564

    // Delete entity overrides which are identical to new bundle setting.
Pawel G's avatar
Pawel G committed
565
    $sitemap_entity_types = $this->entityHelper->getSupportedEntityTypes();
566 567 568
    if (isset($sitemap_entity_types[$entity_type_id])) {
      $entity_type = $sitemap_entity_types[$entity_type_id];
      $keys = $entity_type->getKeys();
569 570

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

573
      $query = $this->entityTypeManager->getStorage($entity_type_id)->getQuery();
574
      if (!$this->entityHelper->entityTypeIsAtomic($entity_type_id)) {
575
        $query->condition($keys['bundle'], $bundle_name);
576
      }
577 578 579 580 581 582 583 584 585
      $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');
      }

586
      $delete_instances = [];
Pawel G's avatar
Pawel G committed
587
      foreach ($query->execute()->fetchAll() as $result) {
588 589 590
        $delete = TRUE;
        $instance_settings = unserialize($result->inclusion_settings);
        foreach ($instance_settings as $setting_key => $instance_setting) {
591
          if ($instance_setting != $settings[$setting_key]) {
592 593 594 595 596
            $delete = FALSE;
            break;
          }
        }
        if ($delete) {
597
          $delete_instances[] = $result->id;
598
        }
599
      }
600 601 602 603 604
      if (!empty($delete_instances)) {
        $this->db->delete('simple_sitemap_entity_overrides')
          ->condition('id', $delete_instances, 'IN')
          ->execute();
      }
605
    }
606 607 608
    else {
      //todo: log error
    }
Pawel G's avatar
Pawel G committed
609
    return $this;
610 611
  }

612
  /**
613 614
   * Gets sitemap settings for an entity bundle, a non-bundle entity type or for
   * all entity types and their bundles.
615
   *
616 617 618
   * @param string|null $entity_type_id
   *  If set to null, sitemap settings for all entity types and their bundles
   *  are fetched.
619 620 621
   * @param string|null $bundle_name
   *
   * @return array|false
Pawel G's avatar
Pawel G committed
622 623
   *  Array of sitemap settings for an entity bundle, a non-bundle entity type
   *  or for all entity types and their bundles.
624
   *  False if entity type does not exist.
625 626
   *
   * @todo May want to return default settings in case bundle of enabled entity type does not have its bundle_settings.*.* configuration file.
627
   */
628
  public function getBundleSettings($entity_type_id = NULL, $bundle_name = NULL) {
629
    if (NULL !== $entity_type_id) {
630
      $bundle_name = NULL !== $bundle_name ? $bundle_name : $entity_type_id;
631
      $bundle_settings = $this->configFactory
632 633
        ->get("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name")
        ->get();
634
      $bundle_settings = !empty($bundle_settings) ? $bundle_settings : FALSE;
635
    }
636
    else {
Pawel G's avatar
Pawel G committed
637
      $config_names = $this->configFactory->listAll('simple_sitemap.bundle_settings.');
638
      $all_settings = [];
Pawel G's avatar
Pawel G committed
639
      foreach ($config_names as $config_name) {
640
        $config_name_parts = explode('.', $config_name);
641
        $all_settings[$config_name_parts[2]][$config_name_parts[3]] = $this->configFactory->get($config_name)->get();
642
      }
643
      $bundle_settings = $all_settings;
644
    }
645
    return $bundle_settings;
646 647 648
  }

  /**
649 650
   * Supplements all missing link setting with default values.
   *
651
   * @param string $type
Pawel G's avatar
Pawel G committed
652 653 654
   *  'entity'|'custom'
   * @param array &$settings
   * @param array $overrides
655
   */
Pawel G's avatar
Pawel G committed
656
  public static function supplementDefaultSettings($type, &$settings, $overrides = []) {
657
    foreach (self::$allowedLinkSettings[$type] as $allowed_link_setting) {
658
      if (!isset($settings[$allowed_link_setting])
Pawel G's avatar
Pawel G committed
659
        && isset(self::$linkSettingDefaults[$allowed_link_setting])) {
660 661 662
        $settings[$allowed_link_setting] = isset($overrides[$allowed_link_setting])
          ? $overrides[$allowed_link_setting]
          : self::$linkSettingDefaults[$allowed_link_setting];
663 664
      }
    }
665 666
  }

Pawel G's avatar
Pawel G committed
667 668 669
  /**
   * Overrides entity bundle/entity type sitemap settings for a single entity.
   *
Pawel G's avatar
Pawel G committed
670 671 672
   * @param string $entity_type_id
   * @param int $id
   * @param array $settings
Pawel G's avatar
Pawel G committed
673
   *
Pawel G's avatar
Pawel G committed
674 675
   * @return $this
   */
676
  public function setEntityInstanceSettings($entity_type_id, $id, $settings) {
677
    $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($id);
678 679 680
    $bundle_settings = $this->getBundleSettings(
      $entity_type_id, $this->entityHelper->getEntityInstanceBundleName($entity)
    );
681
    if (!empty($bundle_settings)) {
682 683 684 685

      // Check if overrides are different from bundle setting before saving.
      $override = FALSE;
      foreach ($settings as $key => $setting) {
686
        if (!isset($bundle_settings[$key]) || $setting != $bundle_settings[$key]) {
687 688 689 690
          $override = TRUE;
          break;
        }
      }
Pawel G's avatar
Pawel G committed
691 692
      // Save overrides for this entity if something is different.
      if ($override) {
693 694 695 696 697 698 699
        $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,
700
            'inclusion_settings' => serialize(array_merge($bundle_settings, $settings)),])
701
          ->execute();
702
      }
Pawel G's avatar
Pawel G committed
703 704
      // Else unset override.
      else {
705
        $this->removeEntityInstanceSettings($entity_type_id, $id);
706
      }
707 708 709
    }
    else {
      //todo: log error
710
    }
Pawel G's avatar
Pawel G committed
711
    return $this;
712 713
  }

Pawel G's avatar
Pawel G committed
714
  /**
715
   * Gets sitemap settings for an entity instance which overrides the sitemap
716
   * settings of its bundle, or bundle settings, if they are not overridden.
Pawel G's avatar
Pawel G committed
717
   *
Pawel G's avatar
Pawel G committed
718
   * @param string $entity_type_id
719
   * @param int $id
Pawel G's avatar
Pawel G committed
720
   *
721
   * @return array|false
Pawel G's avatar
Pawel G committed
722
   */
723 724 725 726 727 728 729 730 731
  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)) {
732
      return unserialize($results);
733 734
    }
    else {
735 736 737 738 739 740
      $entity = $this->entityTypeManager->getStorage($entity_type_id)
        ->load($id);
      return $this->getBundleSettings(
        $entity_type_id,
        $this->entityHelper->getEntityInstanceBundleName($entity)
      );
741 742 743
    }
  }

744 745 746 747 748 749 750 751 752 753 754 755
  /**
   * 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);
756
    if (NULL !== $entity_ids) {
757 758 759 760 761 762 763
      $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
764 765 766 767
  /**
   * Checks if an entity bundle (or a non-bundle entity type) is set to be
   * indexed in the sitemap settings.
   *
768 769
   * @param string $entity_type_id
   * @param string|null $bundle_name
Pawel G's avatar
Pawel G committed
770
   *
Pawel G's avatar
Pawel G committed
771 772
   * @return bool
   */
773
  public function bundleIsIndexed($entity_type_id, $bundle_name = NULL) {
774 775 776 777
    $settings = $this->getBundleSettings($entity_type_id, $bundle_name);
    return !empty($settings['index']);
  }

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

Pawel G's avatar
Pawel G committed
789
  /**
790
   * Stores a custom path along with its sitemap settings to configuration.
Pawel G's avatar
Pawel G committed
791
   *
Pawel G's avatar
Pawel G committed
792 793
   * @param string $path
   * @param array $settings
Pawel G's avatar
Pawel G committed
794
   *
Pawel G's avatar
Pawel G committed
795
   * @return $this
796 797
   *
   * @todo Validate $settings and throw exceptions
Pawel G's avatar
Pawel G committed
798
   */
799
  public function addCustomLink($path, $settings = []) {
Pawel G's avatar
Pawel G committed
800 801 802 803
    if (!$this->pathValidator->isValid($path)) {
      // todo: log error.
      return $this;
    }
Pawel G's avatar
Pawel G committed
804
    if ($path[0] !== '/') {
Pawel G's avatar
Pawel G committed
805 806 807
      // todo: log error.
      return $this;
    }
Pawel G's avatar
Pawel G committed
808

809
    $custom_links = $this->getCustomLinks(FALSE);
Pawel G's avatar
Pawel G committed
810
    foreach ($custom_links as $key => $link) {
811
      if ($link['path'] === $path) {
812 813 814 815 816
        $link_key = $key;
        break;
      }
    }
    $link_key = isset($link_key) ? $link_key : count($custom_links);
817
    $custom_links[$link_key] = ['path' => $path] + $settings;
Pawel G's avatar
Pawel G committed
818
    $this->configFactory->getEditable('simple_sitemap.custom')
819
      ->set('links', $custom_links)->save();
Pawel G's avatar
Pawel G committed
820
    return $this;
821 822
  }

823 824 825
  /**
   * Returns an array of custom paths and their sitemap settings.
   *
826
   * @param bool $supplement_default_settings
827 828
   * @return array
   */
829
  public function getCustomLinks($supplement_default_settings = TRUE) {
830 831
    $custom_links = $this->configFactory
      ->get('simple_sitemap.custom')
832
      ->get('links');
833 834 835

    if ($supplement_default_settings) {
      foreach ($custom_links as $i => $link_settings) {
Pawel G's avatar
Pawel G committed
836 837
        self::supplementDefaultSettings('custom', $link_settings);
        $custom_links[$i] = $link_settings;
838 839 840
      }
    }

841
    return $custom_links !== NULL ? $custom_links : [];
842 843
  }

Pawel G's avatar
Pawel G committed
844 845 846
  /**
   * Returns settings for a custom path added to the sitemap settings.
   *
Pawel G's avatar
Pawel G committed
847
   * @param string $path
Pawel G's avatar
Pawel G committed
848
   *
Pawel G's avatar
Pawel G committed
849
   * @return array|false
Pawel G's avatar
Pawel G committed
850
   */
851
  public function getCustomLink($path) {
852
    foreach ($this->getCustomLinks() as $key => $link) {
853
      if ($link['path'] === $path) {
854
        return $link;
855 856 857 858 859
      }
    }
    return FALSE;
  }

Pawel G's avatar
Pawel G committed
860 861 862
  /**
   * Removes a custom path from the sitemap settings.
   *
Pawel G's avatar
Pawel G committed
863
   * @param string $path
Pawel G's avatar
Pawel G committed
864
   *
Pawel G's avatar
Pawel G committed
865 866
   * @return $this
   */
867
  public function removeCustomLink($path) {
868
    $custom_links = $this->getCustomLinks(FALSE);
Pawel G's avatar
Pawel G committed
869
    foreach ($custom_links as $key => $link) {
870
      if ($link['path'] === $path) {
871 872
        unset($custom_links[$key]);
        $custom_links = array_values($custom_links);
Pawel G's avatar
Pawel G committed
873
        $this->configFactory->getEditable('simple_sitemap.custom')
874
          ->set('links', $custom_links)->save();
875
        break;
876 877
      }
    }
Pawel G's avatar
Pawel G committed
878
    return $this;
879 880
  }

Pawel G's avatar
Pawel G committed
881 882
  /**
   * Removes all custom paths from the sitemap settings.
Pawel G's avatar
Pawel G committed
883 884
   *
   * @return $this
Pawel G's avatar
Pawel G committed
885
   */
886
  public function removeCustomLinks() {
Pawel G's avatar
Pawel G committed
887
    $this->configFactory->getEditable('simple_sitemap.custom')
888
      ->set('links', [])->save();
Pawel G's avatar
Pawel G committed
889
    return $this;
890
  }
891
}