Simplesitemap.php 28.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
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;
Pawel G's avatar
Pawel G committed
9
use Drupal\Core\Path\PathValidator;
Pawel G's avatar
Pawel G committed
10 11
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Datetime\DateFormatter;
12
use Drupal\Component\Datetime\Time;
13 14 15
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;
16
use Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager;
17

18
/**
Pawel G's avatar
Pawel G committed
19
 * Class Simplesitemap
Pawel G's avatar
Pawel G committed
20
 * @package Drupal\simple_sitemap
21 22 23
 */
class Simplesitemap {

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

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

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

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

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

47 48 49 50 51
  /**
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
   */
  protected $entityTypeBundleInfo;

Pawel G's avatar
Pawel G committed
52 53 54
  /**
   * @var \Drupal\Core\Path\PathValidator
   */
55
  protected $pathValidator;
Pawel G's avatar
Pawel G committed
56

57 58 59 60 61
  /**
   * @var \Drupal\Core\Datetime\DateFormatter
   */
  protected $dateFormatter;

62 63 64 65 66
  /**
   * @var \Drupal\Component\Datetime\Time
   */
  protected $time;

67
  /**
68
   * @var \Drupal\Core\Extension\ModuleHandler
69
   */
70
  protected $moduleHandler;
71 72

  /**
73
   * @var \Drupal\simple_sitemap\Batch
74
   */
75
  protected $batch;
76

77 78 79 80 81
  /**
   * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager
   */
  protected $urlGeneratorManager;

82 83 84 85 86
  /**
   * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorManager
   */
  protected $sitemapGeneratorManager;

Pawel G's avatar
Pawel G committed
87 88 89
  /**
   * @var array
   */
Pawel G's avatar
Pawel G committed
90
  protected static $allowedLinkSettings = [
91
    'entity' => ['index', 'priority', 'changefreq', 'include_images'],
92 93 94
    'custom' => ['priority', 'changefreq'],
  ];

Pawel G's avatar
Pawel G committed
95 96 97 98
  /**
   * @var array
   */
  protected static $linkSettingDefaults = [
99
    'index' => FALSE,
100
    'priority' => 0.5,
101
    'changefreq' => '',
102
    'include_images' => FALSE,
103
  ];
104

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

148
  /**
149 150
   * Returns a specific sitemap setting or a default value if setting does not
   * exist.
Pawel G's avatar
Pawel G committed
151
   *
152
   * @param string $name
Pawel G's avatar
Pawel G committed
153
   *  Name of the setting, like 'max_links'.
154 155
   *
   * @param mixed $default
Pawel G's avatar
Pawel G committed
156
   *  Value to be returned if the setting does not exist in the configuration.
157 158
   *
   * @return mixed
Pawel G's avatar
Pawel G committed
159
   *  The current setting from configuration or a default value.
Pawel G's avatar
Pawel G committed
160
   */
161 162 163 164 165 166 167 168 169 170 171
  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
172
   *  Setting name, like 'max_links'.
173
   * @param mixed $setting
Pawel G's avatar
Pawel G committed
174
   *  The setting to be saved.
175 176 177 178
   *
   * @return $this
   */
  public function saveSetting($name, $setting) {
Pawel G's avatar
Pawel G committed
179
    $this->configFactory->getEditable('simple_sitemap.settings')
180 181 182 183 184 185 186 187
      ->set($name, $setting)->save();
    return $this;
  }

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

201
    if (empty($delta) || !isset($chunk_info[$delta])) {
202

203
      if (isset($chunk_info[SitemapGeneratorBase::INDEX_DELTA])) {
Pawel G's avatar
Pawel G committed
204
        // Return sitemap index if one exists.
205 206
        return $this->fetchSitemapChunk($chunk_info[SitemapGeneratorBase::INDEX_DELTA]->id)
          ->sitemap_string;
207 208
      }
      else {
Pawel G's avatar
Pawel G committed
209
        // Return sitemap chunk if there is only one chunk.
210 211
        return isset($chunk_info[SitemapGeneratorBase::FIRST_CHUNK_DELTA])
          ? $this->fetchSitemapChunk($chunk_info[SitemapGeneratorBase::FIRST_CHUNK_DELTA]->id)
212 213 214 215 216 217
            ->sitemap_string
          : FALSE;
      }
    }
    else {
      // Return specific sitemap chunk.
218
      return $this->fetchSitemapChunk($chunk_info[$delta]->id)->sitemap_string;
219 220 221 222
    }
  }

  /**
Pawel G's avatar
Pawel G committed
223
   * Fetches info about all sitemap variants and their chunks.
224
   *
225
   * @param string|null $variant
226
   *
227
   * @return array
Pawel G's avatar
Pawel G committed
228 229
   *  An array containing all sitemap chunk IDs, deltas and creation timestamps
   * keyed by their variant ID.
230
   */
