Simplesitemap.php 29.4 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 307 308 309 310 311

      // Remove variants tied to this definition.
      $variants = $this->getSitemapVariants();
      foreach ($variants as $variant_name => $variant_definition) {
        if ($variant_definition['type'] === $name) {
          $this->removeSitemapVariant($variant_name);
        }
      }

      // Remove type definition from configuration.
312 313 314 315 316
      $this->configFactory->getEditable("simple_sitemap.types.$name")->delete();
    }
    else {
      //todo: exception
    }
Pawel G's avatar
Pawel G committed
317 318

    return $this;
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
  }

  /**
   * @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
   */
339
  public function addSitemapVariant($name, $definition = []) {
340
    if (empty($definition['label'])) {
Pawel G's avatar
Pawel G committed
341
      $definition['label'] = $name;
342 343 344
    }

    if (empty($definition['type'])) {
Pawel G's avatar
Pawel G committed
345
      $definition['type'] = self::DEFAULT_SITEMAP_TYPE;
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
    }

    $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) {
362 363 364 365 366

    // Remove the sitemap variant instance from database.
    $this->removeSitemap($name);

    // Remove the variant definition from configuration.
367
    $variants = $this->getSitemapVariants();
Pawel G's avatar
Pawel G committed
368 369 370 371 372
    if (isset($variants[$name])) {
      unset($variants[$name]);
      $this->configFactory->getEditable('simple_sitemap.variants')
        ->set('variants', $variants)->save();
    }
373 374 375 376

    return $this;
  }

377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407
  /**
   * @param null $variants
   * @return $this
   *
   * @todo test
   */
  public function removeSitemap($variants = NULL) {
    $variant_definitions = $this->getSitemapVariants();
    $remove_variants = [];
    if (NULL === $variants) {
      $remove_variants = $variant_definitions;
    }
    else {
      foreach ((array) $variants as $variant) {
        if (isset($variant_definitions[$variant])) {
          $remove_variants[$variant] = $variant_definitions[$variant];
        }
      }
    }
    if (!empty($remove_variants)) {
      $type_definitions = $this->getSitemapTypeDefinitions();
      foreach ($remove_variants as $variant_name => $variant_definition) {
        $this->sitemapGeneratorManager
          ->createInstance($type_definitions[$variant_definition['type']]['sitemap_generator'])
          ->setSitemapVariant($variant_name)
          ->remove();
      }
    }
    return $this;
  }

408
  /**
Pawel G's avatar
Pawel G committed
409
   * Generates the XML sitemap and saves it to the database.
410 411
   *
   * @param string $from
Pawel G's avatar
Pawel G committed
412 413
   *  Can be 'form', 'backend', 'drush' or 'nobatch'.
   *  This decides how the batch process is to be run.
Pawel G's avatar
Pawel G committed
414
   *
415
   * @param array|string|null $variants
416
   *
Pawel G's avatar
Pawel G committed
417
   * @return bool|\Drupal\simple_sitemap\Simplesitemap
418
   */
419 420
  public function generateSitemap($from = 'form', $variants = NULL) {
    $variants = NULL === $variants ? NULL : (array) $variants;
421

422
    $settings = [
423
      'base_url' => $this->getSetting('base_url', ''),
424
      'batch_process_limit' => $this->getSetting('batch_process_limit', 1500),
425 426 427 428
      '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', []),
429
    ];
430

431 432
    $this->batch->setBatchMeta(['from' => $from]);

433
    $operations = [];
Pawel G's avatar
Pawel G committed
434

435 436
    $type_definitions = $this->getSitemapTypeDefinitions();
    $this->moduleHandler->alter('simple_sitemap_types', $type_definitions);
Pawel G's avatar
Pawel G committed
437

438 439
    $sitemap_variants = $this->getSitemapVariants();
    $this->moduleHandler->alter('simple_sitemap_variants', $sitemap_variants);
440

441 442 443 444 445 446
    foreach ($sitemap_variants as $variant_name => $variant_definition) {

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

448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
      $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',
467
              'arguments' => [
468 469 470 471 472 473
                'url_generator' => $url_generator_id,
                'data_set' => $data_set,
                'variant' => $variant_name,
                'sitemap_generator' => $type_definitions[$type]['sitemap_generator'],
                'settings' => $settings,
              ],
474 475
            ];
          }
476
        }
477
      }
478

