pm-4.1.x-dev/src/PmContentEntityForm.php
src/PmContentEntityForm.php
<?php
namespace Drupal\pm;
use Drupal\Core\Render\Element;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Utility\Html;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\RouterInterface;
class PmContentEntityForm extends ContentEntityForm {
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $request;
/**
* The tempstore factory.
*
* @var \Drupal\Core\TempStore\PrivateTempStoreFactory
*/
protected $tempStoreFactory;
/**
* The Current User object.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* The router service.
*
* @var \Drupal\Core\Routing\Router
*/
protected $router;
/**
* The whitelisted element types that can be pre-populated.
*
* The default list is intentionally limited to not include radios and
* checkboxes for security reasons. Site visitors cannot click a link
* to an admin page that prepopulates additional permissions or settings
* that are un-noticed and unknowingly aggregated to the site.
*
* @var array
*/
protected $whitelistedTypes = [
'container',
'date',
'datelist',
'datetime',
'entity_autocomplete',
'email',
'fieldset',
'inline_entity_form',
'language_select',
'machine_name',
'number',
'path',
'select',
'radios',
'tel',
'textarea',
'text_format',
'textfield',
'url',
];
/**
* Constructs a PmContentEntityForm object.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request
* The Request stack.
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The entity repository.
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
* The factory for the temp store object.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle service.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter service.
* @param \Symfony\Component\Routing\RouterInterface $router
* The Router service.
*/
public function __construct(RequestStack $request, EntityRepositoryInterface $entity_repository, PrivateTempStoreFactory $temp_store_factory, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL, AccountInterface $current_user, DateFormatterInterface $date_formatter, RouterInterface $router) {
parent::__construct($entity_repository, $entity_type_bundle_info, $time);
$this->tempStoreFactory = $temp_store_factory;
$this->currentUser = $current_user;
$this->dateFormatter = $date_formatter;
$this->request = $request;
$this->router = $router;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
/* @phpstan-ignore new.static */
return new static(
$container->get('request_stack'),
$container->get('entity.repository'),
$container->get('tempstore.private'),
$container->get('entity_type.bundle.info'),
$container->get('datetime.time'),
$container->get('current_user'),
$container->get('date.formatter'),
$container->get('router')
);
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
return $this->adjustForm($form);
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$result = parent::save($form, $form_state);
$entity = $this->getEntity();
$entity_type = $entity->getEntityType();
$message_arguments = [
'%label' => $entity->toLink()->toString(),
'%type' => $entity_type->getLabel(),
];
$logger_arguments = [
'%label' => $entity->label(),
'link' => $entity->toLink($this->t('View'))->toString(),
];
switch ($result) {
case SAVED_NEW:
$this->messenger()->addStatus($this->t('New %type %label has been created.', $message_arguments));
$this->logger('pm')->notice('Created new %type %label', $logger_arguments);
break;
case SAVED_UPDATED:
$this->messenger()->addStatus($this->t('The %type %label has been updated.', $message_arguments));
$this->logger('pm')->notice('Updated %type %label.', $logger_arguments);
break;
}
$arguments = [
$entity_type->id() => $entity->id(),
];
if ($entity->hasField('pm_project')) {
$project = $entity->get('pm_project');
if ($project_id = $project->getValue()['0']['target_id'] ?? NULL) {
$arguments['pm_project'] = $project_id;
}
}
$route = 'entity.' . $entity_type->id() . '.collection';
if ($this->router->getRouteCollection()->get($route)) {
$form_state->setRedirect($route, $arguments);
}
else {
$form_state->setRedirect('entity.' . $entity_type->id() . '.canonical', $arguments);
}
return $result;
}
/**
* Make form look pretty out of the box.
*/
protected function adjustForm(array $form): array {
/** @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = $this->entity;
$form['meta'] = [
'#type' => 'container',
'#group' => 'advanced',
'#weight' => -10,
'#title' => $this->t('Status'),
'#attributes' => ['class' => ['entity-meta__header']],
'#tree' => TRUE,
];
$form['meta']['changed'] = [
'#type' => 'item',
'#title' => $this->t('Last updated'),
'#markup' => !$entity->isNew() ? $this->dateFormatter->format($entity->getChangedTime(), 'short') : $this->t('Not saved yet'),
'#wrapper_attributes' => ['class' => ['entity-meta__last-saved']],
];
$form['meta']['author'] = [
'#type' => 'item',
'#title' => $this->t('Creator'),
'#markup' => $entity->getOwner()->getAccountName(),
'#wrapper_attributes' => ['class' => ['entity-meta__author']],
];
// Node author information for administrators.
$form['author'] = [
'#type' => 'details',
'#title' => $this->t('Editing information'),
'#group' => 'advanced',
'#attributes' => [
'class' => ['node-form-author'],
],
'#attached' => [
'library' => ['node/drupal.node'],
],
'#weight' => 90,
'#optional' => TRUE,
];
if (isset($form['uid'])) {
$form['uid']['#group'] = 'author';
}
if (isset($form['created'])) {
$form['created']['#group'] = 'author';
}
if (isset($form['pm_project'])) {
$form['pm_project']['#group'] = 'meta';
}
if (isset($form['pm_assignee'])) {
$form['pm_assignee']['#group'] = 'meta';
}
if (isset($form['pm_reviewer'])) {
$form['pm_reviewer']['#group'] = 'meta';
}
if (isset($form['pm_date'])) {
$form['pm_date']['#group'] = 'meta';
}
$form['#theme'] = ['node_edit_form'];
$form['#attached']['library'][] = 'node/form';
$form['#attached']['library'][] = 'node/drupal.node';
$form['#attached']['library'][] = 'claro/node-form';
// Following is very specific to Claro Theme.
$form['advanced'] = [
'#type' => 'container',
'#attributes' => ['class' => ['entity-meta']],
'#weight' => 99,
'#accordion' => TRUE,
];
$this->pmPopulateForm($form);
return $form;
}
/**
* Pre-populate form elements based on query param.
*/
protected function pmPopulateForm(array &$form, $request_slice = NULL) {
$query = $this->request->getCurrentRequest()->query->all();
if (isset($query['pm-project'])) {
$this->populateForm($form, [
'pm_project' => [
'widget' => $query['pm-project'],
],
]);
$this->populateForm($form, [
'pm_project' => [
'widget' => [
0 => [
'target_id' => $query['pm-project'],
],
],
],
]);
}
}
/**
* Populate form element in a FORM-API array.
*
* Heavily based on drupal/prepoulate module to adjust form elements.
*/
protected function populateForm(array &$form, $request_slice = NULL) {
if (is_array($request_slice)) {
foreach (array_keys($request_slice) as $request_variable) {
if (isset($form[$request_variable])) {
$element = &$form[$request_variable];
if (isset($element['widget'][0]['value']['#type'])) {
$type = $element['widget'][0]['value']['#type'];
}
elseif (isset($element['widget'][0]['target_id']['#type'])) {
$type = $element['widget'][0]['target_id']['#type'];
}
elseif (isset($element['widget']['#type'])) {
$type = $element['widget']['#type'];
}
elseif (isset($element['#type'])) {
$type = $element['#type'];
}
if (Element::child($request_variable) && !empty($element) && (empty($type) || in_array($type, $this->whitelistedTypes))) {
$this->populateForm($element, $request_slice[$request_variable]);
}
}
}
}
else {
// If we don't have a form type, we cannot do anything.
if (empty($form['#type'])) {
return;
}
// If we already have a value in the form, don't overwrite it.
if (!empty($form['#value']) && is_scalar($form['#value'])) {
return;
}
// If we don't have access, don't alter it.
if (isset($form['#access']) && $form['#access'] === FALSE) {
return;
}
$value = Html::escape($request_slice);
switch ($form['#type']) {
case 'radios':
$form['#value'] = $value;
$form['#default_value'] = $value;
$form['#disabled'] = TRUE;
break;
case 'select':
$form['#value'] = [$value];
$form['#default_value'] = [$value];
$form['#disabled'] = TRUE;
break;
case 'entity_autocomplete':
$form['#value'] = $this->formatEntityAutocomplete($value, $form);
$form['#disabled'] = TRUE;
break;
case 'checkbox':
$form['#checked'] = $value === 'true';
$form['#disabled'] = TRUE;
break;
default:
$form['#value'] = $value;
$form['#disabled'] = TRUE;
break;
}
}
}
/**
* Check access and properly format an autocomplete string.
*
* @param string $value
* The value.
* @param array $element
* The form element to populate.
*
* @return string
* The formatted label if entity exists and view label access is allowed.
* Otherwise, the value.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function formatEntityAutocomplete($value, array &$element) {
$entity = $this->entityTypeManager
->getStorage($element['#target_type'])
->load($value);
if ($entity && $entity->access('view label')) {
return "{$entity->label()} ($value)";
}
return $value;
}
}