Pawel G's avatar
Pawel G committed
231
  protected function fetchSitemapVariantInfo($variant = NULL) {
232 233 234
    $query = $this->db->select('simple_sitemap', 's')
      ->fields('s', ['id', 'delta', 'sitemap_created', 'type']);

235 236
    if (NULL !== $variant) {
      $query->condition('s.type', $variant);
237 238 239 240
    }

    $result = $query->execute();

241
    return NULL === $variant ? $result->fetchAllAssoc('type') : $result->fetchAllAssoc('delta');
242 243
  }

244 245 246 247 248 249 250 251 252
  /**
   * Fetches a single sitemap chunk by ID.
   *
   * @param int $id
   *   The chunk ID.
   *
   * @return object
   *   A sitemap chunk object.
   */
253
  protected function fetchSitemapChunk($id) {
254 255 256 257
    return $this->db->query('SELECT * FROM {simple_sitemap} WHERE id = :id',
      [':id' => $id])->fetchObject();
  }

258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
  /**
   * @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
Pawel G's avatar
Pawel G committed
275
   * @return $this
276 277 278 279 280 281 282 283 284 285 286 287 288 289
   *
   * @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();
Pawel G's avatar
Pawel G committed
290 291

    return $this;
292 293 294 295
  }

  /**
   * @param $name
Pawel G's avatar
Pawel G committed
296
   * @return $this
297 298 299 300
   *
   * @todo document
   */
  public function removeSitemapTypeDefinition($name) {
Pawel G's avatar
Pawel G committed
301
    if ($name !== self::DEFAULT_SITEMAP_TYPE) {
302 303 304 305 306
      $this->configFactory->getEditable("simple_sitemap.types.$name")->delete();
    }
    else {
      //todo: exception
    }
Pawel G's avatar
Pawel G committed
307 308

    return $this;
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
  }

  /**
   * @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
331
      $definition['label'] = $name;
332 333 334
    }

    if (empty($definition['type'])) {
Pawel G's avatar
Pawel G committed
335
      $definition['type'] = self::DEFAULT_SITEMAP_TYPE;
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
    }

    $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
353 354 355 356 357
    if (isset($variants[$name])) {
      unset($variants[$name]);
      $this->configFactory->getEditable('simple_sitemap.variants')
        ->set('variants', $variants)->save();
    }
358 359 360 361

    return $this;
  }

362
  /**
Pawel G's avatar
Pawel G committed
363
   * Generates the XML sitemap and saves it to the database.
364 365
   *
   * @param string $from
Pawel G's avatar
Pawel G committed
366 367
   *  Can be 'form', 'backend', 'drush' or 'nobatch'.
   *  This decides how the batch process is to be run.
Pawel G's avatar
Pawel G committed
368
   *
369
   * @param array|string|null $variants
370
   *
Pawel G's avatar
Pawel G committed
371
   * @return bool|\Drupal\simple_sitemap\Simplesitemap
372
   */