479 480
      // Adding generate_index operations for all sitemap variants.
      $operations[] = [
481 482
        'operation' => 'generateIndex',
        'arguments' => [
483 484
          'sitemap_generator' => $type_definitions[$type]['sitemap_generator'],
          'variant' => $variant_name,
485 486 487 488 489
          'settings' => $settings,
        ],
      ];
    }

490 491 492 493
    // Adding operations to and starting batch.
    if (!empty($operations)) {
      foreach ($operations as $operation_data) {
        $this->batch->addOperation($operation_data['operation'], $operation_data['arguments']);
494 495 496 497 498
      }
      $success = $this->batch->start();
    }

    return $from === 'nobatch' ? $this : (isset($success) ? $success : FALSE);
499 500 501 502 503
  }

  /**
   * Returns a 'time ago' string of last timestamp generation.
   *
504
   * @param string|null $variant
505 506
   *
   * @return string|array|false
Pawel G's avatar
Pawel G committed
507
   *  Formatted timestamp of last sitemap generation, otherwise FALSE.
508
   */
509
  public function getGeneratedAgo($variant = NULL) {
Pawel G's avatar
Pawel G committed
510
    $chunks = $this->fetchSitemapVariantInfo($variant);
511
    if ($variant !== NULL) {
512
      return isset($chunks[DefaultSitemapGenerator::FIRST_CHUNK_DELTA]->sitemap_created)
513
        ? $this->dateFormatter
514
          ->formatInterval($this->time->getRequestTime() - $chunks[DefaultSitemapGenerator::FIRST_CHUNK_DELTA]
515 516 517 518 519 520 521 522 523 524 525 526
              ->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;
527 528 529
    }
  }

530 531
  /**
   * Enables sitemap support for an entity type. Enabled entity types show
532 533
   * sitemap settings on their bundle setting forms. If an enabled entity type
   * features bundles (e.g. 'node'), it needs to be set up with
534 535 536
   * setBundleSettings() as well.
   *
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
537
   *  Entity type id like 'node'.
538
   *
Pawel G's avatar
Pawel G committed
539
   * @return $this
540 541
   */
  public function enableEntityType($entity_type_id) {
542 543 544 545
    $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);
546
    }
Pawel G's avatar
Pawel G committed
547
    return $this;
548 549 550 551 552 553 554 555
  }

  /**
   * 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
556
   *  Entity type id like 'node'.
557
   *
Pawel G's avatar
Pawel G committed
558
   * @return $this
559 560
   */
  public function disableEntityType($entity_type_id) {
561 562 563

    // Updating settings.
    $enabled_entity_types = $this->getSetting('enabled_entity_types');
564
    if (FALSE !== ($key = array_search($entity_type_id, $enabled_entity_types))) {
565
      unset ($enabled_entity_types[$key]);
566
      $this->saveSetting('enabled_entity_types', array_values($enabled_entity_types));
567 568 569
    }

    // Deleting inclusion settings.
570
    $config_names = $this->configFactory->listAll("simple_sitemap.bundle_settings.$entity_type_id.");
Pawel G's avatar
Pawel G committed
571
    foreach ($config_names as $config_name) {
572
      $this->configFactory->getEditable($config_name)->delete();
573
    }
574 575 576

    // Deleting entity overrides.
    $this->removeEntityInstanceSettings($entity_type_id);
Pawel G's avatar
Pawel G committed
577
    return $this;
578 579 580
  }

  /**
Pawel G's avatar
Pawel G committed
581
   * Sets sitemap settings for a non-bundle entity type (e.g. user) or a bundle
582 583 584
   * of an entity type (e.g. page).
   *
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
585
   *  Entity type id like 'node' the bundle belongs to.
586
   * @param string $bundle_name
Pawel G's avatar
Pawel G committed
587
   *  Name of the bundle. NULL if entity type has no bundles.
588
   * @param array $settings
Pawel G's avatar
Pawel G committed
589 590
   *  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
591 592
   *
   * @return $this
593 594
   *
   * @todo: enableEntityType automatically
595
   */
