Simplesitemap.php 26.6 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
  /**
   * @var \Drupal\simple_sitemap\EntityHelper
   */
26
  protected $entityHelper;
Pawel G's avatar
Pawel G committed
27 28 29 30

  /**
   * @var \Drupal\Core\Config\ConfigFactory
   */
31
  protected $configFactory;
Pawel G's avatar
Pawel G committed
32 33 34 35

  /**
   * @var \Drupal\Core\Database\Connection
   */
36
  protected $db;
Pawel G's avatar
Pawel G committed
37 38 39 40

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

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

48 49 50 51 52
  /**
   * @var \Drupal\Core\Datetime\DateFormatter
   */
  protected $dateFormatter;

53 54 55 56 57
  /**
   * @var \Drupal\Component\Datetime\Time
   */
  protected $time;

58
  /**
59
   * @var \Drupal\Core\Extension\ModuleHandler
60
   */
61
  protected $moduleHandler;
62 63

  /**
64
   * @var \Drupal\simple_sitemap\Batch
65
   */
66
  protected $batch;
67

68 69 70 71 72
  /**
   * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\UrlGenerator\UrlGeneratorManager
   */
  protected $urlGeneratorManager;

73 74 75 76 77
  /**
   * @var \Drupal\simple_sitemap\Plugin\simple_sitemap\SitemapGenerator\SitemapGeneratorManager
   */
  protected $sitemapGeneratorManager;

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

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

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

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

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

189
    if (empty($delta) || !isset($chunk_info[$delta])) {
190

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

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

222 223
    if (NULL !== $variant) {
      $query->condition('s.type', $variant);
224 225 226 227
    }

    $result = $query->execute();

228
    return NULL === $variant ? $result->fetchAllAssoc('type') : $result->fetchAllAssoc('delta');
229 230
  }

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

245 246 247 248 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 288 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 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
  /**
   * @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
   * @todo translate label, description
   */
  public function removeSitemapTypeDefinition($name) {
    if ($name !== SitemapGeneratorBase::DEFAULT_SITEMAP_TYPE) {
      $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'])) {
      $definition['type'] = $name;
    }

    if (empty($definition['type'])) {
      $definition['type'] = SitemapGeneratorBase::DEFAULT_SITEMAP_TYPE;
    }

    $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();
    unset($variants[$name]);
    $this->configFactory->getEditable('simple_sitemap.variants')
      ->set('variants', $variants)->save();

    return $this;
  }

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

356
    $settings = [
357
      'base_url' => $this->getSetting('base_url', ''),
358
      'batch_process_limit' => $this->getSetting('batch_process_limit', 1500),
359 360 361 362
      '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', []),
363
    ];
364

365 366
    $this->batch->setBatchMeta(['from' => $from]);

367
    $operations = [];
Pawel G's avatar
Pawel G committed
368

369 370
    $type_definitions = $this->getSitemapTypeDefinitions();
    $this->moduleHandler->alter('simple_sitemap_types', $type_definitions);
Pawel G's avatar
Pawel G committed
371

372 373
    $sitemap_variants = $this->getSitemapVariants();
    $this->moduleHandler->alter('simple_sitemap_variants', $sitemap_variants);
374

375 376 377 378 379 380
    foreach ($sitemap_variants as $variant_name => $variant_definition) {

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

382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
      $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',
401
              'arguments' => [
402 403 404 405 406 407
                'url_generator' => $url_generator_id,
                'data_set' => $data_set,
                'variant' => $variant_name,
                'sitemap_generator' => $type_definitions[$type]['sitemap_generator'],
                'settings' => $settings,
              ],
408 409
            ];
          }
410
        }
411
      }
412

413 414
      // Adding generate_index operations for all sitemap variants.
      $operations[] = [
415 416
        'operation' => 'generateIndex',
        'arguments' => [
417 418
          'sitemap_generator' => $type_definitions[$type]['sitemap_generator'],
          'variant' => $variant_name,
419 420 421 422 423
          'settings' => $settings,
        ],
      ];
    }

424 425 426 427
    // Adding operations to and starting batch.
    if (!empty($operations)) {
      foreach ($operations as $operation_data) {
        $this->batch->addOperation($operation_data['operation'], $operation_data['arguments']);
428 429 430 431 432
      }
      $success = $this->batch->start();
    }

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

435
  /**
436
   * @param null|array $variant
Pawel G's avatar
Pawel G committed
437
   *
438 439
   * @todo Add removeSitemap API method.
   */
440
  public function removeSitemap($variant = NULL) {
Pawel G's avatar
Pawel G committed
441 442

  }
