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,10 @@
langcode: en
status: true
dependencies:
module:
- plan
id: plan_activate_action
label: 'Unarchive plan'
type: plan
plugin: 'plan_activate_action'
configuration: { }

View File

@@ -0,0 +1,10 @@
langcode: en
status: true
dependencies:
module:
- plan
id: plan_archive_action
label: 'Archive plan'
type: plan
plugin: 'plan_archive_action'
configuration: { }

View File

@@ -0,0 +1,10 @@
langcode: en
status: true
dependencies:
module:
- plan
id: plan_delete_action
label: 'Delete plan'
type: plan
plugin: entity:delete_action:plan
configuration: { }

View File

@@ -0,0 +1,729 @@
langcode: en
status: true
dependencies:
module:
- options
- plan
- user
id: plan_admin
label: 'Plan admin'
module: views
description: 'Find and manage plan entities.'
tag: default
base_table: plan_field_data
base_field: id
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'administer plans'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Filter
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: full
options:
items_per_page: 25
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous: ' Previous'
next: 'Next '
first: '« First'
last: 'Last »'
quantity: 9
pagination_heading_level: h4
style:
type: table
options:
grouping: { }
row_class: ''
default_row_class: true
override: true
sticky: false
caption: ''
summary: ''
description: ''
columns:
plan_bulk_form: plan_bulk_form
status: status
id: id
name: name
type: type
uid: uid
operations: operations
info:
plan_bulk_form:
align: ''
separator: ''
empty_column: false
responsive: ''
status:
sortable: true
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
id:
sortable: true
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
name:
sortable: true
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
type:
sortable: true
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
uid:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
operations:
align: ''
separator: ''
empty_column: false
responsive: ''
default: '-1'
empty_table: true
row:
type: fields
fields:
plan_bulk_form:
id: plan_bulk_form
table: plan
field: plan_bulk_form
relationship: none
group_type: group
admin_label: ''
label: 'Bulk update'
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
action_title: 'With selection'
include_exclude: exclude
selected_actions: { }
entity_type: plan
plugin_id: bulk_form
status:
id: status
table: plan_field_data
field: status
relationship: none
group_type: group
admin_label: ''
label: Status
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: list_default
settings: { }
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: plan
entity_field: status
plugin_id: field
id:
id: id
table: plan_field_data
field: id
relationship: none
group_type: group
admin_label: ''
label: ID
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: number_integer
settings:
thousand_separator: ''
prefix_suffix: true
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: plan
entity_field: id
plugin_id: field
name:
id: name
table: plan_field_data
field: name
relationship: none
group_type: group
admin_label: ''
label: Name
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: string
settings:
link_to_entity: true
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: plan
entity_field: name
plugin_id: field
type:
id: type
table: plan_field_data
field: type
relationship: none
group_type: group
admin_label: ''
label: 'Plan type'
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: target_id
type: entity_reference_label
settings:
link: true
group_column: target_id
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: plan
entity_field: type
plugin_id: field
uid:
id: uid
table: plan_field_data
field: uid
relationship: none
group_type: group
admin_label: ''
label: 'Authored by'
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: target_id
type: entity_reference_label
settings:
link: true
group_column: target_id
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: plan
entity_field: uid
plugin_id: field
operations:
table: plan
field: operations
id: operations
entity_type: null
entity_field: null
plugin_id: entity_operations
relationship: none
group_type: group
admin_label: ''
label: 'Operations links'
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
destination: true
filters:
name:
id: name
table: plan_field_data
field: name
relationship: none
group_type: group
admin_label: ''
operator: contains
value: ''
group: 1
exposed: true
expose:
operator_id: name_op
label: Name
description: ''
use_operator: false
operator: name_op
identifier: name
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
placeholder: ''
operator_limit_selection: false
operator_list: { }
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
entity_type: plan
entity_field: name
plugin_id: string
type:
id: type
table: plan_field_data
field: type
relationship: none
group_type: group
admin_label: ''
operator: in
value: { }
group: 1
exposed: true
expose:
operator_id: type_op
label: 'Plan type'
description: ''
use_operator: false
operator: type_op
identifier: type
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
reduce: false
operator_limit_selection: false
operator_list: { }
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
entity_type: plan
entity_field: type
plugin_id: bundle
sorts:
created:
id: created
table: plan_field_data
field: created
relationship: none
group_type: group
admin_label: ''
order: ASC
exposed: false
expose:
label: ''
granularity: second
entity_type: plan
entity_field: created
plugin_id: date
title: Plans
header: { }
footer: { }
empty:
area_text_custom:
id: area_text_custom
table: views
field: area_text_custom
relationship: none
group_type: group
admin_label: ''
empty: true
tokenize: false
content: 'No plans available.'
plugin_id: text_custom
relationships: { }
arguments: { }
display_extenders: { }
filter_groups:
operator: AND
groups:
1: AND
cache_metadata:
max-age: 0
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- url.query_args
- user.permissions
tags: { }
collection:
display_plugin: page
id: collection
display_title: Page
position: 1
display_options:
display_extenders: { }
path: admin/content/plan
menu:
type: tab
title: Plans
description: ''
expanded: false
parent: ''
weight: 0
context: '0'
menu_name: admin
tab_options:
type: normal
title: plan
description: ''
weight: 0
cache_metadata:
max-age: 0
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- url.query_args
- user.permissions
tags: { }

