Instances.php in Smart Date 3.3.x
Same filename and directory in other branches
- 8.2 modules/smart_date_recur/src/Controller/Instances.php
- 3.x modules/smart_date_recur/src/Controller/Instances.php
- 3.0.x modules/smart_date_recur/src/Controller/Instances.php
- 3.1.x modules/smart_date_recur/src/Controller/Instances.php
- 3.2.x modules/smart_date_recur/src/Controller/Instances.php
- 3.4.x modules/smart_date_recur/src/Controller/Instances.php
Namespace
Drupal\smart_date_recur\ControllerFile
modules/smart_date_recur/src/Controller/Instances.phpView source
<?php
namespace Drupal\smart_date_recur\Controller;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Url;
use Drupal\smart_date\SmartDateTrait;
use Drupal\smart_date_recur\Entity\SmartDateOverride;
use Drupal\smart_date_recur\Entity\SmartDateRule;
use Drupal\smart_date_recur\Form\SmartDateOverrideDeleteAjaxForm;
use Drupal\smart_date_recur\Form\SmartDateOverrideForm;
use Drupal\smart_date_recur\Form\SmartDateRemoveInstanceForm;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Provides listings of instances (with overrides) for a specified rule.
*/
class Instances extends ControllerBase {
/**
* The rrule object whose instances are being listed.
*
* @var \Drupal\smart_date_recur\Entity\SmartDateRule
*/
protected $rrule;
/**
* The entity storage class.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $storage;
/**
* The entity type ID.
*
* @var string
*/
protected $entityTypeId;
/**
* Information about the entity type.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Indicating whether current controller instance uses Ajax.
*
* @var bool
*/
private $useAjax;
/**
* Provide a list of rule items with operations to change rule items.
*
* @return array
* A render array of list of instances, with actions/operations.
*/
public function listInstancesOutput() {
if (!($entity = $this->rrule
->getParentEntity())) {
return $this
->returnError();
}
$field_name = $this->rrule->field_name
->getString();
if ($this->rrule->limit
->isEmpty()) {
$month_limit = SmartDateRule::getMonthsLimit($this->rrule);
$before = strtotime('+' . (int) $month_limit . ' months');
}
else {
$before = NULL;
}
// Use generated instances so we have a full list, and override as we go.
$gen_instances = $this->rrule
->makeRuleInstances($before)
->toArray();
$instances = [];
foreach ($gen_instances as $gen_instance) {
$gen_index = $gen_instance
->getIndex();
$instances[$gen_index] = [
'value' => $gen_instance
->getStart()
->getTimestamp(),
'end_value' => $gen_instance
->getEnd()
->getTimestamp(),
];
}
if (empty($instances)) {
return $this
->returnError();
}
$overrides = $this->rrule
->getRuleOverrides();
// Build headers.
// Iterate through rows and check for existing overrides.
foreach ($instances as $index => &$instance) {
$row_class = '';
// Check for an override.
if (isset($overrides[$index])) {
// Check for rescheduled, overridden, or cancelled
// add an appropriate class for each, and actions.
$override = $overrides[$index];
if ($override->entity_id
->getString()) {
// Overridden, retrieve appropriate entity.
$override_type = 'overridden';
$override = $entity_storage
->load($override->entity_id
->getString());
$field = $override
->get($field_name);
// TODO: drill down and retrieve, replace values.
// TODO: drop in the URL to edit.
}
elseif ($override->value
->getString()) {
// Rescheduled, use values from override.
$override_type = 'rescheduled';
// TODO: drill down and retrieve, replace values.
$instance['value'] = $override->value
->getString();
$instance['end_value'] = $override->end_value
->getString();
}
else {
// Cancelled, so change class and actions.
$override_type = 'cancelled';
}
$instance['class'] = $override_type;
$instance['override'] = $override;
}
else {
}
$instance['rrule'] = $this->rrule
->id();
$instance['rrule_index'] = $index;
}
return $this
->render($instances);
}
/**
* Builds the render array for the listings.
*
* @param array $instances
* The data for instances to list.
*
* @return array
* A render array of the list and appropriate actions.
*
* @see \Drupal\Core\Entity\EntityListBuilder::render()
*/
private function render(array $instances) {
$build['table'] = [
'#type' => 'table',
'#attributes' => [
'id' => 'manage-instances',
],
'#header' => $this
->buildHeader(),
'#rows' => [],
'#empty' => $this
->t('There are no @label yet.', [
'@label' => 'recurring instances',
]),
];
foreach ($instances as $index => $instance) {
if ($row = $this
->buildRow($instance)) {
$build['table']['#rows'][$index] = $row;
}
}
$build['table']['#attached']['library'][] = 'smart_date_recur/smart_date_recur';
return $build;
}
/**
* Builds the header row for the listing.
*
* @return array
* A render array structure of header strings.
*/
public function buildHeader() {
$row['label'] = $this
->t('Instance');
$row['operations'] = $this
->t('Operations');
return $row;
}
/**
* Builds a row for an instance in the listing.
*
* @param array $instance
* The data for this row of the list.
*
* @return array
* A render array structure of fields for this entity.
*
* @see \Drupal\Core\Entity\EntityListBuilder::render()
*/
public function buildRow(array $instance) {
// Get format settings.
// TODO: make the choice of format configurable?
$format = \Drupal::getContainer()
->get('entity_type.manager')
->getStorage('smart_date_format')
->load('compact');
$settings = $format
->getOptions();
// Format range for this instance.
$row['label']['data'] = SmartDateTrait::formatSmartDate($instance['value'], $instance['end_value'], $settings);
if (isset($instance['class'])) {
$row['label']['class'][] = 'smart-date-instance--' . $instance['class'];
}
$row['operations']['data'] = $this
->buildOperations($instance);
return $row;
}
/**
* Builds a renderable list of operation links for the entity.
*
* @param array $instance
* The entity on which the linked operations will be performed.
*
* @return array
* A renderable array of operation links.
*/
public function buildOperations(array $instance) {
$build = [
'#type' => 'operations',
'#links' => $this
->getOperations($instance),
];
return $build;
}
/**
* Builds a list of operation links for the entity.
*
* @param array $instance
* The entity on which the linked operations will be performed.
*
* @return array
* A not-yet renderable array of operation links.
*/
public function getOperations(array $instance) {
$operations = [];
// Only one use case doesn't need this, so include by default.
$operations['remove'] = [
'title' => $this
->t('Remove Instance'),
'weight' => 80,
'url' => Url::fromRoute('smart_date_recur.instance.remove', [
'rrule' => $instance['rrule'],
'index' => $instance['rrule_index'],
]),
];
if ($this->useAjax) {
$operations['remove']['url'] = Url::fromRoute('smart_date_recur.instance.remove.ajax', [
'rrule' => $instance['rrule'],
'index' => $instance['rrule_index'],
'confirm' => 0,
]);
$operations['remove']['attributes']['class'][] = 'use-ajax';
}
if (isset($instance['override'])) {
// An override exists, so provide an option to revert (delete) it.
$operations['delete'] = [
'title' => $this
->t('Restore Default'),
'weight' => 100,
'url' => $instance['override']
->toUrl('delete-form'),
];
if ($this->useAjax) {
$operations['delete']['url'] = Url::fromRoute('smart_date_recur.instance.revert.ajax', [
'entity' => $instance['override']
->id(),
'confirm' => 0,
]);
$operations['delete']['attributes']['class'][] = 'use-ajax';
}
switch ($instance['class']) {
case 'cancelled':
// Only option should be to revert.
unset($operations['remove']);
break;
case 'rescheduled':
$operations['edit'] = [
'title' => $this
->t('Reschedule'),
'weight' => 0,
'url' => Url::fromRoute('smart_date_recur.instance.reschedule', [
'rrule' => $instance['rrule'],
'index' => $instance['rrule_index'],
]),
];
if ($this->useAjax) {
$operations['edit']['url'] = Url::fromRoute('smart_date_recur.instance.reschedule.ajax', [
'rrule' => $instance['rrule'],
'index' => $instance['rrule_index'],
]);
$operations['edit']['attributes']['class'][] = 'use-ajax';
}
case 'overriden':
// Removal handled by the delete action already defined.
// TODO: Update the URL of the Edit button above to point to the
// entity form of the referenced entity.
break;
}
}
else {
// Default state, so only options are: create override or cancel.
$operations['create'] = [
'title' => $this
->t('Override'),
'weight' => 10,
'url' => Url::fromRoute('smart_date_recur.instance.reschedule', [
'rrule' => $instance['rrule'],
'index' => $instance['rrule_index'],
]),
];
if ($this->useAjax) {
$operations['create']['url'] = Url::fromRoute('smart_date_recur.instance.reschedule.ajax', [
'rrule' => $instance['rrule'],
'index' => $instance['rrule_index'],
]);
$operations['create']['attributes']['class'][] = 'use-ajax';
}
}
// Sort the operations before returning them.
uasort($operations, '\\Drupal\\Component\\Utility\\SortArray::sortByWeightElement');
return $operations;
}
/**
* Builds a renderable array for an error due to invalid input.
*
* @return array
* A renderable array with the error message.
*/
private function returnError() {
return [
'#type' => 'markup',
'#markup' => t('An invalid value was received.'),
];
}
/**
* Use the overrides for this RRule object to update the parent entity.
*
* @param \Drupal\smart_date_recur\Entity\SmartDateRule $rrule
* The rule whose overrides will be applied to the parent entity.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirect to the view of the parent entity.
*/
public function applyChanges(SmartDateRule $rrule) {
// Get all the necessary data elements from the rrule object.
if (!($entity = $rrule
->getParentEntity())) {
return $this
->returnError();
}
$rid = $rrule
->id();
$field_name = $rrule->field_name
->getString();
// Retrieve all existing values for the field.
$values = $entity
->get($field_name)
->getValue();
$first_instance = FALSE;
// Go through the existing values and remove all this rule's instances.
foreach ($values as $index => $value) {
if ($value['rrule'] == $rid) {
if (!$first_instance) {
// Save the first instance to use as a template.
$first_instance = $value;
}
// Remove all existing values for this rrule, so they can be replaced.
unset($values[$index]);
}
}
// Retrieve all instances for this rule, with overrides applied.
$instances = $rrule
->getRuleInstances();
foreach ($instances as $rrule_index => $instance) {
// Apply instance values to our template, and add to the field values.
$first_instance['value'] = $instance['value'];
$first_instance['end_value'] = $instance['end_value'];
// Calculate the duration, since it isn't returned.
$first_instance['duration'] = ($instance['end_value'] - $instance['value']) / 60;
$first_instance['rrule_index'] = $rrule_index;
$values[] = $first_instance;
}
// Add to the entity, and save.
$entity
->set($field_name, $values);
$entity
->save();
// Redirect to the entity view.
return new RedirectResponse($entity
->toUrl()
->toString());
}
/**
* Removing a rule instance.
*
* @param int $index
* Index of the instance to remove.
* @param int|null $oid
* SmartDateOverride override id if existing.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
public function removeInstance(int $index, ?int $oid) {
$rrule = $this->rrule
->id();
// Delete existing override, if it exists.
if ($oid) {
$existing = SmartDateOverride::load($oid);
$existing
->delete();
}
$override = SmartDateOverride::create([
'rrule' => $rrule,
'rrule_index' => $index,
]);
$override
->save();
}
/**
* Preparing the form for removing a rule instance via Ajax.
*
* @param \Drupal\smart_date_recur\Entity\SmartDateRule $rrule
* The rule object.
* @param int $index
* Index of the instance to remove.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* The AJAX response object.
*/
public function removeAjax(SmartDateRule $rrule, int $index) {
$this
->setSmartDateRule($rrule);
$this
->setUseAjax(TRUE);
$content = \Drupal::formBuilder()
->getForm(SmartDateRemoveInstanceForm::class, $rrule, $index, TRUE);
$content['title']['#markup'] = '<p>' . $content['#title'] . '</p>';
$form['#attached']['library'][] = 'core/drupal.ajax';
$response = new AjaxResponse();
$response
->addCommand(new ReplaceCommand('#manage-instances', $content));
return $response;
}
/**
* Preparing output of instance listing either modal/Ajax or default.
*
* @param \Drupal\smart_date_recur\Entity\SmartDateRule $rrule
* The rule object.
* @param bool $modal
* Whether or not to use a modal for display.
*
* @return array|\Drupal\Core\Ajax\AjaxResponse
* The AJAX response object.
*/
public function listInstances(SmartDateRule $rrule, bool $modal = FALSE) {
$this
->setSmartDateRule($rrule);
if ($modal) {
$this
->setUseAjax(TRUE);
}
$instancesList = $this
->listInstancesOutput();
if ($modal) {
$response = new AjaxResponse();
$response
->addCommand(new OpenModalDialogCommand('Manage Instances', $instancesList, [
'width' => '800',
]));
return $response;
}
else {
return $instancesList;
}
}
/**
* Reverting a rule instance in an Ajax confirm dialog.
*
* @param \Drupal\smart_date_recur\Entity\SmartDateOverride $entity
* The override entity to remove.
* @param bool $confirm
* Whether or not the removal has been confirmed.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* The AJAX response object.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function revertAjax(SmartDateOverride $entity, bool $confirm) {
if ($confirm) {
$rrule = $this
->entityTypeManager()
->getStorage('smart_date_rule')
->load($entity->rrule->value);
$this
->setSmartDateRule($rrule);
$this
->setUseAjax(TRUE);
$this
->revertInstance($entity);
$content = $this
->listInstancesOutput();
}
else {
$content = \Drupal::formBuilder()
->getForm(SmartDateOverrideDeleteAjaxForm::class, $entity);
}
$form['#attached']['library'][] = 'core/drupal.ajax';
$response = new AjaxResponse();
$response
->addCommand(new ReplaceCommand('#manage-instances', $content));
return $response;
}
/**
* Preparing the form for rescheduling a rule instance via Ajax.
*
* @param \Drupal\smart_date_recur\Entity\SmartDateRule $rrule
* The rule object.
* @param string $index
* Index of the instance to override.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* The AJAX response object.
*/
public function reschedule(SmartDateRule $rrule, string $index) {
$content = \Drupal::formBuilder()
->getForm(SmartDateOverrideForm::class, $rrule, $index, TRUE);
$form['#attached']['library'][] = 'core/drupal.ajax';
$response = new AjaxResponse();
$response
->addCommand(new ReplaceCommand('#manage-instances', $content));
return $response;
}
/**
* Revert instance by deleting the override.
*
* @param \Drupal\smart_date_recur\Entity\SmartDateOverride $entity
* The override entity to remove.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
public function revertInstance(SmartDateOverride $entity) {
$entity
->delete();
}
/**
* Setting the SmartDateRule on the controller.
*
* @param \Drupal\smart_date_recur\Entity\SmartDateRule $rrule
* The rule object.
*/
public function setSmartDateRule(SmartDateRule $rrule) {
$this->rrule = $rrule;
}
/**
* Setting the use ajax setting on the controller.
*
* @param bool $use_ajax
* Whether or not to use AJAX.
*/
public function setUseAjax(bool $use_ajax) {
$this->useAjax = $use_ajax;
}
}