373 374
  public function generateSitemap($from = 'form', $variants = NULL) {
    $variants = NULL === $variants ? NULL : (array) $variants;
375

376
    $settings = [
377
      'base_url' => $this->getSetting('base_url', ''),
378
      'batch_process_limit' => $this->getSetting('batch_process_limit', 1500),
379 380 381 382
      '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', []),
383
    ];
384

385 386
    $this->batch->setBatchMeta(['from' => $from]);

387
    $operations = [];
Pawel G's avatar
Pawel G committed
388

389 390
    $type_definitions = $this->getSitemapTypeDefinitions();
    $this->moduleHandler->alter('simple_sitemap_types', $type_definitions);
Pawel G's avatar
Pawel G committed
391

392 393
    $sitemap_variants = $this->getSitemapVariants();
    $this->moduleHandler->alter('simple_sitemap_variants', $sitemap_variants);
394

395 396 397 398 399 400
    foreach ($sitemap_variants as $variant_name => $variant_definition) {

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

402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
      $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',
421
              'arguments' => [
422 423 424 425 426 427
                'url_generator' => $url_generator_id,
                'data_set' => $data_set,
                'variant' => $variant_name,
                'sitemap_generator' => $type_definitions[$type]['sitemap_generator'],
                'settings' => $settings,
              ],
428 429
            ];
          }
430
        }
431
      }
432

433 434
      // Adding generate_index operations for all sitemap variants.
      $operations[] = [
435 436
        'operation' => 'generateIndex',
        'arguments' => [
437 438
          'sitemap_generator' => $type_definitions[$type]['sitemap_generator'],
          'variant' => $variant_name,
439 440 441 442 443
          'settings' => $settings,
        ],
      ];
    }

444 445 446 447
    // Adding operations to and starting batch.
    if (!empty($operations)) {
      foreach ($operations as $operation_data) {
        $this->batch->addOperation($operation_data['operation'], $operation_data['arguments']);
448 449 450 451 452
      }
      $success = $this->batch->start();
    }

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

455
  /**
Pawel G's avatar
Pawel G committed
456 457
   * @param null $variant
   * @return $this
Pawel G's avatar
Pawel G committed
458
   *
Pawel G's avatar
Pawel G committed
459
   * @todo implement
460
   */
461
  public function removeSitemap($variant = NULL) {
Pawel G's avatar
Pawel G committed
462
    return $this;
Pawel G's avatar
Pawel G committed
463
  }
464

465 466 467
  /**
   * Returns a 'time ago' string of last timestamp generation.
   *
468
   * @param string|null $variant
469 470
   *
   * @return string|array|false
Pawel G's avatar
Pawel G committed
471
   *  Formatted timestamp of last sitemap generation, otherwise FALSE.
472
   */
473
  public function getGeneratedAgo($variant = NULL) {
Pawel G's avatar
Pawel G committed
474
    $chunks = $this->fetchSitemapVariantInfo($variant);
475
    if ($variant !== NULL) {
476
      return isset($chunks[DefaultSitemapGenerator::FIRST_CHUNK_DELTA]->sitemap_created)
477
        ? $this->dateFormatter
478
          ->formatInterval($this->time->getRequestTime() - $chunks[DefaultSitemapGenerator::FIRST_CHUNK_DELTA]
479 480 481 482 483 484 485 486 487 488 489 490
              ->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;
491 492 493
    }
  }

494 495
  /**
   * Enables sitemap support for an entity type. Enabled entity types show
496 497
   * sitemap settings on their bundle setting forms. If an enabled entity type
   * features bundles (e.g. 'node'), it needs to be set up with
498 499 500
   * setBundleSettings() as well.
   *
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
501
   *  Entity type id like 'node'.
502
   *
Pawel G's avatar
Pawel G committed
503
   * @return $this
504 505
   */
  public function enableEntityType($entity_type_id) {
506 507 508 509
    $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);
510
    }
