wse-1.0.x-dev/wse.module

wse.module
<?php

/**
 * @file
 * Provides extra functionality for the Workspaces module.
 */

use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\SortArray;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\workspaces\Entity\Handler\IgnoredWorkspaceHandler;
use Drupal\workspaces\WorkspaceInterface;
use Drupal\wse\PublishedRevisionStorage;
use Drupal\wse\WseEntityUntranslatableFieldsConstraint;
use Drupal\wse\WseFormOperations;
use Drupal\wse\WseWorkspaceListBuilder;

const WSE_STATUS_OPEN = 'open';
const WSE_STATUS_CLOSED = 'closed';

/**
 * Implements hook_validation_constraint_alter().
 */
function wse_validation_constraint_alter(array &$definitions) {
  // @todo Fix in core.
  if (isset($definitions['EntityUntranslatableFields'])) {
    $definitions['EntityUntranslatableFields']['class'] = WseEntityUntranslatableFieldsConstraint::class;
  }
}

/**
 * Gets the status for a workspace.
 */
function wse_workspace_get_status(WorkspaceInterface $workspace) {
  return $workspace->get('status')->value;
}

/**
 * Implements hook_ENTITY_TYPE_presave().
 */
function wse_workspace_presave(WorkspaceInterface $workspace) {
  // Ensure that a workspace's ID is always its UUID, so we can re-use workspace
  // labels automatically.
  // @see \Drupal\wse\EventSubscriber\WorkspacePublishingEventSubscriber::onPostPublish()
  if ($workspace->isNew()) {
    $workspace->set('id', $workspace->uuid());
  }
}

/**
 * Implements hook_entity_base_field_info().
 */
function wse_entity_base_field_info(EntityTypeInterface $entity_type) {
  if ($entity_type->id() === 'workspace') {
    $fields['status'] = BaseFieldDefinition::create('list_string')
      ->setLabel(t('Status'))
      ->setDescription(t('The workspace status.'))
      ->setStorageRequired(TRUE)
      ->setSetting('allowed_values', [
        WSE_STATUS_OPEN => t('Open'),
        WSE_STATUS_CLOSED => t('Closed'),
      ])
      ->setDisplayConfigurable('form', FALSE)
      ->setDisplayConfigurable('view', FALSE)
      ->setDefaultValue(WSE_STATUS_OPEN)
      ->setInitialValue(WSE_STATUS_OPEN);

    return $fields;
  }
}

/**
 * Implements hook_entity_type_build().
 */
function wse_entity_type_build(array &$entity_types) {
  // Allow CRUD operations for various entity types in workspaces.
  $ignored_entity_types = [
    'crop',
    'embedded_paragraphs',
    'events_logging',
    'file',
    'paragraph',
    'variant',
  ];
  foreach ($ignored_entity_types as $entity_type_id) {
    if (isset($entity_types[$entity_type_id])) {
      $entity_types[$entity_type_id]->setHandlerClass('workspace', IgnoredWorkspaceHandler::class);
    }
  }
}

/**
 * Implements hook_entity_type_alter().
 */
function wse_entity_type_alter(array &$entity_types) {
  // Swap the workspace list builder so we can filter on open/closed statuses.
  $entity_types['workspace']->setListBuilderClass(WseWorkspaceListBuilder::class);

  /** @var \Drupal\workspaces\WorkspaceInformationInterface $workspace_info */
  $workspace_info = \Drupal::service('workspaces.information');
  foreach ($entity_types as $entity_type) {
    if ($workspace_info->isEntityTypeSupported($entity_type)) {
      // For supported entity types, add a constraint that prevents them from
      // being changed in a closed workspace.
      $entity_type->addConstraint('WseClosedWorkspace');

      // Add link templates for the 'Move to workspace' and 'Discard changes'
      // operations.
      $base_path = NULL;
      if ($entity_type->hasLinkTemplate('canonical')) {
        $base_path = $entity_type->getLinkTemplate('canonical');
      }
      elseif ($entity_type->hasLinkTemplate('edit-form')) {
        $base_path = $entity_type->getLinkTemplate('edit-form');
      }

      if ($base_path) {
        $entity_type->setLinkTemplate('move-to-workspace', $base_path . '/move-to-workspace/{source_workspace}');
        $entity_type->setLinkTemplate('discard-changes', $base_path . '/discard-changes/{source_workspace}');
      }
    }
    elseif (!$workspace_info->isEntityTypeIgnored($entity_type)) {
      // For unsupported entity types, add a constraint that prevents them from
      // being changed in a workspace.
      $entity_type->addConstraint('WseUnsupportedEntityType');
    }
  }
}

