clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
<?php
namespace Drupal\log\ContextProvider;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextProviderInterface;
use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\Core\Plugin\Context\EntityContextDefinition;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\log\Entity\Log;
/**
* Sets the current log as a context on log routes.
*/
class LogRouteContext implements ContextProviderInterface {
use StringTranslationTrait;
/**
* The route match object.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* Constructs a new LogRouteContext.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match object.
*/
public function __construct(RouteMatchInterface $route_match) {
$this->routeMatch = $route_match;
}
/**
* {@inheritdoc}
*/
public function getRuntimeContexts(array $unqualified_context_ids) {
$result = [];
$context_definition = EntityContextDefinition::create('log')->setRequired(FALSE);
$value = NULL;
if (($route_object = $this->routeMatch->getRouteObject())) {
$route_contexts = $route_object->getOption('parameters');
// Check for a log revision parameter first.
if (isset($route_contexts['log_revision']) && $revision = $this->routeMatch->getParameter('log_revision')) {
$value = $revision;
}
elseif (isset($route_contexts['log']) && $log = $this->routeMatch->getParameter('log')) {
$value = $log;
}
elseif ($this->routeMatch->getRouteName() == 'log.add') {
$log_type = $this->routeMatch->getParameter('log_type');
$value = Log::create(['type' => $log_type->id()]);
}
}
$cacheability = new CacheableMetadata();
$cacheability->setCacheContexts(['route']);
$context = new Context($context_definition, $value);
$context->addCacheableDependency($cacheability);
$result['log'] = $context;
return $result;
}
/**
* {@inheritdoc}
*/
public function getAvailableContexts() {
$context = EntityContext::fromEntityTypeId('log', $this->t('Log from URL'));
return ['log' => $context];
}
}

View File

@@ -0,0 +1,122 @@
<?php
namespace Drupal\log\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Connection;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
/**
* Returns autocomplete responses for log names.
*/
class LogAutocompleteController extends ControllerBase {
/**
* The database service.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* Constructs a LogAutocompleteController object.
*
* @param \Drupal\Core\Database\Connection $database
* A database connection.
*/
public function __construct(Connection $database) {
$this->database = $database;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('database')
);
}
/**
* Retrieves suggestions for log name autocompletion.
*
* @param string $log_bundle
* The log bundle name.
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* A JSON response containing autocomplete suggestions.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function autocomplete(string $log_bundle, Request $request) {
$matches = [];
if ($input = $request->query->get('q')) {
// A regular database query is used so the results returned can be sorted
// by usage.
$table_mapping = $this->entityTypeManager()->getStorage('log')->getTableMapping();
$query = $this->database->select($table_mapping->getDataTable(), 'log_field_data');
$query->fields('log_field_data', ['name']);
$query->addExpression('COUNT(name)', 'count');
$query->condition('type', $log_bundle);
$query->condition('name', '%' . $this->database->escapeLike($input) . '%', 'LIKE');
// Because a regular database query is used to sort by the usage of the
// log names, a minimal access control is done here.
// If the user has administer log or can view any log entity from any
// bundle, no further condition is added, if the user can see their own
// entities, the query is restricted by user, otherwise an empty set is
// returned.
switch ($this->typeOfAccess($log_bundle)) {
case 'none':
return new JsonResponse([]);
case 'own':
$query->condition('uid', $this->currentUser()->id());
break;
case 'any':
default:
// Nothing to do, full access.
}
$query->groupBy('name');
$query->orderBy('count', 'DESC');
$query->orderBy('name', 'ASC');
$matches = $query->execute()->fetchCol();
}
return new JsonResponse($matches);
}
/**
* Helper function that returns what filter must be applied to the user query.
*
* @param string $log_bundle
* The log bundle.
*
* @return string
* 'any' => Full access.
* 'own' => Access to own logs.
* 'none' => No access to logs.
*/
protected function typeOfAccess(string $log_bundle) {
$account = $this->currentUser();
if ($account->hasPermission('administer log') || $account->hasPermission('view any ' . $log_bundle . ' log')) {
return 'any';
}
if ($account->hasPermission('view own ' . $log_bundle . ' log')) {
return 'own';
}
return 'none';
}
}

View File