View File

@@ -0,0 +1,50 @@
# Schema for the configuration files of the plan module.
plan.type.*:
type: config_entity
label: 'Plan type'
mapping:
id:
type: string
label: 'Machine-readable name'
label:
type: label
label: 'Type'
description:
type: text
label: 'Description'
workflow:
type: string
label: 'Workflow'
new_revision:
type: boolean
label: 'Create new revision'
plan.record.type.*:
type: config_entity
label: 'Plan record relationship type'
mapping:
id:
type: string
label: 'Machine-readable name'
label:
type: label
label: 'Type'
description:
type: text
label: 'Description'
condition.plugin.plan_type:
type: condition.plugin
mapping:
bundles:
type: sequence
sequence:
type: string
action.configuration.plan_activate_action:
type: action_configuration_default
label: 'Configuration for the plan activate action'
action.configuration.plan_archive_action:
type: action_configuration_default
label: 'Configuration for the plan archive action'

View File

@@ -0,0 +1,11 @@
name: Plan
description: Provides an plan entity type for real world record keeping.
type: module
package: Plan
core_version_requirement: ^10
dependencies:
- drupal:system (>=8.8.0)
- drupal:user
- drupal:views
- entity:entity
- state_machine:state_machine

View File

@@ -0,0 +1,11 @@
entity.plan.add_page:
route_name: 'entity.plan.add_page'
title: 'Add plan'
appears_on:
- entity.plan.collection
entity.plan_type.add_form:
route_name: 'entity.plan_type.add_form'
title: 'Add plan type'
appears_on:
- entity.plan_type.collection

View File

@@ -0,0 +1,5 @@
entity.plan_type.collection:
title: 'Plan types'
route_name: entity.plan_type.collection
description: 'List of plan types'
parent: system.admin_structure

View File

@@ -0,0 +1,4 @@
entity.plan.collection:
route_name: entity.plan.collection
title: 'Plans'
base_route: system.admin_content

View File

@@ -0,0 +1,73 @@
<?php
/**
* @file
* Hooks and customizations for the plan module.
*/
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function plan_help($route_name, RouteMatchInterface $route_match) {
$output = '';
// Main module help for the plan module.
if ($route_name == 'help.page.plan') {
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('Provides plan entity') . '</p>';
}
return $output;
}
/**
* Implements hook_theme().
*/
function plan_theme() {
return [
'plan' => [
'render element' => 'elements',
],
];
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function plan_theme_suggestions_plan(array $variables) {
$suggestions = [];
$plan = $variables['elements']['#plan'];
$sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
$suggestions[] = 'plan__' . $sanitized_view_mode;
$suggestions[] = 'plan__' . $plan->bundle();
$suggestions[] = 'plan__' . $plan->bundle() . '__' . $sanitized_view_mode;
$suggestions[] = 'plan__' . $plan->id();
$suggestions[] = 'plan__' . $plan->id() . '__' . $sanitized_view_mode;
return $suggestions;
}
/**
* Prepares variables for plan templates.
*
* Default template: plan.html.twig.
*
* @param array $variables
* An associative array containing:
* - elements: An associative array containing the plan information and any
* fields attached to the plan. Properties used:
* - #plan: A \Drupal\plan\Entity\Plan object. The plan entity.
* - attributes: HTML attributes for the containing element.
*/
function template_preprocess_plan(array &$variables) {
$variables['plan'] = $variables['elements']['#plan'] ?? NULL;
// Helpful $content variable for templates.
foreach (Element::children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
}

View File

@@ -0,0 +1,19 @@
administer plans:
title: 'Administer plans'
description: 'Admin access to all plan entities.'
restrict access: true
administer plan types:
title: 'Administer plan types'
description: 'Maintain the types of content available and the fields that are associated with those types.'
restrict access: true
view all plan revisions:
title: 'View all plan revisions'
description: 'Allow viewing plan entity revisions.'
restrict access: true
revert all plan revisions:
title: 'Revert all plan revisions'
description: 'Allow reverting to a previous plan entity revision.'
restrict access: true

View File

@@ -0,0 +1,37 @@
<?php
/**
* @file
* Post update hooks for the plan module.
*/
/**
* Install plan_record and plan_record_type entity types.
*/
function plan_post_update_install_plan_record(&$sandbox) {
\Drupal::entityDefinitionUpdateManager()->installEntityType(
\Drupal::entityTypeManager()->getDefinition('plan_record_type')
);
\Drupal::entityDefinitionUpdateManager()->installEntityType(
\Drupal::entityTypeManager()->getDefinition('plan_record')
);
}
/**
* Remove the plan_record data_table attribute.
*/
function plan_post_update_remove_plan_record_data_table(&$sandbox) {
// Load existing plan_record entity type and remove the data_table attribute.
$update_manager = \Drupal::entityDefinitionUpdateManager();
$plan_record = $update_manager->getEntityType('plan_record');
$plan_record->set('data_table', NULL);
// Get the existing field storage definitions. This is a required parameter
// for EntityDefinitionUpdateManagerInterface::updateFieldableEntityType.
// We cannot use EntityDefinitionUpdateManagerInterface::updateEntityType.
$field_storage_definitions = \Drupal::service('entity.last_installed_schema.repository')->getLastInstalledFieldStorageDefinitions('plan_record');
// Update the entity type.
$update_manager->updateFieldableEntityType($plan_record, $field_storage_definitions, $sandbox);
}

View File

@@ -0,0 +1,3 @@
plan:
label: plan
entity_type: plan

View File

@@ -0,0 +1,18 @@
plan_default:
id: plan_default
group: plan
label: 'Default'
states:
active:
label: Active
archived:
label: Archived
transitions:
archive:
label: 'Archive'
from: [active]
to: archived
to_active:
label: 'Make active'
from: [archived]
to: active

View File

@@ -0,0 +1,30 @@
<?php
namespace Drupal\plan\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines plan_record access logic.
*/
class PlanRecordAccess extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
// If a plan is referenced, access is based on access to the plan.
/** @var \Drupal\plan\Entity\PlanRecordInterface $plan */
if ($plan = $entity->getPlan()) {
return AccessResult::allowedIf($plan->access($operation, $account));
}
// Otherwise, delegate to the parent method.
return parent::checkAccess($entity, $operation, $account);
}
}