/**
 * Implements hook_entity_field_access().
 */
function wse_entity_field_access($operation, FieldDefinitionInterface $field_definition): AccessResult {
  if ($field_definition->getTargetEntityTypeId() === 'workspace' && $field_definition->getName() === 'parent') {
    $disable_sub_workspaces = \Drupal::config('wse.settings')->get('disable_sub_workspaces') ?? FALSE;
    return AccessResult::forbiddenIf($disable_sub_workspaces);
  }
  return AccessResult::neutral();
}

/**
 * Implements hook_form_FORM_ID_alter() for 'workspace_publish_form'.
 */
function wse_form_workspace_publish_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $config = \Drupal::config('wse.settings');

  $form['clone_on_publish'] = [
    '#type' => 'checkbox',
    '#title' => t('Clone workspace details into a new draft workspace'),
    '#default_value' => $config->get('clone_on_publish'),
  ];

  $form['#validate'][] = 'wse_workspace_publish_form_validate';
  $form['#submit'][] = 'wse_workspace_publish_form_submit';
}

/**
 * Entity form builder to add various information to the workspace.
 */
function wse_workspace_publish_form_validate($form, FormStateInterface $form_state) {
  /** @var \Drupal\workspaces\WorkspaceInterface $workspace */
  $workspace = $form_state->getFormObject()->getWorkspace();

  $workspace->_clone_on_publish = $form_state->getValue('clone_on_publish');
}

/**
 * Submit callback for the workspace publishing form.
 */
function wse_workspace_publish_form_submit($form, FormStateInterface $form_state) {
  // @todo Fix this upstream.
  $form_state->setRedirect('entity.workspace.collection');
}

/**
 * Implements hook_module_implements_alter().
 */
function wse_module_implements_alter(&$implementations, $hook) {
  // Move wse_form_alter() to the end of the list.
  if ($hook === 'form_alter') {
    $temp = $implementations['wse'];
    unset($implementations['wse']);
    $implementations['wse'] = $temp;
  }
}

/**
 * Implements hook_form_alter().
 */
function wse_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  \Drupal::service('class_resolver')
    ->getInstanceFromDefinition(WseFormOperations::class)
    ->formAlter($form, $form_state, $form_id);
}

/**
 * Implements hook_library_info_alter().
 *
 * Includes additional stylesheets to customize the Workspaces toolbar
 * appearance.
 */
function wse_library_info_alter(&$libraries, $extension) {
  if ($extension === 'workspaces' && isset($libraries['drupal.workspaces.toolbar'])) {
    $wse_path = \Drupal::moduleHandler()->getModule('wse')->getPath();
    $libraries['drupal.workspaces.toolbar']['css']['theme']["/$wse_path/css/wse.toolbar.css"] = [];
    $libraries['drupal.workspaces.toolbar']['js']["/$wse_path/js/wse.toolbar.js"] = [];
  }
}

/**
 * Implements hook_toolbar_alter().
 */
function wse_toolbar_alter(&$items) {
  // Always add the 'wse.settings' cache tags to the workspace toolbar tab to
  // account for changes to the 'simplified_toolbar_switcher' option.
  $wse_settings = \Drupal::config('wse.settings');
  $items['workspace']['#cache']['tags'] = Cache::mergeTags($items['workspace']['#cache']['tags'] ?? [], $wse_settings->getCacheTags());
}

/**
 * Implements hook_preprocess_HOOK() for links__action_links.
 */
function wse_preprocess_links__wse_action_links(&$variables) {
  $variables['attributes']['class'][] = 'wse-action-links';
  foreach ($variables['links'] as $delta => $link_item) {
    $variables['links'][$delta]['attributes']->addClass('wse-action-links__item');
  }
}

/**
 * Implements hook_page_attachments().
 */
function wse_page_attachments(array &$attachments) {
  /** @var \Drupal\workspaces\WorkspaceManagerInterface $workspace_manger */
  $workspace_manger = \Drupal::service('workspaces.manager');
  $wse_settings = \Drupal::config('wse.settings');
  if ($wse_settings->get('append_current_workspace_to_url') && $workspace_manger->hasActiveWorkspace()) {
    $attachments['#attached']['drupalSettings']['wse'] = [
      'workspace_id' => $workspace_manger->getActiveWorkspace()->id(),
    ];
    $attachments['#attached']['library'][] = 'wse/current-workspace';
  }
}