Pawel G's avatar
Pawel G committed
511
    return $this;
512 513 514 515 516 517 518 519
  }

  /**
   * 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
520
   *  Entity type id like 'node'.
521
   *
Pawel G's avatar
Pawel G committed
522
   * @return $this
523 524
   */
  public function disableEntityType($entity_type_id) {
525 526 527

    // Updating settings.
    $enabled_entity_types = $this->getSetting('enabled_entity_types');
528
    if (FALSE !== ($key = array_search($entity_type_id, $enabled_entity_types))) {
529
      unset ($enabled_entity_types[$key]);
530
      $this->saveSetting('enabled_entity_types', array_values($enabled_entity_types));
531 532 533
    }

    // Deleting inclusion settings.
534
    $config_names = $this->configFactory->listAll("simple_sitemap.bundle_settings.$entity_type_id.");
Pawel G's avatar
Pawel G committed
535
    foreach ($config_names as $config_name) {
536
      $this->configFactory->getEditable($config_name)->delete();
537
    }
538 539 540

    // Deleting entity overrides.
    $this->removeEntityInstanceSettings($entity_type_id);
Pawel G's avatar
Pawel G committed
541
    return $this;
542 543 544
  }

  /**
Pawel G's avatar
Pawel G committed
545
   * Sets sitemap settings for a non-bundle entity type (e.g. user) or a bundle
546 547 548
   * of an entity type (e.g. page).
   *
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
549
   *  Entity type id like 'node' the bundle belongs to.
550
   * @param string $bundle_name
Pawel G's avatar
Pawel G committed
551
   *  Name of the bundle. NULL if entity type has no bundles.
552
   * @param array $settings
Pawel G's avatar
Pawel G committed
553 554
   *  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
555 556
   *
   * @return $this
557 558
   *
   * @todo: enableEntityType automatically
559
   */
560
  public function setBundleSettings($entity_type_id, $bundle_name = NULL, $settings = ['index' => TRUE]) {
561
    $bundle_name = NULL !== $bundle_name ? $bundle_name : $entity_type_id;
562

Pawel G's avatar
Pawel G committed
563 564 565 566 567 568
    if (!empty($old_settings = $this->getBundleSettings($entity_type_id, $bundle_name))) {
      $settings = array_merge($old_settings, $settings);
    }
    else {
      self::supplementDefaultSettings('entity', $settings);
    }
569 570 571

    $bundle_settings = $this->configFactory
      ->getEditable("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name");
Pawel G's avatar
Pawel G committed
572
    foreach ($settings as $setting_key => $setting) {
573
      $bundle_settings->set($setting_key, $setting);
574
    }
575
    $bundle_settings->save();
576 577

    // Delete entity overrides which are identical to new bundle setting.
Pawel G's avatar
Pawel G committed
578
    $sitemap_entity_types = $this->entityHelper->getSupportedEntityTypes();
579 580 581
    if (isset($sitemap_entity_types[$entity_type_id])) {
      $entity_type = $sitemap_entity_types[$entity_type_id];
      $keys = $entity_type->getKeys();
582 583

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

586
      $query = $this->entityTypeManager->getStorage($entity_type_id)->getQuery();
587
      if (!$this->entityHelper->entityTypeIsAtomic($entity_type_id)) {
588
        $query->condition($keys['bundle'], $bundle_name);
589
      }
590 591 592 593 594 595 596 597 598
      $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');
      }

599
      $delete_instances = [];
Pawel G's avatar
Pawel G committed
600
      foreach ($query->execute()->fetchAll() as $result) {
601 602 603
        $delete = TRUE;
        $instance_settings = unserialize($result->inclusion_settings);
        foreach ($instance_settings as $setting_key => $instance_setting) {
604
          if ($instance_setting != $settings[$setting_key]) {
605 606 607 608 609
            $delete = FALSE;
            break;
          }
        }
        if ($delete) {
610
          $delete_instances[] = $result->id;
611
        }
612
      }
613 614 615 616 617
      if (!empty($delete_instances)) {
        $this->db->delete('simple_sitemap_entity_overrides')
          ->condition('id', $delete_instances, 'IN')
          ->execute();
      }
618
    }