596
  public function setBundleSettings($entity_type_id, $bundle_name = NULL, $settings = ['index' => TRUE]) {
597
    $bundle_name = NULL !== $bundle_name ? $bundle_name : $entity_type_id;
598

Pawel G's avatar
Pawel G committed
599 600 601 602 603 604
    if (!empty($old_settings = $this->getBundleSettings($entity_type_id, $bundle_name))) {
      $settings = array_merge($old_settings, $settings);
    }
    else {
      self::supplementDefaultSettings('entity', $settings);
    }
605 606 607

    $bundle_settings = $this->configFactory
      ->getEditable("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name");
Pawel G's avatar
Pawel G committed
608
    foreach ($settings as $setting_key => $setting) {
609
      $bundle_settings->set($setting_key, $setting);
610
    }
611
    $bundle_settings->save();
612 613

    // Delete entity overrides which are identical to new bundle setting.
Pawel G's avatar
Pawel G committed
614
    $sitemap_entity_types = $this->entityHelper->getSupportedEntityTypes();
615 616 617
    if (isset($sitemap_entity_types[$entity_type_id])) {
      $entity_type = $sitemap_entity_types[$entity_type_id];
      $keys = $entity_type->getKeys();
618 619

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

622
      $query = $this->entityTypeManager->getStorage($entity_type_id)->getQuery();
623
      if (!$this->entityHelper->entityTypeIsAtomic($entity_type_id)) {
624
        $query->condition($keys['bundle'], $bundle_name);
625
      }
626 627 628 629 630 631 632 633 634
      $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');
      }

635
      $delete_instances = [];
Pawel G's avatar
Pawel G committed
636
      foreach ($query->execute()->fetchAll() as $result) {
637 638 639
        $delete = TRUE;
        $instance_settings = unserialize($result->inclusion_settings);
        foreach ($instance_settings as $setting_key => $instance_setting) {
640
          if ($instance_setting != $settings[$setting_key]) {
641 642 643 644 645
            $delete = FALSE;
            break;
          }
        }
        if ($delete) {
646
          $delete_instances[] = $result->id;
647
        }
648
      }
649 650 651 652 653
      if (!empty($delete_instances)) {
        $this->db->delete('simple_sitemap_entity_overrides')
          ->condition('id', $delete_instances, 'IN')
          ->execute();
      }
654
    }
655 656 657
    else {
      //todo: log error
    }
Pawel G's avatar
Pawel G committed
658
    return $this;
659 660
  }

661
  /**
662 663
   * Gets sitemap settings for an entity bundle, a non-bundle entity type or for
   * all entity types and their bundles.
664
   *
665 666 667
   * @param string|null $entity_type_id
   *  If set to null, sitemap settings for all entity types and their bundles
   *  are fetched.
668 669 670
   * @param string|null $bundle_name
   *
   * @return array|false
Pawel G's avatar
Pawel G committed
671 672
   *  Array of sitemap settings for an entity bundle, a non-bundle entity type
   *  or for all entity types and their bundles.
673
   *  False if entity type does not exist.
674
   */
675
  public function getBundleSettings($entity_type_id = NULL, $bundle_name = NULL) {
676
    if (NULL !== $entity_type_id) {
677 678

      // Get bundle settings saved in simple_sitemap.bundle_settings.*.* configuration.
679
      $bundle_name = NULL !== $bundle_name ? $bundle_name : $entity_type_id;
680
      $bundle_settings = $this->configFactory
681 682
        ->get("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name")
        ->get();
683 684 685 686 687

      // 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])) {
688
          self::supplementDefaultSettings('entity', $bundle_settings);
689 690 691 692 693
        }
        else {
          $bundle_settings = FALSE;
        }
      }
694
    }
695
    else {
696
      // Get all bundle settings saved in simple_sitemap.bundle_settings.*.* configuration.
Pawel G's avatar
Pawel G committed
697
      $config_names = $this->configFactory->listAll('simple_sitemap.bundle_settings.');
698
      $bundle_settings = [];
Pawel G's avatar
Pawel G committed
699
      foreach ($config_names as $config_name) {
700
        $config_name_parts = explode('.', $config_name);
701 702 703 704 705 706 707 708
        $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])) {
709
              self::supplementDefaultSettings('entity', $bundle_settings[$type_id][$bundle]);
710 711 712
            }
          }
        }
713 714
      }
    }
715
    return $bundle_settings;
716 717 718
  }

  /**
719 720
   * Supplements all missing link setting with default values.
   *
721
   * @param string $type
Pawel G's avatar
Pawel G committed
722 723 724
   *  'entity'|'custom'
   * @param array &$settings
   * @param array $overrides
725
   */