/**
 * Implements hook_entity_extra_field_info().
 */
function wse_entity_extra_field_info() {
  $extra_fields = [];
  $enabled_entity_type_ids = Drupal::config('wse.settings')->get('entity_workspace_status') ?? [];
  foreach ($enabled_entity_type_ids as $entity_type_id) {
    $bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo($entity_type_id);
    foreach ($bundles as $bundle => $label) {
      $extra_fields[$entity_type_id][$bundle]['display']['entity_workspace_status'] = [
        'label' => t('Workspace Status'),
        'weight' => 100,
        'visible' => FALSE,
      ];
    }
  }

  return $extra_fields;
}

/**
 * Implements hook_entity_view().
 */
function wse_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
  if ($display->getComponent('entity_workspace_status')) {
    $draft = FALSE;
    $workspace = NULL;
    $switch_to_workspace_link = NULL;

    $workspace_manager = Drupal::service('workspaces.manager');
    if ($workspace_manager->hasActiveWorkspace()) {

      $entity_type_manager = Drupal::entityTypeManager();
      // Has this entity been published?
      $id_key = $entity_type_manager->getDefinition($entity->getEntityTypeId())->getKey('id');
      $query = $entity_type_manager
        ->getStorage($entity->getEntityTypeId())
        ->getQuery();
      $published_version = $query
        ->accessCheck(FALSE)
        ->condition($id_key, $entity->id())
        ->condition('workspace', NULL, 'IS')
        ->execute();

      $latest_revision = $workspace_manager->executeOutsideWorkspace(function () use ($entity) {
        $storage = Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
        return $storage->loadRevision($storage->getLatestRevisionId($entity->id()));
      });

      // Is there a draft?
      $active_workspace = $workspace_manager->getActiveWorkspace();
      if ($latest_revision_workspace = $latest_revision->workspace->entity) {
        $draft = TRUE;
        $workspace = $latest_revision_workspace->label();

        // Are we currently in the drafts workspace?
        $descendants_and_self = Drupal::service('workspaces.repository')->getDescendantsAndSelf($latest_revision_workspace->id());
        if (!$active_workspace || !in_array($active_workspace->id(), $descendants_and_self, TRUE)) {
          $switch_to_workspace_link = $latest_revision_workspace->toUrl('activate-form', ['query' => Drupal::destination()->getAsArray()])->toString();
        }
      }
      $build['entity_workspace_status'] = [
        '#theme' => 'wse_entity_status',
        '#published' => !empty($published_version),
        '#draft' => $draft,
        '#workspace' => $workspace,
        '#switch_to_workspace_link' => $switch_to_workspace_link,
      ];
    }
  }

  if ($entity->getEntityTypeId() === 'workspace') {
    // Don't display content entity operations when viewing a closed workspace.
    if (wse_workspace_get_status($entity) === WSE_STATUS_CLOSED) {
      unset($build['changes']['list']['#header']['operations']);
      foreach (Element::children($build['changes']['list']) as $key) {
        unset($build['changes']['list'][$key]['operations']);
      }
    }

    // Add the 'Move to another workspace' operation.
    $modal_attributes = [
      'attributes' => [
        'class' => ['use-ajax'],
        'data-dialog-type' => 'modal',
        'data-dialog-options' => Json::encode([
          'width' => 500,
        ]),
      ],
    ];

    $module_handler = \Drupal::moduleHandler();
    $trash_enabled = $module_handler->moduleExists('trash');
    $diff_enabled = $module_handler->moduleExists('diff');

    foreach (Element::children($build['changes']['list']) as $key) {
      $tracked_entity = $build['changes']['list'][$key]['#entity'];
      $entity_is_deleted = $trash_enabled && trash_entity_is_deleted($tracked_entity);

      if ($entity_is_deleted && isset($build['changes']['list'][$key]['operations']['#links']['restore'])) {
        $build['changes']['list'][$key]['operations']['#links']['restore'] += $modal_attributes;
        $build['changes']['list'][$key]['operations']['#links']['restore']['url']->setOption('query', \Drupal::destination()->getAsArray());

        // Don't show any extra operations for deleted entities.
        continue;
      }

      if ($tracked_entity->access('view') && $diff_enabled) {
        $build['changes']['list'][$key]['operations']['#links']['revision_diff'] = [
          'title' => t('View changes'),
          'weight' => -5,
          'url' => Url::fromRoute("entity.{$tracked_entity->getEntityTypeId()}.workspace.revisions_diff", [
            $tracked_entity->getEntityTypeId() => $tracked_entity->id(),
            'source_workspace' => $entity->id(),
          ]),
        ] + $modal_attributes;
        $build['changes']['list'][$key]['operations']['#links']['revision_diff']['attributes']['data-dialog-options'] = Json::encode([
          'width' => 1000,
        ]);
      }

      if ($entity->access('update')) {
        if ($tracked_entity->hasLinkTemplate('move-to-workspace')) {
          $build['changes']['list'][$key]['operations']['#links']['move_to_workspace'] = [
            'title' => t('Move to another workspace'),
            'weight' => 15,
            'url' => $tracked_entity->toUrl('move-to-workspace')
              ->setRouteParameter('source_workspace', $entity->id()),
          ] + $modal_attributes;
        }

        if ($tracked_entity->hasLinkTemplate('discard-changes')) {
          $build['changes']['list'][$key]['operations']['#links']['discard_changes'] = [
            'title' => t('Discard changes'),
            'weight' => 15,
            'url' => $tracked_entity->toUrl('discard-changes')
              ->setRouteParameter('source_workspace', $entity->id()),
          ] + $modal_attributes;
        }
      }
      if (!empty($build['changes']['list'][$key]['operations']['#links'])) {
        uasort($build['changes']['list'][$key]['operations']['#links'], [SortArray::class, 'sortByWeightElement']);
      }
    }
  }
}