619 620 621
    else {
      //todo: log error
    }
Pawel G's avatar
Pawel G committed
622
    return $this;
623 624
  }

625
  /**
626 627
   * Gets sitemap settings for an entity bundle, a non-bundle entity type or for
   * all entity types and their bundles.
628
   *
629 630 631
   * @param string|null $entity_type_id
   *  If set to null, sitemap settings for all entity types and their bundles
   *  are fetched.
632 633 634
   * @param string|null $bundle_name
   *
   * @return array|false
Pawel G's avatar
Pawel G committed
635 636
   *  Array of sitemap settings for an entity bundle, a non-bundle entity type
   *  or for all entity types and their bundles.
637
   *  False if entity type does not exist.
638
   */
639
  public function getBundleSettings($entity_type_id = NULL, $bundle_name = NULL) {
640
    if (NULL !== $entity_type_id) {
641 642

      // Get bundle settings saved in simple_sitemap.bundle_settings.*.* configuration.
643
      $bundle_name = NULL !== $bundle_name ? $bundle_name : $entity_type_id;
644
      $bundle_settings = $this->configFactory
645 646
        ->get("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name")
        ->get();
647 648 649 650 651

      // 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])) {
652
          self::supplementDefaultSettings('entity', $bundle_settings);
653 654 655 656 657
        }
        else {
          $bundle_settings = FALSE;
        }
      }
658
    }
659
    else {
660
      // Get all bundle settings saved in simple_sitemap.bundle_settings.*.* configuration.
Pawel G's avatar
Pawel G committed
661
      $config_names = $this->configFactory->listAll('simple_sitemap.bundle_settings.');
662
      $bundle_settings = [];
Pawel G's avatar
Pawel G committed
663
      foreach ($config_names as $config_name) {
664
        $config_name_parts = explode('.', $config_name);
665 666 667 668 669 670 671 672
        $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])) {
673
              self::supplementDefaultSettings('entity', $bundle_settings[$type_id][$bundle]);
674 675 676
            }
          }
        }
677 678
      }
    }
679
    return $bundle_settings;
680 681 682
  }

  /**
683 684
   * Supplements all missing link setting with default values.
   *
685
   * @param string $type
Pawel G's avatar
Pawel G committed
686 687 688
   *  'entity'|'custom'
   * @param array &$settings
   * @param array $overrides
689
   */
Pawel G's avatar
Pawel G committed
690
  public static function supplementDefaultSettings($type, &$settings, $overrides = []) {
691
    foreach (self::$allowedLinkSettings[$type] as $allowed_link_setting) {
692
      if (!isset($settings[$allowed_link_setting])
Pawel G's avatar
Pawel G committed
693
        && isset(self::$linkSettingDefaults[$allowed_link_setting])) {
694 695 696
        $settings[$allowed_link_setting] = isset($overrides[$allowed_link_setting])
          ? $overrides[$allowed_link_setting]
          : self::$linkSettingDefaults[$allowed_link_setting];
697 698
      }
    }
699 700
  }

Pawel G's avatar
Pawel G committed
701 702 703
  /**
   * Overrides entity bundle/entity type sitemap settings for a single entity.
   *
Pawel G's avatar
Pawel G committed
704 705 706
   * @param string $entity_type_id
   * @param int $id
   * @param array $settings
Pawel G's avatar
Pawel G committed
707
   *
Pawel G's avatar
Pawel G committed
708 709
   * @return $this
   */