@@ -0,0 +1,289 @@
<?php
namespace Drupal\log\Entity;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\RevisionLogEntityTrait;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\entity\Revision\RevisionableContentEntityBase;
use Drupal\user\EntityOwnerTrait;
/**
* Defines the Log entity.
*
* @ingroup log
*
* @ContentEntityType(
* id = "log",
* label = @Translation("Log"),
* bundle_label = @Translation("Log type"),
* label_collection = @Translation("Logs"),
* label_singular = @Translation("log"),
* label_plural = @Translation("logs"),
* label_count = @PluralTranslation(
* singular = "@count log",
* plural = "@count logs",
* ),
* handlers = {
* "storage" = "Drupal\log\LogStorage",
* "access" = "\Drupal\entity\UncacheableEntityAccessControlHandler",
* "list_builder" = "\Drupal\log\LogListBuilder",
* "permission_provider" = "\Drupal\entity\UncacheableEntityPermissionProvider",
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "views_data" = "Drupal\log\LogViewsData",
* "form" = {
* "add" = "Drupal\log\Form\LogForm",
* "edit" = "Drupal\log\Form\LogForm",
* "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
* },
* "route_provider" = {
* "default" = "Drupal\entity\Routing\AdminHtmlRouteProvider",
* "revision" = "\Drupal\entity\Routing\RevisionRouteProvider",
* "delete-multiple" = "\Drupal\entity\Routing\DeleteMultipleRouteProvider",
* },
* "local_task_provider" = {
* "default" = "\Drupal\entity\Menu\DefaultEntityLocalTaskProvider",
* },
* },
* base_table = "log",
* data_table = "log_field_data",
* revision_table = "log_revision",
* translatable = TRUE,
* revisionable = TRUE,
* show_revision_ui = TRUE,
* admin_permission = "administer log",
* entity_keys = {
* "id" = "id",
* "revision" = "revision_id",
* "bundle" = "type",
* "label" = "name",
* "owner" = "uid",
* "uuid" = "uuid",
* "langcode" = "langcode",
* },
* bundle_entity_type = "log_type",
* field_ui_base_route = "entity.log_type.edit_form",
* common_reference_target = TRUE,
* permission_granularity = "bundle",
* links = {
* "canonical" = "/log/{log}",
* "add-page" = "/log/add",
* "add-form" = "/log/add/{log_type}",
* "collection" = "/admin/content/log",
* "delete-form" = "/log/{log}/delete",
* "delete-multiple-form" = "/log/delete",
* "edit-form" = "/log/{log}/edit",
* "revision" = "/log/{log}/revisions/{log_revision}/view",
* "revision-revert-form" = "/log/{log}/revisions/{log_revision}/revert",
* "version-history" = "/log/{log}/revisions",
* },
* revision_metadata_keys = {
* "revision_user" = "revision_user",
* "revision_created" = "revision_created",
* "revision_log_message" = "revision_log_message"
* },
* )
*/
class Log extends RevisionableContentEntityBase implements LogInterface {
use EntityChangedTrait;
use EntityOwnerTrait;
use RevisionLogEntityTrait;
/**
* {@inheritdoc}
*/
public function label() {
return $this->getName();
}
/**
* {@inheritdoc}
*/
public function getName() {
return $this->get('name')->value;
}
/**
* {@inheritdoc}
*/
public function setName($name) {
$this->set('name', $name);
return $this;
}
/**
* {@inheritdoc}
*/
public function getCreatedTime() {
return $this->get('created')->value;
}
/**
* {@inheritdoc}
*/
public function setCreatedTime($timestamp) {
$this->set('created', $timestamp);
return $this;
}
/**
* {@inheritdoc}
*/
public function getTypeNamePattern() {
/** @var \Drupal\log\Entity\LogTypeInterface $type */
$type = \Drupal::entityTypeManager()
->getStorage('log_type')
->load($this->bundle());
return $type->getNamePattern();
}
/**
* {@inheritdoc}
*/
public function getBundleLabel() {
/** @var \Drupal\log\Entity\LogTypeInterface $type */
$type = \Drupal::entityTypeManager()
->getStorage('log_type')
->load($this->bundle());
return $type->label();
}
/**
* {@inheritdoc}
*/
public static function getCurrentUserId() {
return [\Drupal::currentUser()->id()];
}
/**
* {@inheritdoc}
*/
public static function getRequestTime() {
return \Drupal::time()->getRequestTime();
}
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields += static::ownerBaseFieldDefinitions($entity_type);
$fields += static::revisionLogBaseFieldDefinitions($entity_type);
$fields['name'] = BaseFieldDefinition::create('string')
->setLabel(t('Name'))
->setDescription(t('The name of the log. Leave this blank to automatically generate a name.'))
->setRevisionable(TRUE)
->setTranslatable(TRUE)
->setDefaultValue('')
->setSetting('max_length', 255)
->setSetting('text_processing', 0)
->setDisplayOptions('view', [
'label' => 'hidden',
'type' => 'string',
'weight' => -5,
])
->setDisplayOptions('form', [
'type' => 'string_textfield',
'weight' => -5,
])
->setDisplayConfigurable('form', TRUE);
$fields['timestamp'] = BaseFieldDefinition::create('timestamp')
->setLabel(t('Timestamp'))
->setDescription(t('Timestamp of the event being logged.'))
->setDefaultValueCallback(static::class . '::getRequestTime')
->setRevisionable(TRUE)
->setRequired(TRUE)
->setDisplayOptions('view', [
'label' => 'above',
'type' => 'timestamp',
'weight' => 10,
])
->setDisplayOptions('form', [
'type' => 'datetime_timestamp',
'weight' => 10,
])
->setDisplayConfigurable('view', TRUE)
->setDisplayConfigurable('form', TRUE);
$fields['status'] = BaseFieldDefinition::create('state')
->setLabel(t('Status'))
->setDescription(t('Indicates the status of the log.'))
->setRevisionable(TRUE)
->setRequired(TRUE)
->setSetting('max_length', 255)
->setDisplayOptions('view', [
'label' => 'hidden',
'type' => 'state_transition_form',
'weight' => 10,
])
->setDisplayOptions('form', [
'type' => 'options_select',
'weight' => 11,
])
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE)
->setSetting('workflow_callback', ['\Drupal\log\Entity\Log', 'getWorkflowId']);
$fields['uid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Authored by'))
->setDescription(t('The user ID of author of the log.'))
->setRevisionable(TRUE)
->setSetting('target_type', 'user')
->setSetting('handler', 'default')
->setDefaultValueCallback('Drupal\log\Entity\Log::getCurrentUserId')
->setDisplayOptions('view', [
'label' => 'hidden',
'type' => 'author',
'weight' => 0,
])
->setDisplayOptions('form', [
'type' => 'entity_reference_autocomplete',
'weight' => 12,
'settings' => [
'match_operator' => 'CONTAINS',
'size' => '60',
'autocomplete_type' => 'tags',
'placeholder' => '',
],
])
->setDisplayConfigurable('view', TRUE);
$fields['created'] = BaseFieldDefinition::create('created')
->setLabel(t('Authored on'))
->setDescription(t('The time that the log was created.'))
->setRevisionable(TRUE)
->setDefaultValueCallback(static::class . '::getRequestTime')
->setDisplayOptions('form', [
'type' => 'datetime_timestamp',
'weight' => 13,
])
->setDisplayConfigurable('view', TRUE)
->setDisplayConfigurable('form', TRUE);
$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Changed'))
->setDescription(t('The time the log was last edited.'))
->setRevisionable(TRUE);
return $fields;
}
/**
* Gets the workflow ID for the state field.
*
* @param \Drupal\log\Entity\LogInterface $log
* The log entity.
*
* @return string
* The workflow ID.
*/
public static function getWorkflowId(LogInterface $log) {
$workflow = LogType::load($log->bundle())->getWorkflowId();
return $workflow;
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Drupal\log\Entity;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\RevisionLogInterface;
use Drupal\user\EntityOwnerInterface;
/**
* Provides an interface for defining Log entities.
*
* @ingroup log
*/
interface LogInterface extends ContentEntityInterface, EntityChangedInterface, RevisionLogInterface, EntityOwnerInterface {
/**
* Gets the log name.
*
* @return string
* The log name.
*/
public function getName();
/**
* Sets the log name.
*
* @param string $name
* The log name.
*
* @return \Drupal\log\Entity\LogInterface
* The log entity.
*/
public function setName($name);
/**
* Gets the log creation timestamp.
*
* @return int
* Creation timestamp of the log.
*/
public function getCreatedTime();
/**
* Sets the log creation timestamp.
*
* @param int $timestamp
* Creation timestamp of the log.
*
* @return \Drupal\log\Entity\LogInterface
* The log entity.
*/
public function setCreatedTime($timestamp);
/**
* Gets the name pattern from the log type.
*
* @return string
* The name pattern.
*/
public function getTypeNamePattern();
/**
* Gets the label of the the log type.
*
* @return string
* The label of the log type.
*/
public function getBundleLabel();
}

View File

@@ -0,0 +1,193 @@
<?php
namespace Drupal\log\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
use Drupal\Core\Entity\EntityStorageInterface;
/**
* Defines the Log type entity.
*
* @ConfigEntityType(
* id = "log_type",
* label = @Translation("Log type"),
* label_collection = @Translation("Log types"),
* label_singular = @Translation("log type"),
* label_plural = @Translation("log types"),
* label_count = @PluralTranslation(
* singular = "@count log type",
* plural = "@count log types",
* ),
* handlers = {
* "list_builder" = "Drupal\log\LogTypeListBuilder",
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "form" = {
* "add" = "Drupal\log\Form\LogTypeForm",
* "edit" = "Drupal\log\Form\LogTypeForm",
* "delete" = "\Drupal\Core\Entity\EntityDeleteForm",
* },
* "route_provider" = {
* "default" = "Drupal\entity\Routing\DefaultHtmlRouteProvider",
* },
* },
* admin_permission = "administer log types",
* config_prefix = "type",
* bundle_of = "log",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "uuid" = "uuid"
* },
* links = {
* "canonical" = "/admin/structure/log-type/{log_type}",
* "add-form" = "/admin/structure/log-type/add",
* "edit-form" = "/admin/structure/log-type/{log_type}/edit",
* "delete-form" = "/admin/structure/log-type/{log_type}/delete",
* "collection" = "/admin/structure/log-type"
* },
* config_export = {
* "id",
* "label",
* "description",
* "name_pattern",
* "workflow",
* "new_revision",
* }
* )
*/
class LogType extends ConfigEntityBundleBase implements LogTypeInterface {
/**
* The Log type ID.
*
* @var string
*/
protected $id;
/**
* The Log type label.
*
* @var string
*/
protected $label;
/**
* A brief description of this log type.
*
* @var string
*/
protected $description;
/**
* Pattern for auto-generating the log name, using tokens.
*
* @var string
*/
protected $name_pattern;
/**
* The log type workflow ID.
*
* @var string
*/
protected $workflow;
/**
* Default value of the 'Create new revision' checkbox of this log type.
*
* @var bool
*/
protected $new_revision = TRUE;
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->description;
}
/**
* {@inheritdoc}
*/
public function setDescription($description) {
return $this->set('description', $description);
}
/**
* {@inheritdoc}
*/
public function getNamePattern() {
return $this->name_pattern;
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
// If the log type id changed, update all existing logs of that type.
if ($update && $this->getOriginalId() != $this->id()) {
$update_count = \Drupal::entityTypeManager()->getStorage('log')->updateType($this->getOriginalId(), $this->id());
if ($update_count) {
\Drupal::messenger()->addMessage(\Drupal::translation()->formatPlural($update_count,
'Changed the log type of 1 post from %old-type to %type.',
'Changed the log type of @count posts from %old-type to %type.',
[
'%old-type' => $this->getOriginalId(),
'%type' => $this->id(),
]));
}
}
if ($update) {
// Clear the cached field definitions as some settings affect the field
// definitions.
\Drupal::entityTypeManager()->clearCachedDefinitions();
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
}
}
/**
* {@inheritdoc}
*/
public function getWorkflowId() {
return $this->workflow;
}
/**
* {@inheritdoc}
*/
public function setWorkflowId($workflow_id) {
$this->workflow = $workflow_id;
return $this;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
parent::calculateDependencies();
// The log type must depend on the module that provides the workflow.
$workflow_manager = \Drupal::service('plugin.manager.workflow');
$workflow = $workflow_manager->createInstance($this->getWorkflowId());
$this->calculatePluginDependencies($workflow);
return $this;
}
/**
* {@inheritdoc}
*/
public function shouldCreateNewRevision() {
return $this->new_revision;
}
/**
* {@inheritdoc}
*/
public function setNewRevision($new_revision) {
return $this->set('new_revision', $new_revision);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Drupal\log\Entity;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\EntityDescriptionInterface;
use Drupal\Core\Entity\RevisionableEntityBundleInterface;
/**
* Provides an interface for defining Log type entities.
*/
interface LogTypeInterface extends ConfigEntityInterface, EntityDescriptionInterface, RevisionableEntityBundleInterface {
/**
* Returns the name pattern for a log type.
*
* @return string
* The log type name pattern.
*/
public function getNamePattern();
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Drupal\log\Event;
use Drupal\Component\EventDispatcher\Event;
use Drupal\log\Entity\LogInterface;
/**
* Event that is fired by log save, delete and clone operations.
*/
class LogEvent extends Event {
const PRESAVE = 'log_presave';
const INSERT = 'log_insert';
const UPDATE = 'log_update';
const DELETE = 'log_delete';
const CLONE = 'log_clone';
/**
* The Log entity.
*
* @var \Drupal\log\Entity\LogInterface
*/
public LogInterface $log;
/**
* Constructs the object.
*
* @param \Drupal\log\Entity\LogInterface $log
* The Log entity.
*/
public function __construct(LogInterface $log) {
$this->log = $log;
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace Drupal\log\Form;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base form class for configurable actions.
*/
abstract class LogActionFormBase extends ConfirmFormBase {
/**
* The tempstore factory.
*
* @var \Drupal\Core\TempStore\PrivateTempStoreFactory
*/
protected $tempStoreFactory;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $user;
/**
* The logs to clone.
*
* @var \Drupal\log\Entity\LogInterface[]
*/
protected $logs;
/**
* The action id.
*
* @var string
*/
protected $actionId;
/**
* Constructs a LogActionFormBase form object.
*
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
*/
public function __construct(PrivateTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $entity_type_manager, AccountInterface $user) {
$this->tempStoreFactory = $temp_store_factory;
$this->entityTypeManager = $entity_type_manager;
$this->user = $user;
$this->logs = $this->tempStoreFactory->get($this->actionId)->get($this->user->id());
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('tempstore.private'),
$container->get('entity_type.manager'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('entity.log.collection');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return '';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['date'] = [
'#type' => 'datetime',
'#title' => $this->t('New date'),
'#default_value' => new DrupalDateTime('midnight'),
'#required' => TRUE,
];
$form['revision_message'] = [
'#type' => 'textarea',
'#title' => $this->t('Revision message'),
'#description' => $this->t("Optionally add a message to describe this change. This will appear in the log's revisions."),
'#weight' => 10,
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->tempStoreFactory->get($this->actionId)->delete($this->user->id());
$form_state->setRedirectUrl($this->getCancelUrl());
}
}

View File

@@ -0,0 +1,126 @@
<?php
namespace Drupal\log\Form;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\log\Event\LogEvent;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Provides a log clone confirmation form.
*/
class LogCloneActionForm extends LogActionFormBase {
/**
* The action id.
*
* @var string
*/
protected $actionId = 'log_clone_action';
/**
* The event dispatcher service.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* Constructs a LogCloneActionForm form object.
*
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher service.
*/
public function __construct(PrivateTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $entity_type_manager, AccountInterface $user, EventDispatcherInterface $event_dispatcher) {
parent::__construct($temp_store_factory, $entity_type_manager, $user);
$this->eventDispatcher = $event_dispatcher;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('tempstore.private'),
$container->get('entity_type.manager'),
$container->get('current_user'),
$container->get('event_dispatcher'),
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'log_clone_action_form';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->formatPlural(count($this->logs), 'Are you sure you want to clone this log?', 'Are you sure you want to clone these logs?');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Clone');
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Filter out logs the user doesn't have access to.
$inaccessible_logs = [];
$accessible_logs = [];
$current_user = $this->currentUser();
foreach ($this->logs as $log) {
if (!$log->access('view', $current_user) || !$log->access('create', $current_user)) {
$inaccessible_logs[] = $log;
continue;
}
$accessible_logs[] = $log;
}
/** @var \Drupal\Core\Datetime\DrupalDateTime $new_date */
if ($form_state->getValue('confirm') && !empty($accessible_logs)) {
$new_date = $form_state->getValue('date');
$count = count($this->logs);
foreach ($accessible_logs as $log) {
$cloned_log = $log->createDuplicate();
$cloned_log->set('timestamp', $new_date->getTimestamp());
$cloned_log->setOwnerId($current_user->id());
$cloned_log->setRevisionLogMessage($form_state->getValue('revision_message'));
// Dispatch the log_clone event.
$event = new LogEvent($cloned_log);
$this->eventDispatcher->dispatch($event, LogEvent::CLONE);
$event->log->save();
}
$this->messenger()->addMessage($this->formatPlural($count, 'Cloned 1 log.', 'Cloned @count logs.'));
}
// Add warning message if there were inaccessible logs.
if (!empty($inaccessible_logs)) {
$inaccessible_count = count($inaccessible_logs);
$this->messenger()->addWarning($this->formatPlural($inaccessible_count, 'Could not clone @count log because you do not have the necessary permissions.', 'Could not clone @count logs because you do not have the necessary permissions.'));
}
parent::submitForm($form, $form_state);
}
}

View File

@@ -0,0 +1,167 @@
<?php
namespace Drupal\log\Form;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Field\FieldFilteredMarkup;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form controller for Log entities.
*
* @ingroup log
*/
class LogForm extends ContentEntityForm {
/**
* The Current User object.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* Constructs a LogForm object.
*
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The entity repository.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle service.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter service.
*/
public function __construct(EntityRepositoryInterface $entity_repository, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL, AccountInterface $current_user, DateFormatterInterface $date_formatter) {
parent::__construct($entity_repository, $entity_type_bundle_info, $time);
$this->currentUser = $current_user;
$this->dateFormatter = $date_formatter;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.repository'),
$container->get('entity_type.bundle.info'),
$container->get('datetime.time'),
$container->get('current_user'),
$container->get('date.formatter')
);
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
/** @var \Drupal\log\Entity\LogInterface $log */
$log = $this->entity;
// Changed must be sent to the client, for later overwrite error checking.
$form['changed'] = [
'#type' => 'hidden',
'#default_value' => $log->getChangedTime(),
];
$form += parent::form($form, $form_state);
// Set autocomplete for log names.
if (isset($form['name']) && $this->moduleHandler->moduleExists('views')) {
$num_of_logs = $this->entityTypeManager->getStorage('log')
->getQuery()
->condition('type', $log->bundle())
->count()
->accessCheck(TRUE)
->execute();
if ($num_of_logs > 0) {
$form['name']['widget'][0]['value']['#description'] = FieldFilteredMarkup::create($form['name']['widget'][0]['value']['#description'] . ' ' . $this->t('As you type, frequently used log names will be suggested.'));
$form['name']['widget'][0]['value']['#autocomplete_route_name'] = 'log.autocomplete.name';
$form['name']['widget'][0]['value']['#autocomplete_route_parameters'] = ['log_bundle' => $log->bundle()];
}
}
$form['advanced']['#attributes']['class'][] = 'entity-meta';
$form['meta'] = [
'#type' => 'details',
'#group' => 'advanced',
'#weight' => -10,
'#title' => $this->t('Status'),
'#attributes' => ['class' => ['entity-meta__header']],
'#tree' => TRUE,
'#access' => $this->currentUser->hasPermission('administer log'),
];
$form['meta']['status'] = [
'#type' => 'item',
'#markup' => $log->get('status')->first()->getLabel(),
'#access' => !$log->isNew(),
'#$log' => ['class' => ['entity-meta__title']],
];
$form['meta']['changed'] = [
'#type' => 'item',
'#title' => $this->t('Last saved'),
'#markup' => !$log->isNew() ? $this->dateFormatter->format($log->getChangedTime(), 'short') : $this->t('Not saved yet'),
'#wrapper_attributes' => ['class' => ['entity-meta__last-saved']],
];
$form['meta']['author'] = [
'#type' => 'item',
'#title' => $this->t('Author'),
'#markup' => $log->getOwner()->getAccountName(),
'#wrapper_attributes' => ['class' => ['entity-meta__author']],
];
// Author information for administrators.
$form['author'] = [
'#type' => 'details',
'#title' => $this->t('Authoring information'),
'#group' => 'advanced',
'#weight' => 90,
'#optional' => TRUE,
];
if (isset($form['uid'])) {
$form['uid']['#group'] = 'author';
}
if (isset($form['created'])) {
$form['created']['#group'] = 'author';
}
$form['#attached']['library'][] = 'core/drupal.form';
return $form;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$status = parent::save($form, $form_state);
$entity_url = $this->entity->toUrl()->setAbsolute()->toString();
$this->messenger()->addMessage($this->t(
'Saved log: <a href=":url">%label</a>',
[
':url' => $entity_url,
'%label' => $this->entity->label(),
],
));
$form_state->setRedirectUrl($this->entity->toUrl());
return $status;
}
}

View File

@@ -0,0 +1,182 @@
<?php
namespace Drupal\log\Form;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a log reschedule confirmation form.
*/
class LogRescheduleActionForm extends LogActionFormBase {
/**
* The action id.
*
* @var string
*/
protected $actionId = 'log_reschedule_action';
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'log_reschedule_action_form';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->formatPlural(count($this->logs), 'Are you sure you want to reschedule this log?', 'Are you sure you want to reschedule these logs?');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Reschedule');
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
$form['type_of_date'] = [
'#type' => 'checkbox',
'#title' => $this->t('Reschedule by a relative date'),
'#weight' => -10,
];
$form['title'] = [
'#type' => 'html_tag',
'#tag' => 'h4',
'#value' => $form['date']['#title'],
'#weight' => -9,
];
// Datetime fields need to be wrapped for #states to work.
// @see https://www.drupal.org/project/drupal/issues/2419131
$form['absolute'] = [
'#type' => 'container',
'#states' => [
'visible' => [
':input[name="type_of_date"]' => ['checked' => FALSE],
],
],
];
$form['absolute']['date'] = $form['date'];
unset($form['absolute']['date']['#title']);
$form['absolute']['date']['#required'] = FALSE;
unset($form['date']);
$form['relative'] = [
'#type' => 'container',
'#attributes' => [
'class' => ['container-inline'],
],
'#states' => [
'visible' => [
':input[name="type_of_date"]' => ['checked' => TRUE],
],
],
];
$form['relative']['amount'] = [
'#type' => 'number',
'#size' => 4,
];
$form['relative']['time'] = [
'#type' => 'select',
'#options' => [
'hour' => $this->t('Hours'),
'day' => $this->t('Days'),
'week' => $this->t('Weeks'),
'month' => $this->t('Months'),
'year' => $this->t('Years'),
],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$type_of_date = $form_state->getValue('type_of_date');
if ($type_of_date) {
$amount = $form_state->getValue('amount');
$time = $form_state->getValue('time');
if (empty($amount)) {
$form_state->setError($form['relative']['amount'], 'Please enter the amount of time for rescheduling.');
}
if (empty($time)) {
$form_state->setError($form['relative']['amount'], 'Please enter the time units for rescheduling.');
}
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Filter out logs the user doesn't have access to.
$inaccessible_logs = [];
$accessible_logs = [];
$current_user = $this->currentUser();
foreach ($this->logs as $log) {
if (!$log->get('timestamp')->access('edit', $current_user) || !$log->get('status')->access('edit', $current_user) || !$log->access('update', $current_user)) {
$inaccessible_logs[] = $log;
continue;
}
$accessible_logs[] = $log;
}
if ($form_state->getValue('confirm') && !empty($accessible_logs)) {
$count = count($accessible_logs);
$type_of_date = $form_state->getValue('type_of_date');
if ($type_of_date) {
$amount = $form_state->getValue('amount');
$time = $form_state->getValue('time');
$sign = ($amount >= 0) ? '+' : '';
foreach ($accessible_logs as $log) {
$new_date = new DrupalDateTime();
$new_date->setTimestamp($log->get('timestamp')->value);
$new_date->modify("$sign$amount $time");
if ($log->get('status')->first()->isTransitionAllowed('to_pending')) {
$log->get('status')->first()->applyTransitionById('to_pending');
}
$log->set('timestamp', $new_date->getTimestamp());
$log->setRevisionLogMessage($form_state->getValue('revision_message'));
$log->setNewRevision(TRUE);
$log->save();
}
}
else {
/** @var \Drupal\Core\Datetime\DrupalDateTime $new_date */
$new_date = $form_state->getValue('date');
foreach ($accessible_logs as $log) {
if ($log->get('status')->first()->isTransitionAllowed('to_pending')) {
$log->get('status')->first()->applyTransitionById('to_pending');
}
$log->set('timestamp', $new_date->getTimestamp());
$log->setRevisionLogMessage($form_state->getValue('revision_message'));
$log->setNewRevision(TRUE);
$log->save();
}
}
$this->messenger()->addMessage($this->formatPlural($count, 'Rescheduled 1 log.', 'Rescheduled @count logs.'));
}
// Add warning message if there were inaccessible logs.
if (!empty($inaccessible_logs)) {
$inaccessible_count = count($inaccessible_logs);
$this->messenger()->addWarning($this->formatPlural($inaccessible_count, 'Could not reschedule @count log because you do not have the necessary permissions.', 'Could not reschedule @count logs because you do not have the necessary permissions.'));
}
parent::submitForm($form, $form_state);
}
}

View File

@@ -0,0 +1,140 @@
<?php
namespace Drupal\log\Form;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\state_machine\WorkflowManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form controller for Log type entities.
*
* @package Drupal\log\Form
*/
class LogTypeForm extends EntityForm {
/**
* The workflow manager.
*
* @var \Drupal\state_machine\WorkflowManagerInterface
*/
protected $workflowManager;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs a new LogTypeForm object.
*
* @param \Drupal\state_machine\WorkflowManagerInterface $workflow_manager
* The workflow manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(WorkflowManagerInterface $workflow_manager, ModuleHandlerInterface $module_handler) {
$this->workflowManager = $workflow_manager;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.workflow'),
$container->get('module_handler')
);
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
$log_type = $this->entity;
$form['label'] = [
'#type' => 'textfield',
'#title' => $this->t('Label'),
'#maxlength' => 255,
'#default_value' => $log_type->label(),
'#description' => $this->t('Label for the Log type.'),
'#required' => TRUE,
];
$form['id'] = [
'#type' => 'machine_name',
'#default_value' => $log_type->id(),
'#machine_name' => [
'exists' => '\Drupal\log\Entity\LogType::load',
],
'#disabled' => !$log_type->isNew(),
];
$form['description'] = [
'#type' => 'textarea',
'#title' => $this->t('Description'),
'#default_value' => $log_type->getDescription(),
];
$form['name_pattern'] = [
'#type' => 'textfield',
'#title' => $this->t('Name pattern'),
'#maxlength' => 255,
'#default_value' => $log_type->getNamePattern() ?: 'Log [log:id]',
'#description' => $this->t('When filled in, log names of this type will be auto-generated using this naming pattern. Leave empty for not auto generating log names.'),
'#required' => TRUE,
];
$form['token_help'] = [
'#theme' => 'token_tree_link',
'#token_types' => ['log'],
];
$form['workflow'] = [
'#type' => 'select',
'#title' => $this->t('Workflow'),
'#options' => $this->workflowManager->getGroupedLabels('log'),
'#default_value' => $log_type->getWorkflowId(),
'#description' => $this->t('Used by all logs of this type.'),
];
$form['new_revision'] = [
'#type' => 'checkbox',
'#title' => $this->t('Create new revision'),
'#default_value' => $log_type->shouldCreateNewRevision(),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$log_type = $this->entity;
$status = $log_type->save();
switch ($status) {
case SAVED_NEW:
$this->messenger()->addMessage($this->t('Created the %label Log type.', [
'%label' => $log_type->label(),
]));
break;
default:
$this->messenger()->addMessage($this->t('Saved the %label Log type.', [
'%label' => $log_type->label(),
]));
}
$form_state->setRedirectUrl($log_type->toUrl('collection'));
return $status;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Drupal\log;
use Drupal\Core\Entity\EntityInterface;
use Drupal\entity\BulkFormEntityListBuilder;
/**
* Defines a class to build a listing of Log entities.
*
* @ingroup log
*/
class LogListBuilder extends BulkFormEntityListBuilder {
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['id'] = $this->t('Log ID');
$header['label'] = $this->t('Label');
$header['type'] = $this->t('Type');
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
/** @var \Drupal\log\Entity\LogInterface $entity */
$row['id'] = ['#markup' => $entity->id()];
$row['name'] = $entity->toLink($entity->label(), 'canonical')->toRenderable();
$row['type'] = ['#markup' => $entity->getBundleLabel()];
return $row + parent::buildRow($entity);
}
}

View File

@@ -0,0 +1,156 @@
<?php
namespace Drupal\log;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Utility\Token;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the controller class for logs.
*
* This extends the base storage class, adding required special handling for
* log entities.
*/
class LogStorage extends SqlContentEntityStorage {
/**
* The token service.
*
* @var \Drupal\Core\Utility\Token
*/
protected $token;
/**
* Constructs a SqlContentEntityStorage object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Database\Connection $database
* The database connection to be used.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* The entity field manager.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* The cache backend to be used.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface $memory_cache
* The memory cache backend to be used.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle info.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Utility\Token $token
* The token service.
*/
public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityFieldManagerInterface $entity_field_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityTypeManagerInterface $entity_type_manager, Token $token) {
parent::__construct($entity_type, $database, $entity_field_manager, $cache, $language_manager, $memory_cache, $entity_type_bundle_info, $entity_type_manager);
$this->token = $token;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('database'),
$container->get('entity_field.manager'),
$container->get('cache.entity'),
$container->get('language_manager'),
$container->get('entity.memory_cache'),
$container->get('entity_type.bundle.info'),
$container->get('entity_type.manager'),
$container->get('token'),
);
}
/**
* {@inheritdoc}
*/
protected function doPostSave(EntityInterface $entity, $update) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
if ($update && $this->entityType->isTranslatable()) {
$this->invokeTranslationHooks($entity);
}
// Get the log's current name.
$current_name = $entity->get('name')->value;
// We will automatically set the log name under two conditions:
// 1. Saving new/existing logs without a name.
// 2. Updating existing logs that were saved using the naming pattern.
$set_name = FALSE;
if (empty($current_name)) {
$set_name = TRUE;
}
elseif ($update && !empty($entity->original)) {
// Generate a log name using the original entity.
$original_generated_name = $this->generateLogName($entity->original);
// Compare the current log name to what would have been the original
// auto-generated name, to determine if the name was auto-generated
// previously. If it was, we will regenerate it.
if ($current_name == $original_generated_name) {
$set_name = TRUE;
}
}
// We must run the parent method before we set the name, so that new logs
// have an ID that can be used in token replacements.
// Also, we must run the parent method after the logic above, because the
// parent method unsets $entity->original.
parent::doPostSave($entity, $update);
// Set the log name, if necessary.
if ($set_name) {
// Generate a new name.
$new_name = $this->generateLogName($entity);
// If the name has been changed, update the entity.
if ($current_name != $new_name) {
$entity->set('name', $new_name);
$entity->save();
}
}
}
/**
* Helper method for generating a log name.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The log entity.
*
* @return string
* Returns the generated log name.
*/
protected function generateLogName(EntityInterface $entity) {
// Get the log type's naming pattern.
$name_pattern = $entity->getTypeNamePattern();
// Pass in an empty bubbleable metadata object, so we can avoid starting a
// renderer, for example if this happens in a REST resource creating
// context.
return $this->token->replace(
$name_pattern,
['log' => $entity],
[],
new BubbleableMetadata()
);
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Drupal\log;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Url;
/**
* Provides a listing of Log type entities.
*/
class LogTypeListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['label'] = $this->t('Log type');
$header['id'] = $this->t('Machine name');
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
$row['label'] = $entity->label();
$row['id'] = $entity->id();
// You probably want a few more properties here...
return $row + parent::buildRow($entity);
}
/**
* {@inheritdoc}
*/
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
// Place the edit operation after the operations added by field_ui.module
// which have the weights 15, 20, 25.
if (isset($operations['edit'])) {
$operations['edit']['weight'] = 30;
}
return $operations;
}
/**
* {@inheritdoc}
*/
public function render() {
$build = parent::render();
$build['table']['#empty'] = $this->t('No log types available. <a href=":link">Add log type</a>.', [
':link' => Url::fromRoute('entity.log_type.add_form')->toString(),
]);
return $build;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Drupal\log;
use Drupal\entity\EntityViewsData;
/**
* Provides views data for the file entity type.
*/
class LogViewsData extends EntityViewsData {
/**
* {@inheritdoc}
*/
public function getViewsData() {
$data = parent::getViewsData();
$data['log_field_data']['timestamp']['sort']['id'] = 'log_standard';
$data['log_field_data']['timestamp']['field']['id'] = 'log_field';
return $data;
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace Drupal\log\Plugin\Action;
use Drupal\Component\Plugin\DependentPluginInterface;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TempStore\PrivateTempStore;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base class for the configurable actions for logs.
*/
abstract class LogActionBase extends ActionBase implements DependentPluginInterface, ContainerFactoryPluginInterface {
/**
* The tempstore object.
*
* @var \Drupal\Core\TempStore\PrivateTempStore
*/
protected $tempStore;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $user;
/**
* Constructs a LogActionBase object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param array $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\TempStore\PrivateTempStore $temp_store
* The tempstore factory.
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, PrivateTempStore $temp_store, AccountInterface $user) {
$this->tempStore = $temp_store;
$this->user = $user;
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$temp_store = $container->get('tempstore.private')->get($plugin_id);
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$temp_store,
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function executeMultiple(array $entities) {
$this->tempStore->set($this->user->id(), $entities);
}
/**
* {@inheritdoc}
*/
public function execute($object = NULL) {
$this->executeMultiple([$object]);
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\log\Entity\LogInterface $object */
return $object->access('update', $account, $return_as_object);
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
return [];
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Drupal\log\Plugin\Action;
use Drupal\Core\Session\AccountInterface;
/**
* Action that clones a log entity.
*
* @Action(
* id = "log_clone_action",
* label = @Translation("Clones a log"),
* type = "log",
* confirm_form_route_name = "log.log_clone_action_form"
* )
*/
class LogClone extends LogActionBase {
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\log\Entity\LogInterface $object */
$result = $object->access('view', $account, TRUE)
->andIf($object->access('create', $account, TRUE));
return $return_as_object ? $result : $result->isAllowed();
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Drupal\log\Plugin\Action;
/**
* Action that marks a log as done.
*
* @Action(
* id = "log_mark_as_done_action",
* label = @Translation("Sets a Log as done"),
* type = "log"
* )
*/
class LogMarkAsDone extends LogStateChangeBase {
/**
* {@inheritdoc}
*/
protected $targetState = 'done';
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Drupal\log\Plugin\Action;
/**
* Action that marks a log as pending.
*
* @Action(
* id = "log_mark_as_pending_action",
* label = @Translation("Sets a Log as pending"),
* type = "log"
* )
*/
class LogMarkAsPending extends LogStateChangeBase {
/**
* {@inheritdoc}
*/
protected $targetState = 'pending';
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Drupal\log\Plugin\Action;
use Drupal\Core\Session\AccountInterface;
/**
* Action that reschedules a log entity.
*
* @Action(
* id = "log_reschedule_action",
* label = @Translation("Reschedules a log"),
* type = "log",
* confirm_form_route_name = "log.log_schedule_action_form"
* )
*/
class LogReschedule extends LogActionBase {
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\log\Entity\LogInterface $object */
$result = $object->get('timestamp')->access('edit', $account, TRUE)
->andIf($object->access('update', $account, TRUE));
return $return_as_object ? $result : $result->isAllowed();
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace Drupal\log\Plugin\Action;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Action\Plugin\Action\EntityActionBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\log\Entity\LogInterface;
/**
* Base class for actions that change the log status state.
*/
abstract class LogStateChangeBase extends EntityActionBase {
/**
* The target state to transition to.
*
* @var string
*/
protected $targetState;
/**
* {@inheritdoc}
*/
public function execute(LogInterface $log = NULL) {
// Bail if there is no log.
if (empty($log)) {
return;
}
// Apply the transition to target state if not already the current state.
/** @var \Drupal\state_machine\Plugin\Field\FieldType\StateItemInterface $state_item */
$state_item = $log->get('status')->first();
if ($state_item->getOriginalId() !== $this->targetState && $transition = $state_item->getWorkflow()->findTransition($state_item->getOriginalId(), $this->targetState)) {
$state_item->applyTransition($transition);
$log->setNewRevision(TRUE);
// Validate the entity before saving.
$violations = $log->validate();
if ($violations->count() > 0) {
$this->messenger()->addWarning(
$this->t('Could not change the status of <a href=":entity_link">%entity_label</a>: validation failed.',
[
':entity_link' => $log->toUrl()->setAbsolute()->toString(),
'%entity_label' => $log->label(),
],
),
);
return;
}
$log->save();
}
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\log\Entity\LogInterface $object */
// First check entity and state field access.
$result = $object->get('status')->access('edit', $account, TRUE)
->andIf($object->access('update', $account, TRUE));
// Save the state field.
/** @var \Drupal\state_machine\Plugin\Field\FieldType\StateItemInterface $state_item */
$state_item = $object->get('status')->first();
// If the state field is already in the target state, return early.
// The workflow will not allow a transition to the same state but the
// action itself does not need to fail.
if ($state_item->getOriginalId() === $this->targetState) {
return $return_as_object ? $result : $result->isAllowed();
}
// Check that the target state exists for the workflow.
$workflow = $state_item->getWorkflow();
$target_state = $workflow->getState($this->targetState);
// Deny access if the workflow does not support the target state.
if (empty($target_state)) {
$result = $result->orIf(AccessResult::forbidden(
$this->t(
'The %workflow workflow does not support the %target_state state.',
[
'%workflow' => $workflow->getLabel(),
'%target_state' => $this->targetState,
],
),
));
}
// Else check that a transition exists to the desired target state.
else {
$transition = $workflow->findTransition($state_item->getOriginalId(), $this->targetState);
$result = $result->orIf(AccessResult::forbiddenIf(
empty($transition) || !$state_item->isTransitionAllowed($transition->getId()),
$this->t(
'The state transition from %original_state to %target_state is not allowed.',
[
'%original' => $state_item->getOriginalLabel(),
'%target_state' => $target_state->getLabel(),
],
),
));
}
return $return_as_object ? $result : $result->isAllowed();
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace Drupal\log\Plugin\migrate\source\d7;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
/**
* Log source from database.
*
* @MigrateSource(
* id = "d7_log",
* source_module = "log"
* )
*/
class Log extends FieldableEntity {
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('log', 'l')
->fields('l')
->distinct()
->orderBy('id');
if (isset($this->configuration['bundle'])) {
$query->condition('l.type', (array) $this->configuration['bundle'], 'IN');
}
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'id' => $this->t('The log ID'),
'name' => $this->t('The log name'),
'type' => $this->t('The log type'),
'uid' => $this->t('The log author ID'),
'timestamp' => $this->t('Timestamp of the event being logged'),
'created' => $this->t('Timestamp when the log was created'),
'changed' => $this->t('Timestamp when the log was last modified'),
'done' => $this->t('Boolean indicating whether the log is done (the event happened)'),
];
return $fields;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$id = $row->getSourceProperty('id');
$type = $row->getSourceProperty('type');
// Get Field API field values.
foreach ($this->getFields('log', $type) as $field_name => $field) {
$row->setSourceProperty($field_name, $this->getFieldValues('log', $field_name, $id));
}
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['id']['type'] = 'integer';
return $ids;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Drupal\log\Plugin\views\field;
use Drupal\views\Plugin\views\field\EntityField;
/**
* Field handler to enable custom click-sort behavior for timestamp and id.
*
* @ViewsField("log_field")
*/
class LogField extends EntityField {
/**
* {@inheritdoc}
*/
public function clickSort($order) {
// No column selected, can't continue.
if (empty($this->options['click_sort_column'])) {
return;
}
$this->ensureMyTable();
$field_storage_definition = $this->getFieldStorageDefinition();
$column = $this->getTableMapping()->getFieldColumnName($field_storage_definition, $this->options['click_sort_column']);
if (!isset($this->aliases[$column])) {
// Column is not in query; add a sort on it (without adding the column).
$this->aliases[$column] = $this->tableAlias . '.' . $column;
}
$this->query->addOrderBy(NULL, NULL, $order, $this->aliases[$column]);
$this->query->addOrderBy($this->tableAlias, 'id', $order);
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Drupal\log\Plugin\views\sort;
use Drupal\views\Plugin\views\sort\Date;
/**
* Sort handler for logs based on timestamp and id.
*
* @ViewsSort("log_standard")
*/
class LogStandardSort extends Date {
/**
* {@inheritdoc}
*/
public function query() {
$this->ensureMyTable();
switch ($this->options['granularity']) {
case 'second':
default:
$this->query->addOrderBy($this->tableAlias, $this->realField, $this->options['order']);
$this->query->addOrderBy($this->tableAlias, 'id', $this->options['order']);
return;
case 'minute':
$formula = $this->getDateFormat('YmdHi');
break;
case 'hour':
$formula = $this->getDateFormat('YmdH');
break;
case 'day':
$formula = $this->getDateFormat('Ymd');
break;
case 'month':
$formula = $this->getDateFormat('Ym');
break;
case 'year':
$formula = $this->getDateFormat('Y');
break;
}
$this->query->addOrderBy(NULL, $formula, $this->options['order'], $this->tableAlias . '_' . $this->field . '_' . $this->options['granularity']);
$this->query->addOrderBy($this->tableAlias, 'id', $this->options['order']);
}
/**
* {@inheritdoc}
*/
public function getDateField() {
return $this->query->getDateField("$this->tableAlias.timestamp");
}
}