/**
 * Implements hook_theme().
 */
function wse_theme($existing, $type, $theme, $path) {
  return [
    'wse_entity_status' => [
      'variables' => [
        'published' => NULL,
        'draft' => NULL,
        'workspace' => NULL,
        'switch_to_workspace_link' => NULL,
      ],
    ],
  ];
}

/**
 * Implements hook_ENTITY_TYPE_predelete().
 */
function wse_workspace_predelete(WorkspaceInterface $workspace) {
  // Store a list of closed workspaces or sub-workspaces that are being deleted.
  if (wse_workspace_get_status($workspace) === WSE_STATUS_CLOSED || $workspace->hasParent()) {
    $state = \Drupal::state();
    $deleted_closed_workspace_ids = $state->get('wse.deleted_closed', []);
    $deleted_closed_workspace_ids[$workspace->id()] = $workspace->id();
    $state->set('wse.deleted_closed', $deleted_closed_workspace_ids);
  }
}

/**
 * Implements hook_ENTITY_TYPE_delete().
 *
 * Deletes data about published revisions when a workspace gets deleted.
 */
function wse_workspace_delete(WorkspaceInterface $workspace) {
  \Drupal::database()->delete(PublishedRevisionStorage::TABLE)
    ->condition('workspace_id', $workspace->id())
    ->execute();
}

/**
 * Implements hook_field_info_alter().
 */
function wse_field_info_alter(&$definitions) {
  if (isset($definitions['entity_reference'])) {
    unset($definitions['entity_reference']['constraints']['EntityReferenceSupportedNewEntities']);
    $definitions['entity_reference']['constraints']['WseEntityReferenceSupportedNewEntities'] = [];
  }
}

/**
 * Implements hook_entity_access().
 */
function wse_entity_access(EntityInterface $entity, $operation, AccountInterface $account): AccessResultInterface {
  if (
    !\Drupal::service('workspaces.information')->isEntitySupported($entity)
    || !in_array($operation, ['revert revision', 'revert', 'delete revision'])
  ) {
    return AccessResult::neutral();
  }

  /** @var \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association */
  $workspace_association = \Drupal::service('workspaces.association');
  $tracking_workspace_ids = $workspace_association->getEntityTrackingWorkspaceIds($entity, TRUE);
  if ($tracking_workspace_id = reset($tracking_workspace_ids)) {
    $active_workspace = \Drupal::service('workspaces.manager')->getActiveWorkspace();
    if (!$active_workspace || $active_workspace->id() != $tracking_workspace_id) {
      return AccessResult::forbidden()->addCacheContexts(['workspace']);
    }
  }

  return AccessResult::neutral()->addCacheContexts(['workspace']);
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc