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,11 @@
langcode: en
status: true
dependencies:
module:
- asset
- farm_log
id: asset_add_log_action
label: 'Add log'
type: asset
plugin: 'asset_add_log_action'
configuration: { }

View File

@@ -0,0 +1,4 @@
# Schema for actions.
action.configuration.asset_add_log_action:
type: action_configuration_default
label: 'Configuration for the asset add log action'

View File

@@ -0,0 +1,9 @@
name: farmOS Log
description: Provides additional Log entity features for farmOS.
type: module
package: farmOS
core_version_requirement: ^10
dependencies:
- farm:asset
- farm:farm_log_asset
- log:log

View File

@@ -0,0 +1,54 @@
<?php
/**
* @file
* Contains farm_log.module.
*/
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Implements hook_entity_prepare_form().
*/
function farm_log_entity_prepare_form(EntityInterface $entity, $operation, FormStateInterface $form_state) {
// If not adding a new entity, bail.
if ($operation !== 'add' || !$entity->isNew()) {
return;
}
// If the entity is not a log, bail.
if ($entity->getEntityTypeId() !== 'log') {
return;
}
// Save the current user.
$user = \Drupal::currentUser();
// Save the request query params.
$query = \Drupal::request()->query;
// Prepopulate the log asset field.
if ($query->has('asset')) {
// Get asset IDs. We can't use $query->get('asset') or $query->all('asset')
// because those throw a client error if the parameter is not the expected
// cardinality (single value vs array of values).
$asset_ids = (array) $query->all()['asset'];
/** @var \Drupal\Core\Field\EntityReferenceFieldItemList $asset_field */
$asset_field = $entity->get('asset');
// Add each asset the user has view access to.
$assets = \Drupal::entityTypeManager()->getStorage('asset')->loadMultiple($asset_ids);
foreach ($assets as $asset) {
if ($asset->access('view', $user)) {
$asset_field->appendItem($asset);
}
}
$entity->set('asset', $asset_ids);
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* @file
* Post update hooks for the farm_log module.
*/
use Drupal\log\Entity\LogType;
/**
* Update core log types to make "done" their default status.
*/
function farm_log_post_update_farm_log_workflow(&$sandbox) {
/** @var \Drupal\log\Entity\LogType[] $log_types */
$core_log_types = [
'activity',
'birth',
'harvest',
'input',
'lab_test',
'maintenance',
'medical',
'observation',
'seeding',
'transplanting',
];
$log_types = LogType::loadMultiple();
foreach ($log_types as $log_type) {
if (in_array($log_type->id(), $core_log_types) && $log_type->getWorkflowId() == 'log_default') {
$log_type->setWorkflowId('farm_log_workflow');
$log_type->save();
}
}
}

View File

@@ -0,0 +1,6 @@
farm_log.asset_add_log_action_form:
path: '/asset/add_log'
defaults:
_form: 'Drupal\farm_log\Form\AssetAddLogActionForm'
requirements:
_user_is_logged_in: 'TRUE'

View File

@@ -0,0 +1,9 @@
services:
asset.logs:
class: Drupal\farm_log\AssetLogs
arguments:
[ '@entity_type.manager', '@farm.log_query' ]
farm.log_query:
class: Drupal\farm_log\LogQueryFactory
arguments:
[ '@entity_type.manager' ]

View File

@@ -0,0 +1,18 @@
farm_log_workflow:
id: farm_log_workflow
group: log
label: 'farmOS Log Workflow'
states:
done:
label: Done
pending:
label: Pending
transitions:
done:
label: 'Done'
from: [pending]
to: done
to_pending:
label: 'Move to Pending'
from: [done]
to: pending

View File

@@ -0,0 +1,9 @@
name: farmOS Log Asset
description: Adds an asset reference field to logs.
type: module
package: farmOS
core_version_requirement: ^10
dependencies:
- farm:asset
- farm:farm_field
- log:log

View File

@@ -0,0 +1,34 @@
<?php
/**
* @file
* Contains farm_log_asset.module.
*/
use Drupal\Core\Entity\EntityTypeInterface;
/**
* Implements hook_entity_base_field_info().
*/
function farm_log_asset_entity_base_field_info(EntityTypeInterface $entity_type) {
// We only care about log entities.
if ($entity_type->id() != 'log') {
return [];
}
// Add an asset reference field to logs.
$field_info = [
'type' => 'entity_reference',
'label' => t('Assets'),
'description' => t('What assets do this log pertain to?'),
'target_type' => 'asset',
'multiple' => TRUE,
'weight' => [
'form' => 0,
'view' => 0,
],
];
$fields['asset'] = \Drupal::service('farm_field.factory')->baseFieldDefinition($field_info);
return $fields;
}

View File

@@ -0,0 +1,8 @@
# Schema for the farmOS entity third party settings.
log.type.*.third_party.farm_log_quantity:
type: mapping
label: 'farmOS log quantity settings'
mapping:
default_quantity_type:
type: string
label: 'Default quantity type'

View File

@@ -0,0 +1,10 @@
name: farmOS Log Quantity
description: Adds a quantity reference field to logs.
type: module
package: farmOS
core_version_requirement: ^10
dependencies:
- entity_reference_revisions:entity_reference_revisions
- farm:farm_field
- farm:quantity
- log:log

View File

@@ -0,0 +1,119 @@
<?php
/**
* @file
* Contains farm_log_quantity.module.
*/
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Implements hook_entity_base_field_info().
*/
function farm_log_quantity_entity_base_field_info(EntityTypeInterface $entity_type) {
// We only care about log entities.
if ($entity_type->id() != 'log') {
return [];
}
// Add a quantity reference field to logs.
$field_info = [
'quantity' => [
'type' => 'entity_reference_revisions',
'label' => t('Quantity'),
'description' => t('Add quantity measurements to this log.'),
'target_type' => 'quantity',
'multiple' => TRUE,
'weight' => [
'form' => 0,
'view' => 50,
],
],
];
$fields = [];
foreach ($field_info as $name => $info) {
$fields[$name] = \Drupal::service('farm_field.factory')->baseFieldDefinition($info);
}
return $fields;
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function farm_log_quantity_form_quantity_delete_multiple_confirm_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// Add a warning to bulk quantity delete confirmation form, to emphasize that
// the quantity will be deleted from all log revisions.
$message = t('Warning: Deleting quantities will remove them from all revisions of records that reference them.');
$form['warning'] = [
'#type' => 'html_tag',
'#tag' => 'strong',
'#value' => $message,
'#weight' => -10,
];
}
/**
* Implements hook_form_BASE_FORM_ID_alter().
*/
function farm_log_quantity_form_log_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// Alter the Quantity inline entity form to set the default quantity type.
if (!empty($form['quantity']['widget']['actions']['bundle']['#options'])) {
$bundle_select = &$form['quantity']['widget']['actions']['bundle'];
// Load the log type storage.
/** @var \Drupal\log\Entity\Log $entity */
$entity = $form_state->getFormObject()->getEntity();
// Determine the default quantity type.
$default_type = farm_log_quantity_default_type($entity->bundle());
// Set the default value.
if (array_key_exists($default_type, $bundle_select['#options'])) {
$bundle_select['#default_value'] = $default_type;
}
}
}
/**
* Returns the default quantity type.
*
* @param string|null $log_type
* The log type (optional).
*
* @return string|null
* The log's default quantity type, or NULL if a default is unavailable.
*/
function farm_log_quantity_default_type(?string $log_type = NULL) {
// If a log type is specified, attempt to look up the default quantity type
// from the log type's third party settings.
if (!empty($log_type)) {
/** @var \Drupal\log\Entity\LogType $log_type_storage */
$log_type_definition = \Drupal::service('entity_type.manager')->getStorage('log_type')->load($log_type);
$type = $log_type_definition->getThirdPartySetting('farm_log_quantity', 'default_quantity_type', NULL);
if (!empty($type)) {
return $type;
}
}
// If the farm_quantity_standard module is installed, default to "standard".
if (\Drupal::moduleHandler()->moduleExists('farm_quantity_standard')) {
return 'standard';
}
// Look up all quantity types and take the first one.
/** @var \Drupal\quantity\Entity\QuantityInterface[] $quantity_types */
$quantity_types = \Drupal::service('entity_type.manager')->getStorage('quantity_type')->loadMultiple();
foreach ($quantity_types as $quantity_type) {
if (!empty($quantity_type->id())) {
return $quantity_type->id();
}
}
// Otherwise return NULL.
return NULL;
}

View File

@@ -0,0 +1,7 @@
services:
farm_log_quantity.event_subscriber:
class: Drupal\farm_log_quantity\EventSubscriber\LogQuantityEventSubscriber
arguments:
[ '@entity_type.manager' ]
tags:
- { name: 'event_subscriber' }

View File

@@ -0,0 +1,136 @@
<?php
namespace Drupal\farm_log_quantity\EventSubscriber;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\log\Event\LogEvent;
use Drupal\quantity\Event\QuantityEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Subscribe to events related to log quantities.
*/
class LogQuantityEventSubscriber implements EventSubscriberInterface {
use StringTranslationTrait;
/**
* Entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* {@inheritdoc}
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*
* @return array
* The event names to listen for, and the methods that should be executed.
*/
public static function getSubscribedEvents() {
return [
LogEvent::CLONE => 'logClone',
LogEvent::DELETE => 'logDelete',
QuantityEvent::DELETE => 'quantityDelete',
];
}
/**
* Perform actions on log clone.
*
* @param \Drupal\log\Event\LogEvent $event
* The log event.
*/
public function logClone(LogEvent $event) {
// Get the log entity from the event.
$log = $event->log;
// Bail if the log does not reference any quantities.
if ($log->get('quantity')->isEmpty()) {
return;
}
// Duplicate each referenced quantity.
$new_quantities = [];
/** @var \Drupal\quantity\Entity\QuantityInterface $quantity */
foreach ($log->get('quantity')->referencedEntities() as $quantity) {
$duplicate_quantity = $quantity->createDuplicate();
$new_quantities[] = $duplicate_quantity;
}
// Update the log to reference the new duplicated quantities.
$log->set('quantity', $new_quantities);
}
/**
* Perform actions on log delete.
*
* @param \Drupal\log\Event\LogEvent $event
* The log event.
*/
public function logDelete(LogEvent $event) {
// Get the log entity from the event.
$log = $event->log;
// If the log doesn't have a quantity field, bail.
if (!$log->hasField('quantity')) {
return;
}
// Get any quantities the log references.
$quantities = $log->quantity->referencedEntities();
// Delete quantity entities.
if (!empty($quantities)) {
$this->entityTypeManager->getStorage('quantity')->delete($quantities);
}
}
/**
* Perform actions on quantity delete.
*
* @param \Drupal\quantity\Event\QuantityEvent $event
* The quantity event.
*/
public function quantityDelete(QuantityEvent $event) {
// Get the quantity entity from the event.
$quantity = $event->quantity;
// Look up logs that reference the quantity.
$log_storage = $this->entityTypeManager->getStorage('log');
$query = $log_storage->getQuery();
$query->condition('quantity.target_id', $quantity->id());
$query->accessCheck(FALSE);
$log_ids = $query->execute();
/** @var \Drupal\log\Entity\LogInterface[] $logs */
$logs = [];
if (!empty($log_ids)) {
$logs = $log_storage->loadMultiple($log_ids);
}
// Remove references to the quantity from the log and save a revision.
foreach ($logs as $log) {
$log->set('quantity', array_filter($log->get('quantity')->getValue(), function ($value) use ($quantity) {
if (!empty($value['target_id']) && $value['target_id'] == $quantity->id()) {
return FALSE;
}
return TRUE;
}));
$log->setNewRevision(TRUE);
$log->setRevisionLogMessage($this->t('Removed reference to deleted quantity %uuid.', ['%uuid' => $quantity->uuid()]));
$log->save();
}
}
}

View File

@@ -0,0 +1,8 @@
langcode: en
status: true
id: test
label: Test
description: ''
name_pattern: 'Test log [log:id]'
workflow: log_default
new_revision: true

View File

@@ -0,0 +1,6 @@
langcode: en
status: true
id: test
label: Test
description: 'Test quantity type.'
new_revision: true

View File

@@ -0,0 +1,7 @@
name: farmOS Log Quantity Tests
description: 'Support module for log quantity testing.'
type: module
package: Testing
core_version_requirement: ^10
dependencies:
- farm:farm_log_quantity

View File

@@ -0,0 +1,114 @@
<?php
namespace Drupal\Tests\farm_log_quantity\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\log\Entity\Log;
use Drupal\log\Event\LogEvent;
use Drupal\quantity\Entity\Quantity;
/**
* Tests for farmOS log quantity module.
*
* @group farm
*/
class LogQuantityTest extends KernelTestBase {
/**
* The log storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $logStorage;
/**
* The quantity storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $quantityStorage;
/**
* {@inheritdoc}
*/
protected static $modules = [
'entity_reference_revisions',
'log',
'farm_field',
'farm_log_quantity',
'farm_log_quantity_test',
'farm_unit',
'fraction',
'options',
'quantity',
'state_machine',
'taxonomy',
'text',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('log');
$this->installEntitySchema('quantity');
$this->installEntitySchema('taxonomy_term');
$this->installEntitySchema('user');
$this->installConfig([
'farm_log_quantity_test',
'farm_unit',
]);
$this->logStorage = \Drupal::entityTypeManager()->getStorage('log');
$this->quantityStorage = \Drupal::entityTypeManager()->getStorage('quantity');
}
/**
* Test log quantity events.
*/
public function testLogQuantityEvents() {
// Create a test log with a test quantity.
$quantity = Quantity::create([
'type' => 'test',
'value' => 1,
]);
$quantity->save();
$log = Log::create([
'type' => 'test',
'quantity' => [
[
'target_id' => $quantity->id(),
],
],
]);
$log->save();
// Test that cloning a log clones its quantities.
// This replicates the logic for cloning logs from
// \Drupal\log\Form\LogCloneActionForm::submitForm().
$cloned_log = $log->createDuplicate();
$event = new LogEvent($cloned_log);
\Drupal::service('event_dispatcher')->dispatch($event, LogEvent::CLONE);
$event->log->save();
$logs = $this->logStorage->loadMultiple();
$quantities = $this->quantityStorage->loadMultiple();
$this->assertCount(2, $logs);
$this->assertCount(2, $quantities);
$this->assertEquals($quantities[1]->get('value')->value, $quantities[2]->get('value')->value);
// Test that deleting a log deletes its quantities.
$logs[2]->delete();
$logs = $this->logStorage->loadMultiple();
$quantities = $this->quantityStorage->loadMultiple();
$this->assertCount(1, $logs);
$this->assertCount(1, $quantities);
// Test that deleting a quantity cleans up the log's reference to it.
$quantity->delete();
$logs = $this->logStorage->loadMultiple();
$this->assertEmpty($logs[1]->get('quantity')->getValue());
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace Drupal\farm_log;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\asset\Entity\AssetInterface;
/**
* Service for loading logs that reference assets.
*/
class AssetLogs implements AssetLogsInterface {
/**
* Entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* Log query factory.
*
* @var \Drupal\farm_log\LogQueryFactoryInterface
*/
protected LogQueryFactoryInterface $logQueryFactory;
/**
* Class constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* Entity type manager.
* @param \Drupal\farm_log\LogQueryFactoryInterface $log_query_factory
* Log query factory.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, LogQueryFactoryInterface $log_query_factory) {
$this->entityTypeManager = $entity_type_manager;
$this->logQueryFactory = $log_query_factory;
}
/**
* {@inheritdoc}
*/
public function getLogs(AssetInterface $asset, ?string $log_type = NULL, bool $access_check = TRUE): array {
$log_ids = $this->query($asset, $log_type, $access_check)->execute();
if (empty($log_ids)) {
return [];
}
return $this->entityTypeManager->getStorage('log')->loadMultiple($log_ids);
}
/**
* {@inheritdoc}
*/
public function getFirstLog(AssetInterface $asset, ?string $log_type = NULL, bool $access_check = TRUE) {
$log_ids = $this->query($asset, $log_type, $access_check, 1)->execute();
if (empty($log_ids)) {
return NULL;
}
return $this->entityTypeManager->getStorage('log')->load(reset($log_ids));
}
/**
* Build a log query.
*
* @param \Drupal\asset\Entity\AssetInterface $asset
* The asset entity.
* @param string|null $log_type
* Optionally filter by log type.
* @param bool $access_check
* Whether to check log entity access.
* @param int|null $limit
* The number of logs to return.
*
* @return \Drupal\Core\Entity\Query\QueryInterface
* A query object.
*/
protected function query(AssetInterface $asset, ?string $log_type = NULL, bool $access_check = TRUE, ?int $limit = NULL) {
$options = [
'asset' => $asset,
'direction' => 'ASC',
];
if (!empty($limit)) {
$options['limit'] = $limit;
}
$query = $this->logQueryFactory->getQuery($options);
if (!empty($log_type)) {
$query->condition('type', $log_type);
}
$query->accessCheck($access_check);
return $query;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Drupal\farm_log;
use Drupal\asset\Entity\AssetInterface;
/**
* The interface for asset logs service.
*/
interface AssetLogsInterface {
/**
* Get all logs for an asset.
*
* @param \Drupal\asset\Entity\AssetInterface $asset
* The asset entity.
* @param string|null $log_type
* Optionally filter by log type.
* @param bool $access_check
* Whether to check log entity access (defaults to TRUE).
*
* @return \Drupal\log\Entity\LogInterface[]
* Returns an array of Log entities.
*/
public function getLogs(AssetInterface $asset, ?string $log_type = NULL, bool $access_check = TRUE): array;
/**
* Get the first log of an asset.
*
* @param \Drupal\asset\Entity\AssetInterface $asset
* The asset entity.
* @param string|null $log_type
* Optionally filter by log type.
* @param bool $access_check
* Whether to check log entity access.
*
* @return \Drupal\log\Entity\LogInterface|null
* Returns a log entity or NULL if no logs were found.
*/
public function getFirstLog(AssetInterface $asset, ?string $log_type = NULL, bool $access_check = TRUE);
}

View File

@@ -0,0 +1,206 @@
<?php
namespace Drupal\farm_log\Form;
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;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Provides an asset add log confirmation form.
*/
class AssetAddLogActionForm extends ConfirmFormBase {
/**
* The tempstore factory.
*
* @var \Drupal\Core\TempStore\SharedTempStore
*/
protected $tempStore;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $user;
/**
* The entity type.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* The assets to create logs for.
*
* @var \Drupal\Core\Entity\EntityInterface[]
*/
protected $entities;
/**
* Constructs an AssetAddLogActionForm 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->tempStore = $temp_store_factory->get('asset_add_log_confirm');
$this->entityTypeManager = $entity_type_manager;
$this->user = $user;
}
/**
* {@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 getFormId() {
return 'asset_add_log_action_confirm_form';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->formatPlural(count($this->entities), 'Are you sure you want to add a log referencing this @item?', 'Are you sure you want to add a log referencing these @items?', [
'@item' => $this->entityType->getSingularLabel(),
'@items' => $this->entityType->getPluralLabel(),
]);
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
if ($this->entityType->hasLinkTemplate('collection')) {
return new Url('entity.' . $this->entityType->id() . '.collection');
}
else {
return new Url('<front>');
}
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return '';
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Continue');
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$this->entityType = $this->entityTypeManager->getDefinition('asset');
$this->entities = $this->tempStore->get($this->user->id());
if (empty($this->entityType) || empty($this->entities)) {
return new RedirectResponse($this->getCancelUrl()
->setAbsolute()
->toString());
}
// Build list of log type options.
// Limit to log types the user has access to create.
$log_access_control_handler = $this->entityTypeManager->getAccessControlHandler('log');
$log_types = array_filter($this->entityTypeManager->getStorage('log_type')->loadMultiple(), function ($log_type) use ($log_access_control_handler) {
return $log_access_control_handler->createAccess($log_type->id(), $this->currentUser());
});
$log_type_options = array_map(function ($log_type) {
return $log_type->label();
}, $log_types);
$form['log_type'] = [
'#type' => 'select',
'#title' => $this->t('Log type'),
'#description' => $this->t('Select the type of log to create.'),
'#options' => $log_type_options,
'#required' => TRUE,
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Filter out entities the user doesn't have access to.
$inaccessible_entities = [];
$accessible_entities = [];
foreach ($this->entities as $entity) {
if (!$entity->access('view', $this->currentUser())) {
$inaccessible_entities[] = $entity;
continue;
}
$accessible_entities[] = $entity;
}
// Default redirect url.
$redirect_url = $this->getCancelUrl();
if (!empty($form_state->getValue('confirm')) && !empty($accessible_entities)) {
$log_type = $form_state->getValue('log_type');
if (!empty($log_type)) {
// If a destination query param is set, save it and remove it.
// First we need to redirect to the /log/add/{log_type} form.
$destination = $this->getCancelUrl()->setAbsolute()->toString();
if ($this->getRequest()->query->has('destination')) {
$destination = $this->getRequest()->query->get('destination');
$this->getRequest()->query->remove('destination');
}
// Build list of asset ids.
$asset_ids = array_map(function ($asset) {
return $asset->id();
}, $accessible_entities);
// Build query params to include in the redirect.
$query_params = [
'destination' => $destination,
'asset' => $asset_ids,
];
$redirect_url = Url::fromRoute('entity.log.add_form', ['log_type' => $log_type], ['query' => $query_params]);
}
}
$this->tempStore->delete($this->currentUser()->id());
$form_state->setRedirectUrl($redirect_url);
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace Drupal\farm_log;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\QueryInterface;
/**
* Factory for generating a log query.
*
* @internal
*/
class LogQueryFactory implements LogQueryFactoryInterface {
/**
* Entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* Class constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* Entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public function getQuery(array $options = []): QueryInterface {
// Start with a standard log entity query.
$query = $this->entityTypeManager->getStorage('log')->getQuery();
// Add a tag.
$query->addTag('farm.log_query');
// If a type is specified, only include logs of that type.
if (isset($options['type'])) {
$query->condition('type', $options['type']);
}
// If a timestamp is specified, only include logs with a timestamp less than
// or equal to it.
if (isset($options['timestamp'])) {
$query->condition('timestamp', $options['timestamp'], '<=');
}
// If a status is specified, only include logs with that status.
if (isset($options['status'])) {
$query->condition('status', $options['status']);
}
// If an asset is provided, only include logs that reference it.
if (isset($options['asset'])) {
$query->condition('asset.entity.id', $options['asset']->id());
}
// Sort by timestamp and then log ID. Optionally accept a sort direction.
// Default to timestamp+id descending.
$direction = $options['direction'] ?? 'DESC';
$query->sort('timestamp', $direction);
$query->sort('id', $direction);
// If a limit is specified, limit the results.
if (isset($options['limit'])) {
$query->range(0, $options['limit']);
}
// Return the query.
return $query;
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Drupal\farm_log;
use Drupal\Core\Entity\Query\QueryInterface;
/**
* The interface for a log query factory.
*
* @internal
*/
interface LogQueryFactoryInterface {
/**
* Get a new log query object.
*
* @param array $options
* An array of options for building the query.
*
* @return \Drupal\Core\Entity\Query\QueryInterface
* A query object.
*/
public function getQuery(array $options = []): QueryInterface;
}

View File

@@ -0,0 +1,96 @@
<?php
namespace Drupal\farm_log\Plugin\Action;
use Drupal\Core\Action\Plugin\Action\EntityActionBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Action that adds a log referencing assets.
*
* @Action(
* id = "asset_add_log_action",
* label = @Translation("Add a log referencing assets."),
* type = "asset",
* confirm_form_route_name = "farm_log.asset_add_log_action_form"
* )
*/
class AssetAddLog extends EntityActionBase {
/**
* The tempstore object.
*
* @var \Drupal\Core\TempStore\SharedTempStore
*/
protected $tempStore;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a new AssetAddLog 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 mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param \Drupal\Core\Session\AccountInterface $current_user
* Current user.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) {
$this->currentUser = $current_user;
$this->tempStore = $temp_store_factory->get('asset_add_log_confirm');
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('tempstore.private'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
public function executeMultiple(array $entities) {
/** @var \Drupal\Core\Entity\EntityInterface[] $entities */
$this->tempStore->set($this->currentUser->id(), $entities);
}
/**
* {@inheritdoc}
*/
public function execute($object = NULL) {
$this->executeMultiple([$object]);
}
/**
* {@inheritdoc}
*/
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
return $object->access('update', $account, $return_as_object);
}
}

View File

@@ -0,0 +1,7 @@
langcode: en
status: true
id: test
label: Test
description: ''
workflow: asset_default
new_revision: true

View File

@@ -0,0 +1,8 @@
langcode: en
status: true
id: bar
label: Bar
description: ''
name_pattern: 'Bar log [log:id]'
workflow: farm_log_workflow
new_revision: true

View File

@@ -0,0 +1,8 @@
langcode: en
status: true
id: foo
label: Foo
description: ''
name_pattern: 'Foo log [log:id]'
workflow: log_default
new_revision: true

View File

@@ -0,0 +1,9 @@
name: farmOS Log Tests
description: 'Support module for log testing.'
type: module
package: Testing
core_version_requirement: ^10
dependencies:
- farm:asset
- farm:farm_log
- farm:farm_log_asset

View File

@@ -0,0 +1,191 @@
<?php
namespace Drupal\Tests\farm_log\Kernel;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests for farmOS log module.
*
* @group farm
*/
class LogTest extends KernelTestBase {
/**
* Log query factory service.
*
* @var \Drupal\farm_log\LogQueryFactoryInterface
*/
protected $logQueryFactory;
/**
* The asset logs service.
*
* @var \Drupal\farm_log\AssetLogsInterface
*/
protected $assetLogs;
/**
* {@inheritdoc}
*/
protected static $modules = [
'asset',
'log',
'farm_field',
'farm_log',
'farm_log_asset',
'farm_log_test',
'state_machine',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->logQueryFactory = \Drupal::service('farm.log_query');
$this->assetLogs = \Drupal::service('asset.logs');
$this->installEntitySchema('asset');
$this->installEntitySchema('log');
$this->installEntitySchema('user');
$this->installConfig([
'farm_log_test',
]);
}
/**
* Test log query factory.
*/
public function testLogQueryFactory() {
// Get asset and log storage.
$asset_storage = \Drupal::service('entity_type.manager')->getStorage('asset');
$log_storage = \Drupal::service('entity_type.manager')->getStorage('log');
// Create one asset and two logs of different types.
$asset = $asset_storage->create(['type' => 'test']);
$asset->save();
$foo_log = $log_storage->create(['type' => 'foo']);
$foo_log->save();
$bar_log = $log_storage->create(['type' => 'bar']);
$bar_log->save();
// Test that the logs are in default log query results.
$log_ids = $this->logQueryFactory->getQuery()->accessCheck(FALSE)->execute();
$this->assertContains($foo_log->id(), $log_ids, 'Log 1 appears in log query results.');
$this->assertContains($bar_log->id(), $log_ids, 'Log 2 appears in log query results.');
// Test that results can be filtered by log type.
$log_ids = $this->logQueryFactory->getQuery(['type' => 'foo'])->accessCheck(FALSE)->execute();
$this->assertContains($foo_log->id(), $log_ids, 'Log query results can be filtered by type.');
// Set the timestamp of one log to the future.
$now = \Drupal::time()->getRequestTime();
$foo_log->timestamp = $now + 86400;
$foo_log->save();
// Test that results can be filtered by timestamp.
$log_ids = $this->logQueryFactory->getQuery(['timestamp' => $now])->accessCheck(FALSE)->execute();
$this->assertNotContains($foo_log->id(), $log_ids, 'Log query results can be filtered by timestamp.');
// Set the status of one log to complete.
$bar_log->status = 'complete';
$bar_log->save();
// Test that results can be filtered by status.
$log_ids = $this->logQueryFactory->getQuery(['status' => 'pending'])->accessCheck(FALSE)->execute();
$this->assertNotContains($bar_log->id(), $log_ids, 'Log query results can be filtered by status.');
// Reference the asset in one of the logs.
$foo_log->asset[] = $asset;
$foo_log->save();
// Test that results can be filtered by asset reference.
$log_ids = $this->logQueryFactory->getQuery(['asset' => $asset])->accessCheck(FALSE)->execute();
$this->assertContains($foo_log->id(), $log_ids, 'Log that references asset is included in results.');
$this->assertNotContains($bar_log->id(), $log_ids, 'Log that does not reference asset is not included in results.');
// Set the timestamps of both logs to now.
$now = \Drupal::time()->getRequestTime();
$foo_log->timestamp = $now;
$foo_log->save();
$bar_log->timestamp = $now;
$bar_log->save();
// Test that logs with the same timestamp are sorted by ID descending.
$log_ids = $this->logQueryFactory->getQuery()->accessCheck(FALSE)->execute();
$this->assertEquals($bar_log->id(), reset($log_ids), 'Logs with the same timestamp are sorted by ID descending.');
// Set the timestamp of one log to the future.
$now = \Drupal::time()->getRequestTime();
$foo_log->timestamp = $now + 86400;
$foo_log->save();
// Test that logs are sorted by timestamp descending.
$log_ids = $this->logQueryFactory->getQuery()->accessCheck(FALSE)->execute();
$this->assertEquals($foo_log->id(), reset($log_ids), 'Logs are sorted by timestamp descending.');
// Test that results can be limited.
$log_ids = $this->logQueryFactory->getQuery(['limit' => 1])->accessCheck(FALSE)->execute();
$this->assertEquals(1, count($log_ids), 'Log query results can be limited.');
}
/**
* Test asset.logs service.
*/
public function testAssetLogsService() {
// Get asset and log storage.
$asset_storage = \Drupal::service('entity_type.manager')->getStorage('asset');
$log_storage = \Drupal::service('entity_type.manager')->getStorage('log');
// Create one asset and two logs of different types that reference it.
$asset = $asset_storage->create(['type' => 'test']);
$asset->save();
$timestamp = time();
$foo_log = $log_storage->create([
'timestamp' => $timestamp + 1,
'type' => 'foo',
'asset' => [$asset],
]);
$foo_log->save();
$bar_log = $log_storage->create([
'timestamp' => $timestamp,
'type' => 'bar',
'asset' => [$asset],
]);
$bar_log->save();
// Test that the asset.logs service returns both logs.
$logs = $this->assetLogs->getLogs($asset);
$this->assertCount(2, $logs);
// Test that logs can be filtered by type.
$logs = $this->assetLogs->getLogs($asset, 'bar');
$this->assertCount(1, $logs);
$this->assertEquals($bar_log->id(), reset($logs)->id());
// Test that we can get the first log.
$first_log = $this->assetLogs->getFirstLog($asset);
$this->assertEquals($bar_log->id(), $first_log->id());
}
/**
* Test log status workflow.
*/
public function testLogStatusWorkflow() {
// Get log storage.
$log_storage = \Drupal::service('entity_type.manager')->getStorage('log');
// Confirm that the default status of foo logs is "pending".
$foo_log = $log_storage->create(['type' => 'foo']);
$this->assertEquals('pending', $foo_log->get('status')->value);
// Confirm that the default status of bar logs is "done".
$bar_log = $log_storage->create(['type' => 'bar']);
$this->assertEquals('done', $bar_log->get('status')->value);
}
}