443

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

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

  /**
   * 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
499
   *  Entity type id like 'node'.
500
   *
Pawel G's avatar
Pawel G committed
501
   * @return $this
502 503
   */
  public function disableEntityType($entity_type_id) {
504 505 506

    // Updating settings.
    $enabled_entity_types = $this->getSetting('enabled_entity_types');
507
    if (FALSE !== ($key = array_search($entity_type_id, $enabled_entity_types))) {
508
      unset ($enabled_entity_types[$key]);
509
      $this->saveSetting('enabled_entity_types', array_values($enabled_entity_types));
510 511 512
    }

    // Deleting inclusion settings.
513
    $config_names = $this->configFactory->listAll("simple_sitemap.bundle_settings.$entity_type_id.");
Pawel G's avatar
Pawel G committed
514
    foreach ($config_names as $config_name) {
515
      $this->configFactory->getEditable($config_name)->delete();
516
    }
517 518 519

    // Deleting entity overrides.
    $this->removeEntityInstanceSettings($entity_type_id);
Pawel G's avatar
Pawel G committed
520
    return $this;
521 522 523
  }

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

Pawel G's avatar
Pawel G committed
542 543 544 545 546 547
    if (!empty($old_settings = $this->getBundleSettings($entity_type_id, $bundle_name))) {
      $settings = array_merge($old_settings, $settings);
    }
    else {
      self::supplementDefaultSettings('entity', $settings);
    }
548 549 550

    $bundle_settings = $this->configFactory
      ->getEditable("simple_sitemap.bundle_settings.$entity_type_id.$bundle_name");
Pawel G's avatar
Pawel G committed
551
    foreach ($settings as $setting_key => $setting) {
552
      if ($setting_key === 'index') {
553 554
        $setting = intval($setting);
      }
555
      $bundle_settings->set($setting_key, $setting);
556
    }
557
    $bundle_settings->save();
558 559

    // Delete entity overrides which are identical to new bundle setting.
Pawel G's avatar
Pawel G committed
560
    $sitemap_entity_types = $this->entityHelper->getSupportedEntityTypes();
561 562 563
    if (isset($sitemap_entity_types[$entity_type_id])) {
      $entity_type = $sitemap_entity_types[$entity_type_id];
      $keys = $entity_type->getKeys();
564 565

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

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

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

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

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

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

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

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

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

Pawel G's avatar
Pawel G committed
771 772 773
  /**
   * Checks if an entity type is enabled in the sitemap settings.
   *
774
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
775
   *
Pawel G's avatar
Pawel G committed
776 777
   * @return bool
   */
778
  public function entityTypeIsEnabled($entity_type_id) {
779
    return in_array($entity_type_id, $this->getSetting('enabled_entity_types', []));
780 781
  }

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

802
    $custom_links = $this->getCustomLinks(FALSE);
Pawel G's avatar
Pawel G committed
803
    foreach ($custom_links as $key => $link) {
804
      if ($link['path'] === $path) {
805 806 807 808 809
        $link_key = $key;
        break;
      }
    }
    $link_key = isset($link_key) ? $link_key : count($custom_links);
810
    $custom_links[$link_key] = ['path' => $path] + $settings;
Pawel G's avatar
Pawel G committed
811
    $this->configFactory->getEditable('simple_sitemap.custom')
812
      ->set('links', $custom_links)->save();
Pawel G's avatar
Pawel G committed
813
    return $this;
814 815
  }

816 817 818
  /**
   * Returns an array of custom paths and their sitemap settings.
   *
819
   * @param bool $supplement_default_settings
820 821
   * @return array
   */
822
  public function getCustomLinks($supplement_default_settings = TRUE) {
823 824
    $custom_links = $this->configFactory
      ->get('simple_sitemap.custom')
825
      ->get('links');
826 827 828

    if ($supplement_default_settings) {
      foreach ($custom_links as $i => $link_settings) {
Pawel G's avatar
Pawel G committed
829 830
        self::supplementDefaultSettings('custom', $link_settings);
        $custom_links[$i] = $link_settings;
831 832 833
      }
    }

834
    return $custom_links !== NULL ? $custom_links : [];
835 836
  }

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

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

Pawel G's avatar
Pawel G committed
874 875
  /**
   * Removes all custom paths from the sitemap settings.
Pawel G's avatar
Pawel G committed
876 877
   *
   * @return $this
Pawel G's avatar
Pawel G committed
878
   */
879
  public function removeCustomLinks() {
Pawel G's avatar
Pawel G committed
880
    $this->configFactory->getEditable('simple_sitemap.custom')
881
      ->set('links', [])->save();
Pawel G's avatar
Pawel G committed
882
    return $this;
883
  }
884
}