You are here

class XmlSitemapLinkStorage in XML sitemap 8

Same name and namespace in other branches
  1. 2.x src/XmlSitemapLinkStorage.php \Drupal\xmlsitemap\XmlSitemapLinkStorage

XmlSitemap link storage service class.

Hierarchy

Expanded class hierarchy of XmlSitemapLinkStorage

1 string reference to 'XmlSitemapLinkStorage'
xmlsitemap.services.yml in ./xmlsitemap.services.yml
xmlsitemap.services.yml
1 service uses XmlSitemapLinkStorage
xmlsitemap.link_storage in ./xmlsitemap.services.yml
Drupal\xmlsitemap\XmlSitemapLinkStorage

File

src/XmlSitemapLinkStorage.php, line 20

Namespace

Drupal\xmlsitemap
View source
class XmlSitemapLinkStorage implements XmlSitemapLinkStorageInterface {

  /**
   * The state store.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  protected $state;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The anonymous user object.
   *
   * @var \Drupal\Core\Session\AnonymousUserSession
   */
  protected $anonymousUser;

  /**
   * The database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $connection;

  /**
   * Constructs a XmlSitemapLinkStorage object.
   *
   * @param \Drupal\Core\State\StateInterface $state
   *   The state handler.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   * @param \Drupal\Core\Database\Connection $connection
   *   The database connection.
   */
  public function __construct(StateInterface $state, ModuleHandlerInterface $module_handler, Connection $connection) {
    $this->state = $state;
    $this->moduleHandler = $module_handler;
    $this->anonymousUser = new AnonymousUserSession();
    $this->connection = $connection;
  }

  /**
   * {@inheritdoc}
   */
  public function create(EntityInterface $entity) {
    if (!isset($entity->xmlsitemap) || !is_array($entity->xmlsitemap)) {
      $entity->xmlsitemap = [];
      if ($entity
        ->id() && ($link = $this
        ->load($entity
        ->getEntityTypeId(), $entity
        ->id()))) {
        $entity->xmlsitemap = $link;
      }
    }
    $settings = xmlsitemap_link_bundle_load($entity
      ->getEntityTypeId(), $entity
      ->bundle());
    $entity->xmlsitemap += [
      'type' => $entity
        ->getEntityTypeId(),
      'id' => (string) $entity
        ->id(),
      'subtype' => $entity
        ->bundle(),
      'status' => (int) $settings['status'],
      'status_default' => (int) $settings['status'],
      'status_override' => 0,
      'priority' => $settings['priority'],
      'priority_default' => $settings['priority'],
      'priority_override' => 0,
      'changefreq' => isset($settings['changefreq']) ? $settings['changefreq'] : 0,
    ];
    if ($entity instanceof EntityChangedInterface) {
      $entity->xmlsitemap['lastmod'] = $entity
        ->getChangedTime();
    }

    // The following values must always be checked because they are volatile.
    try {

      // @todo Could we move this logic to some kind of handler on the menu link entity class?
      if ($entity instanceof MenuLinkContentInterface) {
        $url = $entity
          ->getUrlObject();
        if ($url
          ->isRouted()) {
          if ($url
            ->getRouteName() === '<nolink>') {
            $loc = '';
          }
          else {
            $loc = $url
              ->getInternalPath();
          }
        }
        else {

          // Attempt to transform this to a relative URL.
          $loc = file_url_transform_relative($url
            ->toString());

          // If it could not be transformed into a relative path, disregard it
          // since we cannot store external URLs in the sitemap.
          if (UrlHelper::isExternal($loc)) {
            $loc = '';
          }
        }
        $access = $url
          ->access($this->anonymousUser);
      }
      else {
        $loc = $entity
          ->id() && $entity
          ->hasLinkTemplate('canonical') ? $entity
          ->toUrl()
          ->getInternalPath() : '';
        $access = $entity
          ->access('view', $this->anonymousUser);
      }
    } catch (RouteNotFoundException $e) {
      $loc = '';
    }
    $entity->xmlsitemap['loc'] = '/' . ltrim($loc, '/');
    $entity->xmlsitemap['access'] = $loc && $access;
    $language = $entity
      ->language();
    $entity->xmlsitemap['language'] = !empty($language) ? $language
      ->getId() : LanguageInterface::LANGCODE_NOT_SPECIFIED;
    return $entity->xmlsitemap;
  }