Pawel G's avatar
Pawel G committed
726
  public static function supplementDefaultSettings($type, &$settings, $overrides = []) {
727
    foreach (self::$allowedLinkSettings[$type] as $allowed_link_setting) {
728
      if (!isset($settings[$allowed_link_setting])
Pawel G's avatar
Pawel G committed
729
        && isset(self::$linkSettingDefaults[$allowed_link_setting])) {
730 731 732
        $settings[$allowed_link_setting] = isset($overrides[$allowed_link_setting])
          ? $overrides[$allowed_link_setting]
          : self::$linkSettingDefaults[$allowed_link_setting];
733 734
      }
    }
735 736
  }

Pawel G's avatar
Pawel G committed
737 738 739
  /**
   * Overrides entity bundle/entity type sitemap settings for a single entity.
   *
Pawel G's avatar
Pawel G committed
740 741 742
   * @param string $entity_type_id
   * @param int $id
   * @param array $settings
Pawel G's avatar
Pawel G committed
743
   *
Pawel G's avatar
Pawel G committed
744 745
   * @return $this
   */
746
  public function setEntityInstanceSettings($entity_type_id, $id, $settings) {
747
    $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($id);
748 749 750
    $bundle_settings = $this->getBundleSettings(
      $entity_type_id, $this->entityHelper->getEntityInstanceBundleName($entity)
    );
751
    if (!empty($bundle_settings)) {
752 753 754 755

      // Check if overrides are different from bundle setting before saving.
      $override = FALSE;
      foreach ($settings as $key => $setting) {
756
        if (!isset($bundle_settings[$key]) || $setting != $bundle_settings[$key]) {
757 758 759 760
          $override = TRUE;
          break;
        }
      }
Pawel G's avatar
Pawel G committed
761 762
      // Save overrides for this entity if something is different.
      if ($override) {
763 764 765 766 767 768 769
        $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,
770
            'inclusion_settings' => serialize(array_merge($bundle_settings, $settings)),])
771
          ->execute();
772
      }
Pawel G's avatar
Pawel G committed
773 774
      // Else unset override.
      else {
775
        $this->removeEntityInstanceSettings($entity_type_id, $id);
776
      }
777 778 779
    }
    else {
      //todo: log error
780
    }
Pawel G's avatar
Pawel G committed
781
    return $this;
782 783
  }

Pawel G's avatar
Pawel G committed
784
  /**
785
   * Gets sitemap settings for an entity instance which overrides the sitemap
786
   * settings of its bundle, or bundle settings, if they are not overridden.
Pawel G's avatar
Pawel G committed
787
   *
Pawel G's avatar
Pawel G committed
788
   * @param string $entity_type_id
789
   * @param int $id
Pawel G's avatar
Pawel G committed
790
   *
791
   * @return array|false
Pawel G's avatar
Pawel G committed
792
   */
793 794 795 796 797 798 799 800 801
  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)) {
802
      return unserialize($results);
803 804
    }
    else {
805 806 807 808 809 810
      $entity = $this->entityTypeManager->getStorage($entity_type_id)
        ->load($id);
      return $this->getBundleSettings(
        $entity_type_id,
        $this->entityHelper->getEntityInstanceBundleName($entity)
      );
811 812 813
    }
  }

814 815 816 817 818 819 820 821 822 823 824 825
  /**
   * 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);
826
    if (NULL !== $entity_ids) {
827 828 829 830 831 832 833
      $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
834 835 836 837
  /**
   * Checks if an entity bundle (or a non-bundle entity type) is set to be
   * indexed in the sitemap settings.
   *
838 839
   * @param string $entity_type_id
   * @param string|null $bundle_name
Pawel G's avatar
Pawel G committed
840
   *
Pawel G's avatar
Pawel G committed
841 842
   * @return bool
   */
843
  public function bundleIsIndexed($entity_type_id, $bundle_name = NULL) {
844 845 846 847
    $settings = $this->getBundleSettings($entity_type_id, $bundle_name);
    return !empty($settings['index']);
  }

Pawel G's avatar
Pawel G committed
848 849 850
  /**
   * Checks if an entity type is enabled in the sitemap settings.
   *
851
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
852
   *
Pawel G's avatar
Pawel G committed
853 854
   * @return bool
   */
855
  public function entityTypeIsEnabled($entity_type_id) {
856
    return in_array($entity_type_id, $this->getSetting('enabled_entity_types', []));
857 858
  }