710
  public function setEntityInstanceSettings($entity_type_id, $id, $settings) {
711
    $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($id);
712 713 714
    $bundle_settings = $this->getBundleSettings(
      $entity_type_id, $this->entityHelper->getEntityInstanceBundleName($entity)
    );
715
    if (!empty($bundle_settings)) {
716 717 718 719

      // Check if overrides are different from bundle setting before saving.
      $override = FALSE;
      foreach ($settings as $key => $setting) {
720
        if (!isset($bundle_settings[$key]) || $setting != $bundle_settings[$key]) {
721 722 723 724
          $override = TRUE;
          break;
        }
      }
Pawel G's avatar
Pawel G committed
725 726
      // Save overrides for this entity if something is different.
      if ($override) {
727 728 729 730 731 732 733
        $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,
734
            'inclusion_settings' => serialize(array_merge($bundle_settings, $settings)),])
735
          ->execute();
736
      }
Pawel G's avatar
Pawel G committed
737 738
      // Else unset override.
      else {
739
        $this->removeEntityInstanceSettings($entity_type_id, $id);
740
      }
741 742 743
    }
    else {
      //todo: log error
744
    }
Pawel G's avatar
Pawel G committed
745
    return $this;
746 747
  }

Pawel G's avatar
Pawel G committed
748
  /**
749
   * Gets sitemap settings for an entity instance which overrides the sitemap
750
   * settings of its bundle, or bundle settings, if they are not overridden.
Pawel G's avatar
Pawel G committed
751
   *
Pawel G's avatar
Pawel G committed
752
   * @param string $entity_type_id
753
   * @param int $id
Pawel G's avatar
Pawel G committed
754
   *
755
   * @return array|false
Pawel G's avatar
Pawel G committed
756
   */
757 758 759 760 761 762 763 764 765
  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)) {
766
      return unserialize($results);
767 768
    }
    else {
769 770 771 772 773 774
      $entity = $this->entityTypeManager->getStorage($entity_type_id)
        ->load($id);
      return $this->getBundleSettings(
        $entity_type_id,
        $this->entityHelper->getEntityInstanceBundleName($entity)
      );
775 776 777
    }
  }

778 779 780 781 782 783 784 785 786 787 788 789
  /**
   * 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);
790
    if (NULL !== $entity_ids) {
791 792 793 794 795 796 797
      $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
798 799 800 801
  /**
   * Checks if an entity bundle (or a non-bundle entity type) is set to be
   * indexed in the sitemap settings.
   *
802 803
   * @param string $entity_type_id
   * @param string|null $bundle_name
Pawel G's avatar
Pawel G committed
804
   *
Pawel G's avatar
Pawel G committed
805 806
   * @return bool
   */
807
  public function bundleIsIndexed($entity_type_id, $bundle_name = NULL) {
808 809 810 811
    $settings = $this->getBundleSettings($entity_type_id, $bundle_name);
    return !empty($settings['index']);
  }

Pawel G's avatar
Pawel G committed
812 813 814
  /**
   * Checks if an entity type is enabled in the sitemap settings.
   *
815
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
816
   *
Pawel G's avatar
Pawel G committed
817 818
   * @return bool
   */
819
  public function entityTypeIsEnabled($entity_type_id) {
820
    return in_array($entity_type_id, $this->getSetting('enabled_entity_types', []));
821 822
  }

Pawel G's avatar
Pawel G committed
823
  /**
824
   * Stores a custom path along with its sitemap settings to configuration.
Pawel G's avatar
Pawel G committed
825
   *
Pawel G's avatar
Pawel G committed
826 827
   * @param string $path
   * @param array $settings
Pawel G's avatar
Pawel G committed
828
   *
Pawel G's avatar
Pawel G committed
829
   * @return $this
830 831
   *
   * @todo Validate $settings and throw exceptions
Pawel G's avatar
Pawel G committed
832
   */