View File

@@ -0,0 +1,285 @@
<?php
namespace Drupal\plan\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 plan entity.
*
* @ingroup plan
*
* @ContentEntityType(
* id = "plan",
* label = @Translation("Plan"),
* bundle_label = @Translation("Plan type"),
* label_collection = @Translation("Plans"),
* label_singular = @Translation("plan"),
* label_plural = @Translation("plans"),
* label_count = @PluralTranslation(
* singular = "@count plan",
* plural = "@count plans",
* ),
* handlers = {
* "storage" = "Drupal\plan\PlanStorage",
* "access" = "\Drupal\entity\UncacheableEntityAccessControlHandler",
* "list_builder" = "\Drupal\plan\PlanListBuilder",
* "permission_provider" = "\Drupal\entity\UncacheableEntityPermissionProvider",
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "views_data" = "Drupal\views\EntityViewsData",
* "form" = {
* "add" = "Drupal\plan\Form\PlanForm",
* "edit" = "Drupal\plan\Form\PlanForm",
* "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
* "delete-multiple-confirm" = "Drupal\Core\Entity\Form\DeleteMultipleForm",
* },
* "route_provider" = {
* "default" = "Drupal\entity\Routing\AdminHtmlRouteProvider",
* "revision" = "\Drupal\entity\Routing\RevisionRouteProvider",
* },
* "local_task_provider" = {
* "default" = "\Drupal\entity\Menu\DefaultEntityLocalTaskProvider",
* },
* },
* base_table = "plan",
* data_table = "plan_field_data",
* revision_table = "plan_revision",
* translatable = TRUE,
* revisionable = TRUE,
* show_revision_ui = TRUE,
* admin_permission = "administer plans",
* entity_keys = {
* "id" = "id",
* "revision" = "revision_id",
* "bundle" = "type",
* "label" = "name",
* "owner" = "uid",
* "uuid" = "uuid",
* "langcode" = "langcode",
* },
* bundle_entity_type = "plan_type",
* field_ui_base_route = "entity.plan_type.edit_form",
* common_reference_target = TRUE,
* permission_granularity = "bundle",
* links = {
* "canonical" = "/plan/{plan}",
* "add-page" = "/plan/add",
* "add-form" = "/plan/add/{plan_type}",
* "collection" = "/admin/content/plan",
* "delete-form" = "/plan/{plan}/delete",
* "delete-multiple-form" = "/plan/delete",
* "edit-form" = "/plan/{plan}/edit",
* "revision" = "/plan/{plan}/revisions/{plan_revision}/view",
* "revision-revert-form" = "/plan/{plan}/revisions/{plan_revision}/revert",
* "version-history" = "/plan/{plan}/revisions",
* },
* revision_metadata_keys = {
* "revision_user" = "revision_user",
* "revision_created" = "revision_created",
* "revision_log_message" = "revision_log_message"
* },
* )
*/
class Plan extends RevisionableContentEntityBase implements PlanInterface {
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 getArchivedTime() {
return $this->get('archived')->value;
}
/**
* {@inheritdoc}
*/
public function setArchivedTime($timestamp) {
$this->set('archived', $timestamp);
return $this;
}
/**
* {@inheritdoc}
*/
public function getBundleLabel() {
/** @var \Drupal\plan\Entity\PlanTypeInterface $type */
$type = $this->entityTypeManager()
->getStorage('plan_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 plan.'))
->setRevisionable(TRUE)
->setTranslatable(TRUE)
->setRequired(TRUE)
->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['status'] = BaseFieldDefinition::create('state')
->setLabel(t('Status'))
->setDescription(t('Indicates the status of the plan.'))
->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\plan\Entity\Plan', 'getWorkflowId']);
$fields['uid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Authored by'))
->setDescription(t('The user ID of author of the plan.'))
->setRevisionable(TRUE)
->setSetting('target_type', 'user')
->setSetting('handler', 'default')
->setDefaultValueCallback('Drupal\plan\Entity\Plan::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('form', TRUE)
->setDisplayConfigurable('view', TRUE);
$fields['created'] = BaseFieldDefinition::create('created')
->setLabel(t('Authored on'))
->setDescription(t('The time that the plan was created.'))
->setRevisionable(TRUE)
->setDefaultValueCallback(static::class . '::getRequestTime')
->setDisplayOptions('view', [
'label' => 'hidden',
'type' => 'timestamp',
'weight' => 0,
])
->setDisplayOptions('form', [
'type' => 'datetime_timestamp',
'weight' => 13,
])
->setDisplayConfigurable('form', TRUE);
$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Changed'))
->setDescription(t('The time the plan was last edited.'))
->setRevisionable(TRUE);
$fields['archived'] = BaseFieldDefinition::create('timestamp')
->setLabel(t('Timestamp'))
->setDescription(t('The time the plan was archived.'))
->setRevisionable(TRUE);
return $fields;
}
/**
* Gets the workflow ID for the state field.
*
* @param \Drupal\plan\Entity\PlanInterface $plan
* The plan entity.
*
* @return string
* The workflow ID.
*/
public static function getWorkflowId(PlanInterface $plan) {
$workflow = PlanType::load($plan->bundle())->getWorkflowId();
return $workflow;
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace Drupal\plan\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 plan entities.
*
* @ingroup plan
*/
interface PlanInterface extends ContentEntityInterface, EntityChangedInterface, RevisionLogInterface, EntityOwnerInterface {
/**
* Gets the plan name.
*
* @return string
* The plan name.
*/
public function getName();
/**
* Sets the plan name.
*
* @param string $name
* The plan name.
*
* @return \Drupal\plan\Entity\PlanInterface
* The plan entity.
*/
public function setName($name);
/**
* Gets the plan creation timestamp.
*
* @return int
* Creation timestamp of the plan.
*/
public function getCreatedTime();
/**
* Sets the plan creation timestamp.
*
* @param int $timestamp
* Creation timestamp of the plan.
*
* @return \Drupal\plan\Entity\PlanInterface
* The plan entity.
*/
public function setCreatedTime($timestamp);
/**
* Gets the plan archived timestamp.
*
* @return int
* Archived timestamp of the plan.
*/
public function getArchivedTime();
/**
* Sets the plan archived timestamp.
*
* @param int $timestamp
* Archived timestamp of the plan.
*
* @return \Drupal\plan\Entity\PlanInterface
* The plan entity.
*/
public function setArchivedTime($timestamp);
/**
* Gets the label of the the plan type.
*
* @return string
* The label of the plan type.
*/
public function getBundleLabel();
}

View File

@@ -0,0 +1,91 @@
<?php
namespace Drupal\plan\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
/**
* Defines the Plan record relationship entity.
*
* This entity type can be used to create relationships between a plan and other
* record(s) along with additional metadata fields to describe the relationship.
*
* @ContentEntityType(
* id = "plan_record",
* label = @Translation("Plan record relationship"),
* bundle_label = @Translation("Plan record relationship type"),
* label_collection = @Translation("Plan record relationships"),
* label_singular = @Translation("plan record relationship"),
* label_plural = @Translation("plan record relationships"),
* label_count = @PluralTranslation(
* singular = "@count plan record relationship",
* plural = "@count plan record relationships",
* ),
* handlers = {
* "access" = "Drupal\plan\Access\PlanRecordAccess",
* "form" = {
* "edit" = "Drupal\Core\Entity\ContentEntityForm",
* },
* "route_provider" = {
* "default" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
* },
* },
* base_table = "plan_record",
* entity_keys = {
* "id" = "id",
* "uuid" = "uuid",
* "label" = "uuid",
* "bundle" = "type",
* },
* bundle_entity_type = "plan_record_type",
* common_reference_target = TRUE,
* links = {
* "edit-form" = "/plan/record/{plan_record}/edit",
* },
* )
*/
class PlanRecord extends ContentEntityBase implements PlanRecordInterface {
/**
* {@inheritdoc}
*/
public function getBundleLabel() {
/** @var \Drupal\plan\Entity\PlanRecordTypeInterface $type */
$type = $this->entityTypeManager()
->getStorage('plan_record_type')
->load($this->bundle());
return $type->label();
}
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields['plan'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Plan'))
->setDescription(t('Associate this plan record relationship with a plan entity.'))
->setTranslatable(FALSE)
->setCardinality(1)
->setSetting('target_type', 'plan')
->setDisplayOptions('form', [
'type' => 'entity_reference',
'weight' => 12,
])
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);
return $fields;
}
/**
* {@inheritdoc}
*/
public function getPlan(): ?PlanInterface {
return $this->get('plan')->first()?->entity;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Drupal\plan\Entity;
use Drupal\Core\Entity\ContentEntityInterface;
/**
* Provides an interface for defining plan record relationship entities.
*/
interface PlanRecordInterface extends ContentEntityInterface {
/**
* Gets the label of the plan record relationship type.
*
* @return string
* The label of the plan record relationship type.
*/
public function getBundleLabel();
/**
* Returns the Plan entity the plan record is assigned to.
*
* @return \Drupal\plan\Entity\PlanInterface|null
* The plant entity or NULL if not assigned.
*/
public function getPlan(): ?PlanInterface;
}

View File

@@ -0,0 +1,74 @@
<?php
namespace Drupal\plan\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
/**
* Defines the Plan record relationship type entity.
*
* @ConfigEntityType(
* id = "plan_record_type",
* label = @Translation("Plan record relationship type"),
* label_collection = @Translation("Plan record relationship types"),
* label_singular = @Translation("Plan record relationship type"),
* label_plural = @Translation("plan record relationship types"),
* label_count = @PluralTranslation(
* singular = "@count plan record relationship type",
* plural = "@count plan record relationship types",
* ),
* handlers = {
* "access" = "\Drupal\entity\BundleEntityAccessControlHandler",
* },
* config_prefix = "record.type",
* bundle_of = "plan_record",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "uuid" = "uuid",
* },
* config_export = {
* "id",
* "label",
* "description",
* }
* )
*/
class PlanRecordType extends ConfigEntityBundleBase implements PlanRecordTypeInterface {
/**
* The Plan record relationship type ID.
*
* @var string
*/
protected $id;
/**
* The Plan record relationship type label.
*
* @var string
*/
protected $label;
/**
* A brief description of this plan record relationship type.
*
* @var string
*/
protected $description;
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->description;
}
/**
* {@inheritdoc}
*/
public function setDescription($description) {
return $this->set('description', $description);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Drupal\plan\Entity;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\EntityDescriptionInterface;
/**
* Provides an interface for defining Plan record relationship type entities.
*/
interface PlanRecordTypeInterface extends ConfigEntityInterface, EntityDescriptionInterface {
}

View File

@@ -0,0 +1,178 @@
<?php
namespace Drupal\plan\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
use Drupal\Core\Entity\EntityStorageInterface;
/**
* Defines the plan type entity.
*
* @ConfigEntityType(
* id = "plan_type",
* label = @Translation("Plan type"),
* label_collection = @Translation("Plan types"),
* label_singular = @Translation("Plan type"),
* label_plural = @Translation("Plan types"),
* label_count = @PluralTranslation(
* singular = "@count plan type",
* plural = "@count plan types",
* ),
* handlers = {
* "list_builder" = "Drupal\plan\PlanTypeListBuilder",
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "form" = {
* "add" = "Drupal\plan\Form\PlanTypeForm",
* "edit" = "Drupal\plan\Form\PlanTypeForm",
* "delete" = "\Drupal\Core\Entity\EntityDeleteForm",
* },
* "route_provider" = {
* "default" = "Drupal\entity\Routing\DefaultHtmlRouteProvider",
* },
* },
* admin_permission = "administer plan types",
* config_prefix = "type",
* bundle_of = "plan",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "uuid" = "uuid"
* },
* links = {
* "canonical" = "/admin/structure/plan-type/{plan_type}",
* "add-form" = "/admin/structure/plan-type/add",
* "edit-form" = "/admin/structure/plan-type/{plan_type}/edit",
* "delete-form" = "/admin/structure/plan-type/{plan_type}/delete",
* "collection" = "/admin/structure/plan-type"
* },
* config_export = {
* "id",
* "label",
* "description",
* "workflow",
* "new_revision",
* }
* )
*/
class PlanType extends ConfigEntityBundleBase implements PlanTypeInterface {
/**
* The plan type ID.
*
* @var string
*/
protected $id;
/**
* The plan type label.
*
* @var string
*/
protected $label;
/**
* A brief description of this plan type.
*
* @var string
*/
protected $description;
/**
* The plan type workflow ID.
*
* @var string
*/
protected $workflow;
/**
* Default value of the 'Create new revision' checkbox of the plan 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 postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
// If the plan type id changed, update all existing plans of that type.
if ($update && $this->getOriginalId() != $this->id()) {
$update_count = $this->entityTypeManager()->getStorage('plan')->updateType($this->getOriginalId(), $this->id());
if ($update_count) {
\Drupal::messenger()->addMessage(\Drupal::translation()->formatPlural($update_count,
'Changed the plan type of 1 post from %old-type to %type.',
'Changed the plan 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.
$this->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 plan 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,12 @@
<?php
namespace Drupal\plan\Entity;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\EntityDescriptionInterface;
use Drupal\Core\Entity\RevisionableEntityBundleInterface;
/**
* Provides an interface for defining plan type entities.
*/
interface PlanTypeInterface extends ConfigEntityInterface, EntityDescriptionInterface, RevisionableEntityBundleInterface {}

View File

@@ -0,0 +1,26 @@
<?php
namespace Drupal\plan\Form;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;
/**
* Form controller for plan entities.
*
* @ingroup plan
*/
class PlanForm extends ContentEntityForm {
/**
* {@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 plan: <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,115 @@
<?php
namespace Drupal\plan\Form;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\state_machine\WorkflowManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form controller for plan type entities.
*
* @package Drupal\plan\Form
*/
class PlanTypeForm extends EntityForm {
/**
* The workflow manager.
*
* @var \Drupal\state_machine\WorkflowManagerInterface
*/
protected $workflowManager;
/**
* Constructs a new PlanTypeForm object.
*
* @param \Drupal\state_machine\WorkflowManagerInterface $workflow_manager
* The workflow manager.
*/
public function __construct(WorkflowManagerInterface $workflow_manager) {
$this->workflowManager = $workflow_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.workflow')
);
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
$plan_type = $this->entity;
$form['label'] = [
'#type' => 'textfield',
'#title' => $this->t('Label'),
'#maxlength' => 255,
'#default_value' => $plan_type->label(),
'#description' => $this->t('Label for the plan type.'),
'#required' => TRUE,
];
$form['id'] = [
'#type' => 'machine_name',
'#default_value' => $plan_type->id(),
'#machine_name' => [
'exists' => '\Drupal\plan\Entity\PlanType::load',
],
'#disabled' => !$plan_type->isNew(),
];
$form['description'] = [
'#type' => 'textarea',
'#title' => $this->t('Description'),
'#default_value' => $plan_type->getDescription(),
];
$form['workflow'] = [
'#type' => 'select',
'#title' => $this->t('Workflow'),
'#options' => $this->workflowManager->getGroupedLabels('plan'),
'#default_value' => $plan_type->getWorkflowId(),
'#description' => $this->t('Used by all plans of this type.'),
];
$form['new_revision'] = [
'#type' => 'checkbox',
'#title' => $this->t('Create new revision'),
'#default_value' => $plan_type->shouldCreateNewRevision(),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$plan_type = $this->entity;
$status = $plan_type->save();
switch ($status) {
case SAVED_NEW:
$this->messenger()->addMessage($this->t('Created the %label plan type.', [
'%label' => $plan_type->label(),
]));
break;
default:
$this->messenger()->addMessage($this->t('Saved the %label plan type.', [
'%label' => $plan_type->label(),
]));
}
$form_state->setRedirectUrl($plan_type->toUrl('collection'));
return $status;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Drupal\plan;
use Drupal\Core\Entity\EntityInterface;
use Drupal\entity\BulkFormEntityListBuilder;
/**
* Defines a class to build a listing of plan entities.
*
* @ingroup plan
*/
class PlanListBuilder extends BulkFormEntityListBuilder {
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['id'] = $this->t('Plan ID');
$header['label'] = $this->t('Label');
$header['type'] = $this->t('Type');
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
/** @var \Drupal\plan\Entity\PlanInterface $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,128 @@
<?php
namespace Drupal\plan;
use Drupal\Component\Datetime\TimeInterface;
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 Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the controller class for plans.
*
* This extends the base storage class, adding required special handling for
* plan entities.
*/
class PlanStorage extends SqlContentEntityStorage {
/**
* The time service.
*
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected $time;
/**
* Constructs an AssetStorage 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\Component\Datetime\TimeInterface $time
* The time 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, TimeInterface $time) {
parent::__construct($entity_type, $database, $entity_field_manager, $cache, $language_manager, $memory_cache, $entity_type_bundle_info, $entity_type_manager);
$this->time = $time;
}
/**
* {@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('datetime.time'),
);
}
/**
* {@inheritdoc}
*/
protected function doPreSave(EntityInterface $entity) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$id = parent::doPreSave($entity);
// If there is no original entity, bail.
if (empty($entity->original)) {
return $id;
}
// Load new and original states.
$new_state = $entity->get('status')->first()->getString();
$old_state = $entity->original->get('status')->first()->getString();
$state_unchanged = $new_state == $old_state;
// If the entity is not archived and this would otherwise not be a state
// transition but the archive timestamp is set, then transition to the
// archived state.
if ($state_unchanged && $old_state != 'archived' && $entity->getArchivedTime() != NULL) {
$entity->get('status')->first()->applyTransitionById('archive');
}
// If the entity is archived and this would otherwise not be a state
// transition but the archive timestemp is NULL, then transition to the
// active state.
if ($state_unchanged && $old_state == 'archived' && $entity->getArchivedTime() == NULL) {
$entity->get('status')->first()->applyTransitionById('to_active');
}
// If the state has not changed, bail.
if ($state_unchanged) {
return $id;
}
// If the state has changed to archived and no archived timestamp was
// specified, set it to the current time.
if ($new_state == 'archived' && $entity->getArchivedTime() == NULL) {
$entity->setArchivedTime($this->time->getRequestTime());
}
// Or, if the state has changed from archived, set a null value.
elseif ($old_state == 'archived') {
$entity->setArchivedTime(NULL);
}
return $id;
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Drupal\plan;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Url;
/**
* Provides a listing of plan type entities.
*/
class PlanTypeListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['label'] = $this->t('Plan 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 plan types available. <a href=":link">Add plan type</a>.', [
':link' => Url::fromRoute('entity.plan_type.add_form')->toString(),
]);
return $build;
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Drupal\plan\Plugin\Action;
/**
* Action that marks a plan as active.
*
* @Action(
* id = "plan_activate_action",
* label = @Translation("Makes a Plan active"),
* type = "plan"
* )
*/
class PlanActivate extends PlanStateChangeBase {
/**
* {@inheritdoc}
*/
protected $targetState = 'active';
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Drupal\plan\Plugin\Action;
/**
* Action that archives a plan.
*
* @Action(
* id = "plan_archive_action",
* label = @Translation("Archive a plan"),
* type = "plan"
* )
*/
class PlanArchive extends PlanStateChangeBase {
/**
* {@inheritdoc}
*/
protected $targetState = 'archived';
}

View File

@@ -0,0 +1,99 @@
<?php
namespace Drupal\plan\Plugin\Action;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Action\Plugin\Action\EntityActionBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\plan\Entity\PlanInterface;
/**
* Base class for actions that change the plan status state.
*/
abstract class PlanStateChangeBase extends EntityActionBase {
/**
* The target state to transition to.
*
* @var string
*/
protected $targetState;
/**
* {@inheritdoc}
*/
public function execute(?PlanInterface $plan = NULL) {
// Bail if there is no plan.
if (empty($plan)) {
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 = $plan->get('status')->first();
if ($state_item->getOriginalId() !== $this->targetState && $transition = $state_item->getWorkflow()->findTransition($state_item->getOriginalId(), $this->targetState)) {
$state_item->applyTransition($transition);
$plan->setNewRevision(TRUE);
// Validate the entity before saving.
$violations = $plan->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' => $plan->toUrl()->setAbsolute()->toString(),
'%entity_label' => $plan->label(),
],
),
);
return;
}
$plan->save();
}
}
/**
* {@inheritdoc}
*/
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\plan\Entity\PlanInterface $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,73 @@
<?php
namespace Drupal\plan\Plugin\migrate\source\d7;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
/**
* Plan source from database.
*
* @MigrateSource(
* id = "d7_plan",
* source_module = "farm_plan"
* )
*/
class Plan extends FieldableEntity {
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('farm_plan', 'fa')
->fields('fa')
->distinct()
->orderBy('id');
if (isset($this->configuration['bundle'])) {
$query->condition('fa.type', (array) $this->configuration['bundle'], 'IN');
}
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'id' => $this->t('The plan ID'),
'name' => $this->t('The plan name'),
'type' => $this->t('The plan type'),
'uid' => $this->t('The plan author ID'),
'created' => $this->t('Timestamp when the plan was created'),
'changed' => $this->t('Timestamp when the plan was last modified'),
'archived' => $this->t('Timestamp when the plan was archived'),
];
return $fields;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$id = $row->getSourceProperty('id');
$type = $row->getSourceProperty('type');
// Get Field API field values.
foreach ($this->getFields('farm_plan', $type) as $field_name => $field) {
$row->setSourceProperty($field_name, $this->getFieldValues('farm_plan', $field_name, $id));
}
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['id']['type'] = 'integer';
return $ids;
}
}

View File

@@ -0,0 +1,22 @@
{#
/**
* @file plan.html.twig
* Default theme implementation to present plan data.
*
* This template is used when viewing plan pages.
*
*
* Available variables:
* - content: A list of content items. Use 'content' to print all content, or
* - attributes: HTML attributes for the container element.
*
* @see template_preprocess_plan()
*
* @ingroup themeable
*/
#}
<div{{ attributes.addClass('plan') }}>
{% if content %}
{{- content -}}
{% endif %}
</div>

View File

@@ -0,0 +1,6 @@
id: default
label: default
description: 'Test plan type'
langcode: en
workflow: plan_default
new_revision: TRUE

View File

@@ -0,0 +1,7 @@
name: Plan module tests
description: Support module for plan testing.
type: module
package: Testing
core_version_requirement: ^10
dependencies:
- farm:plan

View File

@@ -0,0 +1,46 @@
langcode: en
status: true
dependencies:
module:
- plan
- datetime
- options
- user
id: plan_test_view
label: 'plan test view'
module: views
description: ''
tag: ''
base_table: plan_field_data
base_field: id
display:
default:
display_options:
defaults:
fields: false
pager: false
sorts: false
row:
type: fields
fields:
id:
id: id
table: plan_field_data
field: id
relationship: none
entity_type: plan
entity_field: id
plugin_id: field
name:
id: name
table: plan_field_data
field: name
relationship: none
entity_type: plan
entity_field: name
plugin_id: field
sorts: { }
display_plugin: default
display_title: Master
id: default
position: 0

View File

@@ -0,0 +1,178 @@
<?php
namespace Drupal\Tests\plan\Functional;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\plan\Entity\Plan;
/**
* Tests the plan CRUD.
*
* @group farm
*/
class PlanCRUDTest extends PlanTestBase {
use StringTranslationTrait;
/**
* Run all tests.
*/
public function testAll() {
$this->doTestFieldsVisibility();
$this->doTestCreatePlan();
$this->doTestViewPlan();
$this->doTestEditPlan();
$this->doTestDeletePlan();
$this->doTestArchivePlan();
$this->doTestArchivePlanViaTimestamp();
}
/**
* Fields are displayed correctly.
*/
public function doTestFieldsVisibility() {
$this->drupalGet('plan/add/default');
$assert_session = $this->assertSession();
$assert_session->statusCodeEquals(200);
$assert_session->fieldExists('name[0][value]');
$assert_session->fieldExists('status');
$assert_session->fieldExists('revision_log_message[0][value]');
$assert_session->fieldExists('uid[0][target_id]');
$assert_session->fieldExists('created[0][value][date]');
$assert_session->fieldExists('created[0][value][time]');
}
/**
* Create plan entity.
*/
public function doTestCreatePlan() {
$assert_session = $this->assertSession();
$name = $this->randomMachineName();
$edit = [
'name[0][value]' => $name,
];
$this->drupalGet('plan/add/default');
$this->submitForm($edit, 'Save');
$result = \Drupal::entityTypeManager()
->getStorage('plan')
->getQuery()
->accessCheck(TRUE)
->range(0, 1)
->execute();
$plan_id = reset($result);
$plan = Plan::load($plan_id);
$this->assertEquals($plan->get('name')->value, $name, 'plan has been saved.');
$assert_session->pageTextContains("Saved plan: $name");
$assert_session->pageTextContains($name);
}
/**
* Display plan entity.
*/
public function doTestViewPlan() {
$edit = [
'name' => $this->randomMachineName(),
'created' => \Drupal::time()->getRequestTime(),
];
$plan = $this->createPlanEntity($edit);
$plan->save();
$this->drupalGet($plan->toUrl('canonical'));
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains($edit['name']);
$this->assertSession()->responseContains(\Drupal::service('date.formatter')->format(\Drupal::time()->getRequestTime()));
}
/**
* Edit plan entity.
*/
public function doTestEditPlan() {
$plan = $this->createPlanEntity();
$plan->save();
$edit = [
'name[0][value]' => $this->randomMachineName(),
];
$this->drupalGet($plan->toUrl('edit-form'));
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains($edit['name[0][value]']);
}
/**
* Delete plan entity.
*/
public function doTestDeletePlan() {
$plan = $this->createPlanEntity();
$plan->save();
$label = $plan->getName();
$plan_id = $plan->id();
$this->drupalGet($plan->toUrl('delete-form'));
$this->submitForm([], 'Delete');
$this->assertSession()->responseContains($this->t('The @entity-type %label has been deleted.', [
'@entity-type' => $plan->getEntityType()->getSingularLabel(),
'%label' => $label,
]));
$this->assertNull(Plan::load($plan_id));
}
/**
* Plan archiving.
*/
public function doTestArchivePlan() {
$plan = $this->createPlanEntity();
$plan->save();
$this->assertEquals($plan->get('status')->first()->getString(), 'active', 'New plans are active by default');
$this->assertNull($plan->getArchivedTime(), 'Archived timestamp is null by default');
$plan->get('status')->first()->applyTransitionById('archive');
$plan->save();
$this->assertEquals($plan->get('status')->first()->getString(), 'archived', 'Plans can be archived');
$this->assertNotNull($plan->getArchivedTime(), 'Archived timestamp is saved');
$plan->get('status')->first()->applyTransitionById('to_active');
$plan->save();
$this->assertEquals($plan->get('status')->first()->getString(), 'active', 'Plans can be made active');
$this->assertNull($plan->getArchivedTime(), 'Plan made active has a null timestamp');
$plan->get('status')->first()->applyTransitionById('archive');
$plan->setArchivedTime('2021-07-17T19:45:49+00:00');
$plan->save();
$this->assertEquals($plan->get('status')->first()->getString(), 'archived', 'Plans can be archived with explicit timestamp');
$this->assertEquals($plan->getArchivedTime(), '2021-07-17T19:45:49+00:00', 'Explicit archived timestamp is saved');
}
/**
* Plan archiving/unarchiving via timestamp.
*/
public function doTestArchivePlanViaTimestamp() {
$plan = $this->createPlanEntity();
$plan->save();
$this->assertEquals($plan->get('status')->first()->getString(), 'active', 'New plans are active by default');
$this->assertNull($plan->getArchivedTime(), 'Archived timestamp is null by default');
$plan->setArchivedTime('2021-07-17T19:45:49+00:00');
$plan->save();
$this->assertEquals($plan->get('status')->first()->getString(), 'archived', 'Plans can be archived');
$this->assertEquals($plan->getArchivedTime(), '2021-07-17T19:45:49+00:00', 'Archived timestamp is saved');
$plan->setArchivedTime(NULL);
$plan->save();
$this->assertEquals($plan->get('status')->first()->getString(), 'active', 'Plans can be made active');
$this->assertNull($plan->getArchivedTime(), 'Plan made active has a null timestamp');
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace Drupal\Tests\plan\Functional;
use Drupal\Tests\farm_test\Functional\FarmBrowserTestBase;
/**
* Tests the plan CRUD.
*/
abstract class PlanTestBase extends FarmBrowserTestBase {
/**
* Modules to install.
*
* @var array
*/
protected static $modules = [
'plan',
'plan_test',
'entity',
'user',
'field',
'text',
];
/**
* A test user with administrative privileges.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->adminUser = $this->drupalCreateUser($this->getAdministratorPermissions());
$this->drupalLogin($this->adminUser);
drupal_flush_all_caches();
}
/**
* Gets the permissions for the admin user.
*
* @return string[]
* The permissions.
*/
protected function getAdministratorPermissions() {
return [
'access administration pages',
'administer plans',
'view any plan',
'create default plan',
'view any default plan',
'update own default plan',
'update any default plan',
'delete own default plan',
'delete any default plan',
];
}
/**
* Creates a plan entity.
*
* @param array $values
* Array of values to feed the entity.
*
* @return \Drupal\plan\Entity\PlanInterface
* The plan entity.
*/
protected function createPlanEntity(array $values = []) {
$storage = \Drupal::service('entity_type.manager')->getStorage('plan');
$entity = $storage->create($values + [
'name' => $this->randomMachineName(),
'created' => \Drupal::time()->getRequestTime(),
'type' => 'default',
]);
return $entity;
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Drupal\Tests\plan\Traits;
use Drupal\plan\Entity\Plan;
/**
* Provides methods to create plan entities.
*
* This trait is meant to be used only by test classes.
*/
trait PlanCreationTrait {
/**
* Creates an plan entity.
*
* @param array $values
* Array of values to feed the entity.
*
* @return \Drupal\plan\Entity\PlanInterface
* The plan entity.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
protected function createPlanEntity(array $values = []) {
/** @var \Drupal\plan\Entity\PlanInterface $entity */
$entity = Plan::create($values + [
'name' => $this->randomMachineName(),
'type' => 'default',
]);
$entity->save();
return $entity;
}
}