Simplesitemap.php 14.3 KB
Newer Older
1 2
<?php

Pawel G's avatar
Pawel G committed
3
namespace Drupal\simple_sitemap;
4

5
use Drupal\Core\Entity\ContentEntityTypeInterface;
6
use Drupal\simple_sitemap\Form\FormHelper;
Pawel G's avatar
Pawel G committed
7
use Drupal\simple_sitemap\SitemapGenerator;
8

9
/**
Pawel G's avatar
Pawel G committed
10 11
 * Class Simplesitemap.
 *
Pawel G's avatar
Pawel G committed
12
 * @package Drupal\simple_sitemap
13 14 15
 */
class Simplesitemap {

16
  private $sitemapGenerator;
17 18 19
  private $configFactory;
  private $db;
  private $entityTypeManager;
20
  private $pathValidator;
21 22
  private static $allowed_link_settings = [
    'entity' => ['index', 'priority'],
Pawel G's avatar
Pawel G committed
23
    'custom' => ['priority'],
24
  ];
25

26 27
  /**
   * Simplesitemap constructor.
Pawel G's avatar
Pawel G committed
28
   *
29
   * @param $sitemapGenerator
30
   * @param $configFactoryInterface
31
   * @param $database
32
   * @param $entityTypeManager
33 34
   * @param $pathValidator
   * @param $dateFormatter
35
   */
36
  public function __construct(
Pawel G's avatar
Pawel G committed
37
    SitemapGenerator $sitemapGenerator,
38
    $configFactoryInterface,
39
    $database,
40
    $entityTypeManager,
41 42
    $pathValidator,
    $dateFormatter
43 44
  ) {
    $this->sitemapGenerator = $sitemapGenerator;
45 46 47
    $this->configFactory = $configFactoryInterface;
    $this->db = $database;
    $this->entityTypeManager = $entityTypeManager;
48
    $this->pathValidator = $pathValidator;
49
    $this->dateFormatter = $dateFormatter;
50 51
  }

52
  /**
53
   * Gets a specific sitemap configuration from the configuration storage.
54
   *
Pawel G's avatar
Pawel G committed
55
   * @param string $key
Pawel G's avatar
Pawel G committed
56 57
   *   Configuration key, like 'entity_types'.
   *
58
   * @return mixed
Pawel G's avatar
Pawel G committed
59
   *   The requested configuration.
60
   */
61
  public function getConfig($key) {
62
    return $this->configFactory->get('simple_sitemap.settings')->get($key);
63 64
  }

Pawel G's avatar
Pawel G committed
65 66 67
  /**
   *
   */
Pawel G's avatar
Pawel G committed
68
  private function fetchSitemapChunks() {
69
    return $this->db
70 71 72 73
      ->query("SELECT * FROM {simple_sitemap}")
      ->fetchAllAssoc('id');
  }

74
  /**
75
   * Saves a specific sitemap configuration to db.
76
   *
77
   * @param string $key
Pawel G's avatar
Pawel G committed
78
   *   Configuration key, like 'entity_types'.
79
   * @param mixed $value
Pawel G's avatar
Pawel G committed
80
   *   The configuration to be saved.
Pawel G's avatar
Pawel G committed
81 82
   *
   * @return $this
83
   */
84
  public function saveConfig($key, $value) {
85
    $this->configFactory->getEditable('simple_sitemap.settings')
86
      ->set($key, $value)->save();
Pawel G's avatar
Pawel G committed
87
    return $this;
88 89
  }

90 91 92 93 94 95 96
  /**
   * Enables sitemap support for an entity type. Enabled entity types show
   * sitemap settings on their bundles. If an enabled entity type does not
   * featured bundles (e.g. 'user'), it needs to be set up with
   * setBundleSettings() as well.
   *
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
97
   *   Entity type id like 'node'.
98
   *
Pawel G's avatar
Pawel G committed
99
   * @return $this
100 101 102 103 104 105 106
   */
  public function enableEntityType($entity_type_id) {
    $entity_types = $this->getConfig('entity_types');
    if (empty($entity_types[$entity_type_id])) {
      $entity_types[$entity_type_id] = [];
      $this->saveConfig('entity_types', $entity_types);
    }
Pawel G's avatar
Pawel G committed
107
    return $this;
108 109 110 111 112 113 114 115
  }