Pawel G's avatar
Pawel G committed
859
  /**
860
   * Stores a custom path along with its sitemap settings to configuration.
Pawel G's avatar
Pawel G committed
861
   *
Pawel G's avatar
Pawel G committed
862 863
   * @param string $path
   * @param array $settings
Pawel G's avatar
Pawel G committed
864
   *
Pawel G's avatar
Pawel G committed
865
   * @return $this
866 867
   *
   * @todo Validate $settings and throw exceptions
Pawel G's avatar
Pawel G committed
868
   */
869
  public function addCustomLink($path, $settings = []) {
Pawel G's avatar
Pawel G committed
870 871 872 873
    if (!$this->pathValidator->isValid($path)) {
      // todo: log error.
      return $this;
    }
Pawel G's avatar
Pawel G committed
874
    if ($path[0] !== '/') {
Pawel G's avatar
Pawel G committed
875 876 877
      // todo: log error.
      return $this;
    }
Pawel G's avatar
Pawel G committed
878

879
    $custom_links = $this->getCustomLinks(FALSE);
Pawel G's avatar
Pawel G committed
880
    foreach ($custom_links as $key => $link) {
881
      if ($link['path'] === $path) {
882 883 884 885 886
        $link_key = $key;
        break;
      }
    }
    $link_key = isset($link_key) ? $link_key : count($custom_links);
887
    $custom_links[$link_key] = ['path' => $path] + $settings;
Pawel G's avatar
Pawel G committed
888
    $this->configFactory->getEditable('simple_sitemap.custom')
889
      ->set('links', $custom_links)->save();
Pawel G's avatar
Pawel G committed
890
    return $this;
891 892
  }

893 894 895
  /**
   * Returns an array of custom paths and their sitemap settings.
   *
896
   * @param bool $supplement_default_settings
897 898
   * @return array
   */
899
  public function getCustomLinks($supplement_default_settings = TRUE) {
900 901
    $custom_links = $this->configFactory
      ->get('simple_sitemap.custom')
902
      ->get('links');
903 904 905

    if ($supplement_default_settings) {
      foreach ($custom_links as $i => $link_settings) {
Pawel G's avatar
Pawel G committed
906 907
        self::supplementDefaultSettings('custom', $link_settings);
        $custom_links[$i] = $link_settings;
908 909 910
      }
    }

911
    return $custom_links !== NULL ? $custom_links : [];
912 913
  }

Pawel G's avatar
Pawel G committed
914 915 916
  /**
   * Returns settings for a custom path added to the sitemap settings.
   *
Pawel G's avatar
Pawel G committed
917
   * @param string $path
Pawel G's avatar
Pawel G committed
918
   *
Pawel G's avatar
Pawel G committed
919
   * @return array|false
Pawel G's avatar
Pawel G committed
920
   */
921
  public function getCustomLink($path) {
922
    foreach ($this->getCustomLinks() as $key => $link) {
923
      if ($link['path'] === $path) {
924
        return $link;
925 926 927 928 929
      }
    }
    return FALSE;
  }

Pawel G's avatar
Pawel G committed
930 931 932
  /**
   * Removes a custom path from the sitemap settings.
   *
Pawel G's avatar
Pawel G committed
933
   * @param string $path
Pawel G's avatar
Pawel G committed
934
   *
Pawel G's avatar
Pawel G committed
935 936
   * @return $this
   */
937
  public function removeCustomLink($path) {
938
    $custom_links = $this->getCustomLinks(FALSE);
Pawel G's avatar
Pawel G committed
939
    foreach ($custom_links as $key => $link) {
940
      if ($link['path'] === $path) {
941 942
        unset($custom_links[$key]);
        $custom_links = array_values($custom_links);
Pawel G's avatar
Pawel G committed
943
        $this->configFactory->getEditable('simple_sitemap.custom')
944
          ->set('links', $custom_links)->save();
945
        break;
946 947
      }
    }
Pawel G's avatar
Pawel G committed
948
    return $this;
949 950
  }

Pawel G's avatar
Pawel G committed
951 952
  /**
   * Removes all custom paths from the sitemap settings.
Pawel G's avatar
Pawel G committed
953 954
   *
   * @return $this
Pawel G's avatar
Pawel G committed
955
   */
956
  public function removeCustomLinks() {
Pawel G's avatar
Pawel G committed
957
    $this->configFactory->getEditable('simple_sitemap.custom')
958
      ->set('links', [])->save();
Pawel G's avatar
Pawel G committed
959
    return $this;
960
  }
961
}