833
  public function addCustomLink($path, $settings = []) {
Pawel G's avatar
Pawel G committed
834 835 836 837
    if (!$this->pathValidator->isValid($path)) {
      // todo: log error.
      return $this;
    }
Pawel G's avatar
Pawel G committed
838
    if ($path[0] !== '/') {
Pawel G's avatar
Pawel G committed
839 840 841
      // todo: log error.
      return $this;
    }
Pawel G's avatar
Pawel G committed
842

843
    $custom_links = $this->getCustomLinks(FALSE);
Pawel G's avatar
Pawel G committed
844
    foreach ($custom_links as $key => $link) {
845
      if ($link['path'] === $path) {
846 847 848 849 850
        $link_key = $key;
        break;
      }
    }
    $link_key = isset($link_key) ? $link_key : count($custom_links);
851
    $custom_links[$link_key] = ['path' => $path] + $settings;
Pawel G's avatar
Pawel G committed
852
    $this->configFactory->getEditable('simple_sitemap.custom')
853
      ->set('links', $custom_links)->save();
Pawel G's avatar
Pawel G committed
854
    return $this;
855 856
  }

857 858 859
  /**
   * Returns an array of custom paths and their sitemap settings.
   *
860
   * @param bool $supplement_default_settings
861 862
   * @return array
   */
863
  public function getCustomLinks($supplement_default_settings = TRUE) {
864 865
    $custom_links = $this->configFactory
      ->get('simple_sitemap.custom')
866
      ->get('links');
867 868 869

    if ($supplement_default_settings) {
      foreach ($custom_links as $i => $link_settings) {
Pawel G's avatar
Pawel G committed
870 871
        self::supplementDefaultSettings('custom', $link_settings);
        $custom_links[$i] = $link_settings;
872 873 874
      }
    }

875
    return $custom_links !== NULL ? $custom_links : [];
876 877
  }

Pawel G's avatar
Pawel G committed
878 879 880
  /**
   * Returns settings for a custom path added to the sitemap settings.
   *
Pawel G's avatar
Pawel G committed
881
   * @param string $path
Pawel G's avatar
Pawel G committed
882
   *
Pawel G's avatar
Pawel G committed
883
   * @return array|false
Pawel G's avatar
Pawel G committed
884
   */
885
  public function getCustomLink($path) {
886
    foreach ($this->getCustomLinks() as $key => $link) {
887
      if ($link['path'] === $path) {
888
        return $link;
889 890 891 892 893
      }
    }
    return FALSE;
  }

Pawel G's avatar
Pawel G committed
894 895 896
  /**
   * Removes a custom path from the sitemap settings.
   *
Pawel G's avatar
Pawel G committed
897
   * @param string $path
Pawel G's avatar
Pawel G committed
898
   *
Pawel G's avatar
Pawel G committed
899 900
   * @return $this
   */
901
  public function removeCustomLink($path) {
902
    $custom_links = $this->getCustomLinks(FALSE);
Pawel G's avatar
Pawel G committed
903
    foreach ($custom_links as $key => $link) {
904
      if ($link['path'] === $path) {
905 906
        unset($custom_links[$key]);
        $custom_links = array_values($custom_links);
Pawel G's avatar
Pawel G committed
907
        $this->configFactory->getEditable('simple_sitemap.custom')
908
          ->set('links', $custom_links)->save();
909
        break;
910 911
      }
    }
Pawel G's avatar
Pawel G committed
912
    return $this;
913 914
  }

Pawel G's avatar
Pawel G committed
915 916
  /**
   * Removes all custom paths from the sitemap settings.
Pawel G's avatar
Pawel G committed
917 918
   *
   * @return $this
Pawel G's avatar
Pawel G committed
919
   */
920
  public function removeCustomLinks() {
Pawel G's avatar
Pawel G committed
921
    $this->configFactory->getEditable('simple_sitemap.custom')
922
      ->set('links', [])->save();
Pawel G's avatar
Pawel G committed
923
    return $this;
924
  }
925
}