  /**
   * 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
Pawel G's avatar
Pawel G committed
116
   *   Entity type id like 'node'.
117
   *
Pawel G's avatar
Pawel G committed
118
   * @return $this
119 120 121 122 123 124 125
   */
  public function disableEntityType($entity_type_id) {
    $entity_types = $this->getConfig('entity_types');
    if (isset($entity_types[$entity_type_id])) {
      unset($entity_types[$entity_type_id]);
      $this->saveConfig('entity_types', $entity_types);
    }
Pawel G's avatar
Pawel G committed
126
    return $this;
127 128 129
  }

  /**
Pawel G's avatar
Pawel G committed
130
   * Sets sitemap settings for a non-bundle entity type (e.g. user) or a bundle
131 132 133
   * of an entity type (e.g. page).
   *
   * @param string $entity_type_id
Pawel G's avatar
Pawel G committed
134
   *   Entity type id like 'node' the bundle belongs to.
135
   * @param string $bundle_name
Pawel G's avatar
Pawel G committed
136
   *   Name of the bundle. NULL if entity type has no bundles.
137
   * @param array $settings
Pawel G's avatar
Pawel G committed
138 139
   *   An array of sitemap settings for this bundle/entity type.
   *   Example: ['index' => TRUE, 'priority' => 0.5].
Pawel G's avatar
Pawel G committed
140 141
   *
   * @return $this
142 143
   */
  public function setBundleSettings($entity_type_id, $bundle_name = NULL, $settings) {
144
    $bundle_name = empty($bundle_name) ? $entity_type_id : $bundle_name;
145
    $entity_types = $this->getConfig('entity_types');
146
    $this->addLinkSettings('entity', $settings, $entity_types[$entity_type_id][$bundle_name]);
147
    $this->saveConfig('entity_types', $entity_types);
Pawel G's avatar
Pawel G committed
148
    return $this;
149 150
  }

Pawel G's avatar
Pawel G committed
151 152 153
  /**
   * Overrides entity bundle/entity type sitemap settings for a single entity.
   *
Pawel G's avatar
Pawel G committed
154 155 156
   * @param string $entity_type_id
   * @param int $id
   * @param array $settings
Pawel G's avatar
Pawel G committed
157
   *
Pawel G's avatar
Pawel G committed
158 159
   * @return $this
   */
160 161
  public function setEntityInstanceSettings($entity_type_id, $id, $settings) {
    $entity_types = $this->getConfig('entity_types');
162 163
    $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($id);
    $bundle_name = $this->getEntityInstanceBundleName($entity);
164 165 166 167 168 169 170 171 172 173
    if (isset($entity_types[$entity_type_id][$bundle_name])) {

      // Check if overrides are different from bundle setting before saving.
      $override = FALSE;
      foreach ($settings as $key => $setting) {
        if ($setting != $entity_types[$entity_type_id][$bundle_name][$key]) {
          $override = TRUE;
          break;
        }
      }
Pawel G's avatar
Pawel G committed
174 175
      // Save overrides for this entity if something is different.
      if ($override) {
176
        $this->addLinkSettings('entity', $settings, $entity_types[$entity_type_id][$bundle_name]['entities'][$id]);
177
      }
Pawel G's avatar
Pawel G committed
178 179
      // Else unset override.
      else {
180 181 182 183
        unset($entity_types[$entity_type_id][$bundle_name]['entities'][$id]);
      }
      $this->saveConfig('entity_types', $entity_types);
    }
Pawel G's avatar
Pawel G committed
184
    return $this;
185 186
  }

Pawel G's avatar
Pawel G committed
187 188 189
  /**
   * Gets sitemap settings for an entity bundle or a non-bundle entity type.
   *
Pawel G's avatar
Pawel G committed
190 191
   * @param string $entity_type_id
   * @param string|null $bundle_name
Pawel G's avatar
Pawel G committed
192
   *
Pawel G's avatar
Pawel G committed
193
   * @return array|false
Pawel G's avatar
Pawel G committed
194
   */
195
  public function getBundleSettings($entity_type_id, $bundle_name = NULL) {
196
    $bundle_name = empty($bundle_name) ? $entity_type_id : $bundle_name;
197 198 199 200 201 202 203 204 205
    $entity_types = $this->getConfig('entity_types');
    if (isset($entity_types[$entity_type_id][$bundle_name])) {
      $settings = $entity_types[$entity_type_id][$bundle_name];
      unset($settings['entities']);
      return $settings;
    }
    return FALSE;
  }

Pawel G's avatar
Pawel G committed
206 207 208 209 210 211
  /**
   * Checks if an entity bundle (or a non-bundle entity type) is set to be
   * indexed in the sitemap settings.
   *
   * @param $entity_type_id
   * @param null $bundle_name
Pawel G's avatar
Pawel G committed
212
   *
Pawel G's avatar
Pawel G committed
213 214
   * @return bool
   */
215
  public function bundleIsIndexed($entity_type_id, $bundle_name = NULL) {
216 217 218 219
    $settings = $this->getBundleSettings($entity_type_id, $bundle_name);
    return !empty($settings['index']);
  }

Pawel G's avatar
Pawel G committed
220 221 222 223
  /**
   * Checks if an entity type is enabled in the sitemap settings.
   *
   * @param $entity_type_id
Pawel G's avatar
Pawel G committed
224
   *
Pawel G's avatar
Pawel G committed
225 226
   * @return bool
   */
227 228 229 230 231
  public function entityTypeIsEnabled($entity_type_id) {
    $entity_types = $this->getConfig('entity_types');
    return isset($entity_types[$entity_type_id]);
  }

Pawel G's avatar
Pawel G committed
232 233 234 235
  /**
   * Gets sitemap settings for an entity instance which overrides bundle
   * settings.
   *
Pawel G's avatar
Pawel G committed
236 237
   * @param string $entity_type_id
   * @param int $id
Pawel G's avatar
Pawel G committed
238
   *
Pawel G's avatar
Pawel G committed
239
   * @return array
Pawel G's avatar
Pawel G committed
240
   */
241 242
  public function getEntityInstanceSettings($entity_type_id, $id) {
    $entity_types = $this->getConfig('entity_types');
243 244
    $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($id);
    $bundle_name = $this->getEntityInstanceBundleName($entity);
245 246 247 248 249 250 251 252
    if (isset($entity_types[$entity_type_id][$bundle_name]['entities'][$id])) {
      return $entity_types[$entity_type_id][$bundle_name]['entities'][$id];
    }
    else {
      return $this->getBundleSettings($entity_type_id, $bundle_name);
    }
  }

Pawel G's avatar
Pawel G committed
253 254 255
  /**
   * Adds a custom path to the sitemap settings.
   *
Pawel G's avatar
Pawel G committed
256 257
   * @param string $path
   * @param array $settings
Pawel G's avatar
Pawel G committed
258
   *
Pawel G's avatar
Pawel G committed
259
   * @return $this
Pawel G's avatar
Pawel G committed
260
   */
261
  public function addCustomLink($path, $settings) {
Pawel G's avatar
Pawel G committed
262 263 264 265 266 267 268 269
    if (!$this->pathValidator->isValid($path)) {
      // todo: log error.
      return $this;
    }
    if ($path[0] != '/') {
      // todo: log error.
      return $this;
    }
Pawel G's avatar
Pawel G committed
270

271
    $custom_links = $this->getConfig('custom');
Pawel G's avatar
Pawel G committed
272
    foreach ($custom_links as $key => $link) {
273 274 275 276 277 278 279 280 281
      if ($link['path'] == $path) {
        $link_key = $key;
        break;
      }
    }
    $link_key = isset($link_key) ? $link_key : count($custom_links);
    $custom_links[$link_key]['path'] = $path;
    $this->addLinkSettings('entity', $settings, $custom_links[$link_key]);
    $this->saveConfig('custom', $custom_links);
Pawel G's avatar
Pawel G committed
282
    return $this;
283 284
  }

Pawel G's avatar
Pawel G committed
285 286 287
  /**
   * Returns settings for a custom path added to the sitemap settings.
   *
Pawel G's avatar
Pawel G committed
288
   * @param string $path
Pawel G's avatar
Pawel G committed
289
   *
Pawel G's avatar
Pawel G committed
290
   * @return array|false
Pawel G's avatar
Pawel G committed
291
   */
292 293
  public function getCustomLink($path) {
    $custom_links = $this->getConfig('custom');
Pawel G's avatar
Pawel G committed
294
    foreach ($custom_links as $key => $link) {
295 296 297 298 299 300 301
      if ($link['path'] == $path) {
        return $custom_links[$key];
      }
    }
    return FALSE;
  }

Pawel G's avatar
Pawel G committed
302 303 304
  /**
   * Removes a custom path from the sitemap settings.
   *
Pawel G's avatar
Pawel G committed
305
   * @param string $path
Pawel G's avatar
Pawel G committed
306
   *
Pawel G's avatar
Pawel G committed
307 308
   * @return $this
   */
309 310
  public function removeCustomLink($path) {
    $custom_links = $this->getConfig('custom');
Pawel G's avatar
Pawel G committed
311
    foreach ($custom_links as $key => $link) {
312 313 314 315 316 317
      if ($link['path'] == $path) {
        unset($custom_links[$key]);
        $custom_links = array_values($custom_links);
        $this->saveConfig('custom', $custom_links);
      }
    }
Pawel G's avatar
Pawel G committed
318
    return $this;
319 320
  }

Pawel G's avatar
Pawel G committed
321 322
  /**
   * Removes all custom paths from the sitemap settings.
Pawel G's avatar
Pawel G committed
323 324
   *
   * @return $this
Pawel G's avatar
Pawel G committed
325
   */
326 327
  public function removeCustomLinks() {
    $this->saveConfig('custom', []);
Pawel G's avatar
Pawel G committed
328
    return $this;
329 330
  }

Pawel G's avatar
Pawel G committed
331 332 333
  /**
   *
   */
334
  private function addLinkSettings($type, $settings, &$target) {
Pawel G's avatar
Pawel G committed
335
    foreach ($settings as $setting_key => $setting) {
336
      if (in_array($setting_key, self::$allowed_link_settings[$type])) {
Pawel G's avatar
Pawel G committed
337
        switch ($setting_key) {
338
          case 'priority':
339
            if (!FormHelper::isValidPriority($setting)) {
Pawel G's avatar
Pawel G committed
340
              // todo: register error.
341 342 343
              continue;
            }
            break;
Pawel G's avatar
Pawel G committed
344 345

          // todo: add index check.
346 347 348 349 350 351
        }
        $target[$setting_key] = $setting;
      }
    }
  }

Pawel G's avatar
Pawel G committed
352 353 354 355
  /**
   * @param $entity
   * @return mixed
   */
Pawel G's avatar
Pawel G committed
356 357
  public function getEntityInstanceBundleName($entity) {
    return $entity->getEntityTypeId() == 'menu_link_content'
Pawel G's avatar
Pawel G committed
358 359
    // Menu fix.
      ? $entity->getMenuName() : $entity->bundle();
Pawel G's avatar
Pawel G committed
360 361
  }

Pawel G's avatar
Pawel G committed
362 363 364 365
  /**
   * @param $entity
   * @return string
   */
Pawel G's avatar
Pawel G committed
366 367
  public function getBundleEntityTypeId($entity) {
    return $entity->getEntityTypeId() == 'menu'
Pawel G's avatar
Pawel G committed
368 369
    // Menu fix.
      ? 'menu_link_content' : $entity->getEntityType()->getBundleOf();
Pawel G's avatar
Pawel G committed
370 371
  }

372 373 374 375
  /**
   * Returns the whole sitemap, a requested sitemap chunk,
   * or the sitemap index file.
   *
Pawel G's avatar
Pawel G committed
376
   * @param int $chunk_id
377
   *
Pawel G's avatar
Pawel G committed
378
   * @return string|false
Pawel G's avatar
Pawel G committed
379 380 381 382
   *   If no sitemap id provided, either a sitemap index is returned, or the
   *   whole sitemap, if the amount of links does not exceed the max links
   *   setting. If a sitemap id is provided, a sitemap chunk is returned. False
   *   if sitemap is not retrievable from the database.
383
   */
Pawel G's avatar
Pawel G committed
384 385 386
  public function getSitemap($chunk_id = NULL) {
    $chunks = $this->fetchSitemapChunks();
    if (is_null($chunk_id) || !isset($chunks[$chunk_id])) {
387 388

      // Return sitemap index, if there are multiple sitemap chunks.
Pawel G's avatar
Pawel G committed
389 390
      if (count($chunks) > 1) {
        return $this->getSitemapIndex($chunks);
391
      }
Pawel G's avatar
Pawel G committed
392 393
      // Return sitemap if there is only one chunk.
      else {
Pawel G's avatar
Pawel G committed
394 395
        if (isset($chunks[1])) {
          return $chunks[1]->sitemap_string;
396 397
        }
        return FALSE;
398 399
      }
    }
Pawel G's avatar
Pawel G committed
400 401
    // Return specific sitemap chunk.
    else {
Pawel G's avatar
Pawel G committed
402
      return $chunks[$chunk_id]->sitemap_string;
403
    }
404 405
  }

406 407
  /**
   * Generates the sitemap for all languages and saves it to the db.
408 409
   *
   * @param string $from
Pawel G's avatar
Pawel G committed
410 411
   *   Can be 'form', 'cron', 'drush' or 'nobatch'.
   *   This decides how the batch process is to be run.
412
   */
413
  public function generateSitemap($from = 'form') {
414 415 416 417
    $this->sitemapGenerator
      ->setGenerator($this)
      ->setGenerateFrom($from)
      ->startGeneration();
418 419
  }

420
  /**
421 422
   * Generates and returns the sitemap index as string.
   *
Pawel G's avatar
Pawel G committed
423
   * @param array $chunks
Pawel G's avatar
Pawel G committed
424
   *   Sitemap chunks which to generate the index from.
425
   *
426
   * @return string
Pawel G's avatar
Pawel G committed
427
   *   The sitemap index.
428
   */
Pawel G's avatar
Pawel G committed
429
  private function getSitemapIndex($chunks) {
430 431
    return $this->sitemapGenerator
      ->setGenerator($this)
Pawel G's avatar
Pawel G committed
432
      ->generateSitemapIndex($chunks);
Pawel G's avatar
Pawel G committed
433 434
  }

435
  /**
Pawel G's avatar
Pawel G committed
436
   * Returns a specific sitemap setting.
437 438
   *
   * @param string $name
Pawel G's avatar
Pawel G committed
439
   *   Name of the setting, like 'max_links'.
440
   *
441
   * @param mixed $default
Pawel G's avatar
Pawel G committed
442
   *   Value to be returned if the setting does not exist in the configuration.
443
   *
444
   * @return mixed
Pawel G's avatar
Pawel G committed
445
   *   The current setting from db or a default value.
446
   */
447
  public function getSetting($name, $default = FALSE) {
448
    $settings = $this->getConfig('settings');
449
    return isset($settings[$name]) ? $settings[$name] : $default;
450
  }
451

452
  /**
453 454
   * Saves a specific sitemap setting to db.
   *
Pawel G's avatar
Pawel G committed
455
   * @param string $name
Pawel G's avatar
Pawel G committed
456
   *   Setting name, like 'max_links'.
Pawel G's avatar
Pawel G committed
457
   * @param mixed $setting
Pawel G's avatar
Pawel G committed
458
   *   The setting to be saved.
Pawel G's avatar
Pawel G committed
459 460
   *
   * @return $this
461
   */
462 463
  public function saveSetting($name, $setting) {
    $settings = $this->getConfig('settings');
464
    $settings[$name] = $setting;
465
    $this->saveConfig('settings', $settings);
Pawel G's avatar
Pawel G committed
466
    return $this;
467
  }
468

Pawel G's avatar
Pawel G committed
469 470
  /**
   * Returns a 'time ago' string of last timestamp generation.
471
   *
Pawel G's avatar
Pawel G committed
472
   * @return string|false
Pawel G's avatar
Pawel G committed
473
   *   Formatted timestamp of last sitemap generation, otherwise FALSE.
Pawel G's avatar
Pawel G committed
474
   */
475
  public function getGeneratedAgo() {
476
    $chunks = $this->fetchSitemapChunks();
Pawel G's avatar
Pawel G committed
477
    if (isset($chunks[1]->sitemap_created)) {
478
      return $this->dateFormatter
Pawel G's avatar
Pawel G committed
479
        ->formatInterval(REQUEST_TIME - $chunks[1]->sitemap_created);
480 481 482
    }
    return FALSE;
  }
Pawel G's avatar
Pawel G committed
483

484 485 486 487
  /**
   * Returns objects of entity types that can be indexed by the sitemap.
   *
   * @return array
Pawel G's avatar
Pawel G committed
488
   *   Objects of entity types that can be indexed by the sitemap.
489
   */
490 491
  public function getSitemapEntityTypes() {
    $entity_types = $this->entityTypeManager->getDefinitions();
492 493 494 495 496 497 498 499

    foreach ($entity_types as $entity_type_id => $entity_type) {
      if (!$entity_type instanceof ContentEntityTypeInterface || !method_exists($entity_type, 'getBundleEntityType')) {
        unset($entity_types[$entity_type_id]);
      }
    }
    return $entity_types;
  }
500

Pawel G's avatar
Pawel G committed
501 502 503 504
  /**
   * @param $entity_type_id
   * @return bool
   */
505
  public function entityTypeIsAtomic($entity_type_id) {
Pawel G's avatar
Pawel G committed
506 507
    // Menu fix.
    if ($entity_type_id == 'menu_link_content') {
508
      return FALSE;
Pawel G's avatar
Pawel G committed
509
    }
510
    $sitemap_entity_types = $this->getSitemapEntityTypes();
511 512 513 514 515 516
    if (isset($sitemap_entity_types[$entity_type_id])) {
      $entity_type = $sitemap_entity_types[$entity_type_id];
      if (empty($entity_type->getBundleEntityType())) {
        return TRUE;
      }
    }
Pawel G's avatar
Pawel G committed
517 518
    // todo: throw exception.
    return FALSE;
519
  }
Pawel G's avatar
Pawel G committed
520

521
}