  /**
   * {@inheritdoc}
   */
  public function save(array $link, array $context = []) {
    $link += [
      'access' => 1,
      'status' => 1,
      'status_override' => 0,
      'lastmod' => 0,
      'priority' => XMLSITEMAP_PRIORITY_DEFAULT,
      'priority_override' => 0,
      'changefreq' => 0,
      'changecount' => 0,
      'language' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
    ];

    // Allow other modules to alter the link before saving.
    $this->moduleHandler
      ->alter('xmlsitemap_link', $link, $context);

    // Temporary validation checks.
    // @todo Remove in final?
    if ($link['priority'] < 0 || $link['priority'] > 1) {
      trigger_error("The XML sitemap link for {$link['type']} {$link['id']} has an invalid priority of {$link['priority']}.<br/>" . var_export($link, TRUE), E_USER_ERROR);
    }
    if ($link['changecount'] < 0) {
      trigger_error("The XML sitemap link for {$link['type']} {$link['id']} has a negative changecount value. Please report this to https://www.drupal.org/node/516928.<br/>" . var_export($link, TRUE), E_USER_ERROR);
      $link['changecount'] = 0;
    }

    // Throw an error with the link does not start with a slash.
    // @see \Drupal\Core\Url::fromInternalUri()
    if ($link['loc'][0] !== '/') {
      trigger_error("The XML sitemap link path {$link['loc']} for {$link['type']} {$link['id']} is invalid because it does not start with a slash.", E_USER_ERROR);
    }

    // Check if this is a changed link and set the regenerate flag if necessary.
    if (!$this->state
      ->get('xmlsitemap_regenerate_needed')) {
      $this
        ->checkChangedLink($link, NULL, TRUE);
    }
    $queryStatus = $this->connection
      ->merge('xmlsitemap')
      ->keys([
      'type' => $link['type'],
      'id' => $link['id'],
      'language' => $link['language'],
    ])
      ->fields([
      'loc' => $link['loc'],
      'subtype' => $link['subtype'],
      'access' => (int) $link['access'],
      'status' => (int) $link['status'],
      'status_override' => $link['status_override'],
      'lastmod' => $link['lastmod'],
      'priority' => $link['priority'],
      'priority_override' => $link['priority_override'],
      'changefreq' => $link['changefreq'],
      'changecount' => $link['changecount'],
    ])
      ->execute();
    switch ($queryStatus) {
      case Merge::STATUS_INSERT:
        $this->moduleHandler
          ->invokeAll('xmlsitemap_link_insert', [
          $link,
          $context,
        ]);
        break;
      case Merge::STATUS_UPDATE:
        $this->moduleHandler
          ->invokeAll('xmlsitemap_link_update', [
          $link,
          $context,
        ]);
        break;
    }
    return $link;
  }

  /**
   * {@inheritdoc}
   */
  public function checkChangedLink(array $link, array $original_link = NULL, $flag = FALSE) {
    $changed = FALSE;
    if ($original_link === NULL) {

      // Load only the fields necessary for data to be changed in the sitemap.
      $original_link = $this->connection
        ->queryRange("SELECT loc, access, status, lastmod, priority, changefreq, changecount, language FROM {xmlsitemap} WHERE type = :type AND id = :id", 0, 1, [
        ':type' => $link['type'],
        ':id' => $link['id'],
      ])
        ->fetchAssoc();
    }
    if (!$original_link) {
      if ($link['access'] && $link['status']) {

        // Adding a new visible link.
        $changed = TRUE;
      }
    }
    else {
      if (!($original_link['access'] && $original_link['status']) && $link['access'] && $link['status']) {

        // Changing a non-visible link to a visible link.
        $changed = TRUE;
      }
      elseif ($original_link['access'] && $original_link['status'] && array_diff_assoc($original_link, $link)) {

        // Changing a visible link.
        $changed = TRUE;
      }
    }
    if ($changed && $flag) {
      $this->state
        ->set('xmlsitemap_regenerate_needed', TRUE);
    }
    return $changed;
  }

  /**
   * {@inheritdoc}
   */
  public function checkChangedLinks(array $conditions = [], array $updates = [], $flag = FALSE) {

    // If we are changing status or access, check for negative current values.
    $conditions['status'] = !empty($updates['status']) && empty($conditions['status']) ? 0 : 1;
    $conditions['access'] = !empty($updates['access']) && empty($conditions['access']) ? 0 : 1;
    $query = $this->connection
      ->select('xmlsitemap');
    $query
      ->addExpression('1');
    foreach ($conditions as $field => $value) {
      $operator = is_array($value) ? 'IN' : '=';
      $query
        ->condition($field, $value, $operator);
    }
    $query
      ->range(0, 1);
    $changed = $query
      ->execute()
      ->fetchField();
    if ($changed && $flag) {
      $this->state
        ->set('xmlsitemap_regenerate_needed', TRUE);
    }
    return $changed;
  }

  /**
   * {@inheritdoc}
   */
  public function delete($entity_type, $entity_id, $langcode = NULL) {
    $conditions = [
      'type' => $entity_type,
      'id' => $entity_id,
    ];
    if ($langcode) {
      $conditions['language'] = $langcode;
    }
    return $this
      ->deleteMultiple($conditions);
  }

  /**
   * {@inheritdoc}
   */
  public function deleteMultiple(array $conditions) {
    if (!$this->state
      ->get('xmlsitemap_regenerate_needed')) {
      $this
        ->checkChangedLinks($conditions, [], TRUE);
    }

    // @todo Add a hook_xmlsitemap_link_delete() hook invoked here.
    $query = $this->connection
      ->delete('xmlsitemap');
    foreach ($conditions as $field => $value) {
      $operator = is_array($value) ? 'IN' : '=';
      $query
        ->condition($field, $value, $operator);
    }
    return $query
      ->execute();
  }

  /**
   * {@inheritdoc}
   */
  public function updateMultiple(array $updates = [], array $conditions = [], $check_flag = TRUE) {

    // If we are going to modify a visible sitemap link, we will need to set
    // the regenerate needed flag.
    if ($check_flag && !$this->state
      ->get('xmlsitemap_regenerate_needed')) {
      $this
        ->checkChangedLinks($conditions, $updates, TRUE);
    }

    // Process updates.
    $query = $this->connection
      ->update('xmlsitemap');
    $query
      ->fields($updates);
    foreach ($conditions as $field => $value) {
      $operator = is_array($value) ? 'IN' : '=';
      $query
        ->condition($field, $value, $operator);
    }
    return $query
      ->execute();
  }

  /**
   * {@inheritdoc}
   */
  public function load($entity_type, $entity_id) {
    $link = $this
      ->loadMultiple([
      'type' => $entity_type,
      'id' => $entity_id,
    ]);
    return $link ? reset($link) : FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function loadMultiple(array $conditions = []) {
    $query = $this->connection
      ->select('xmlsitemap');
    $query
      ->fields('xmlsitemap');
    foreach ($conditions as $field => $value) {
      $operator = is_array($value) ? 'IN' : '=';
      $query
        ->condition($field, $value, $operator);
    }
    $links = $query
      ->execute()
      ->fetchAll(\PDO::FETCH_ASSOC);
    return $links;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
XmlSitemapLinkStorage::$anonymousUser protected property The anonymous user object.
XmlSitemapLinkStorage::$connection protected property The database connection.
XmlSitemapLinkStorage::$moduleHandler protected property The module handler.
XmlSitemapLinkStorage::$state protected property The state store.
XmlSitemapLinkStorage::checkChangedLink public function Check if there is sitemap link is changed from the existing data. Overrides XmlSitemapLinkStorageInterface::checkChangedLink
XmlSitemapLinkStorage::checkChangedLinks public function Check if there is a visible sitemap link given a certain set of conditions. Overrides XmlSitemapLinkStorageInterface::checkChangedLinks
XmlSitemapLinkStorage::create public function Create a sitemap link from an entity. Overrides XmlSitemapLinkStorageInterface::create
XmlSitemapLinkStorage::delete public function Delete a specific sitemap link from the database. Overrides XmlSitemapLinkStorageInterface::delete
XmlSitemapLinkStorage::deleteMultiple public function Delete multiple sitemap links from the database. Overrides XmlSitemapLinkStorageInterface::deleteMultiple
XmlSitemapLinkStorage::load public function Load a specific sitemap link from the database. Overrides XmlSitemapLinkStorageInterface::load
XmlSitemapLinkStorage::loadMultiple public function Load sitemap links from the database. Overrides XmlSitemapLinkStorageInterface::loadMultiple
XmlSitemapLinkStorage::save public function Saves or updates a sitemap link. Overrides XmlSitemapLinkStorageInterface::save
XmlSitemapLinkStorage::updateMultiple public function Perform a mass update of sitemap data. Overrides XmlSitemapLinkStorageInterface::updateMultiple
XmlSitemapLinkStorage::__construct public function Constructs a XmlSitemapLinkStorage object.