stacks-8.x-1.x-dev/src/WidgetAdmin/Controller/WidgetAdmin.php
src/WidgetAdmin/Controller/WidgetAdmin.php
<?php
namespace Drupal\stacks\WidgetAdmin\Controller;
use Drupal\Core\Url;
use Drupal\stacks\Plugin\Field\FieldWidget\FormWidgetType;
use Drupal\stacks\Form\WidgetEntityDeleteFromContentForm;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\stacks\Ajax\CancelWidgetCommand;
use Drupal\stacks\Ajax\ReplaceWidgetCommand;
use Drupal\Core\Ajax\CloseDialogCommand;
use Drupal\stacks\Ajax\AttachOnChangeEvents;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Ajax\AppendCommand;
use Drupal\Core\Ajax\RemoveCommand;
use Drupal\Core\Entity\Element\EntityAutocomplete;
use Drupal\stacks\Ajax\UndoWidgetDeleteCommand;
use Drupal\views\Plugin\views\field\Field;
use Symfony\Component\HttpFoundation\JsonResponse;
use Drupal\views\Views;
use Drupal\stacks\Entity\WidgetInstanceEntity;
use Drupal\stacks\Widget\WidgetData;
use Drupal\stacks\Plugin\Field\FieldWidget;
class WidgetAdmin extends ControllerBase {
/**
* @inheritDoc.
*/
static public function validateWidgetName($current = "", $search = "") {
// Exclude current entity ID, so it does not validate against itself.
if($search != '') {
// Using autocomplete engine to check for matches.
$autocomplete = \Drupal::service('entity.autocomplete_matcher');
$matches = $autocomplete->getMatches('widget_instance_entity', 'default', ['match_operator' => '='], $search);
$endresult = [];
foreach($matches as $match) {
// Helper function to extract the ID from an autocomplete match in the form of "Label (entityID)".
$id = EntityAutocomplete::extractEntityIdFromAutocompleteInput($match['value']);
if($id != $current) {
$endresult[] = $match;
}
}
if(count($endresult)) {
$response = "FAIL";
}
else {
$response = "OK";
}
return new JsonResponse($response);
}
}
/**
* Submit status.
*/
static public function nodeSubmitStatus(AjaxResponse $response, $status) {
if($status) {
$response->addCommand(new InvokeCommand('#edit-actions input', 'removeAttr', ['disabled']));
$response->addCommand(new InvokeCommand('#edit-actions input', 'removeClass', ['is-disabled']));
}
else {
$response->addCommand(new InvokeCommand('#edit-actions input', 'attr', ['disabled', 'true']));
$response->addCommand(new InvokeCommand('#edit-actions input', 'addClass', ['is-disabled']));
}
}
/**
* Returns the widget admin form via AJAX
*/
static public function ajaxForm() {
$delta = (int)$_GET['delta'];
$response = new AjaxResponse();
WidgetAdmin::nodeSubmitStatus($response, FALSE);
$response->addCommand(new HtmlCommand('#widget-form-' . $delta, \Drupal::formBuilder()->getForm('Drupal\stacks\WidgetAdmin\Form\WidgetFormAdmin')));
$response->addCommand(new InvokeCommand('#widget-form-' . $delta, 'attr', ['data-haschanged', 'false']));
$response->addCommand(new AttachOnChangeEvents('#widget-form-' . $delta));
return $response;
}
/**
* Return the delete form.
*/
static public function ajaxFormDelete() {
$delta = (int)$_GET['delta'];
$response = new AjaxResponse();
WidgetAdmin::nodeSubmitStatus($response, TRUE);
// Clear out the widget instance hidden text field.
$response->addCommand(new InvokeCommand('#widget-instance-' . $delta . ' input', 'val', ['']));
// Display message.
$widget_instance_id = $_GET['widget_instance_id'];
$widget_instance = WidgetInstanceEntity::load($widget_instance_id);
$undo_link_options = $_GET;
// Top message
$element['undo_header'] = [
'#markup' => "<div class='widget-removed'><h3>'{$widget_instance->getTitle()}' " . t('has been removed') . "</h3>"
];
// Handle Undo Button
$element['undo'] = [
'#prefix' => '<p>' . t('Save the page to apply the changes or') . ' ',
'#suffix' => '</p></div>',
'#type' => 'link',
'#title' => t('click here to undo'),
'#url' => Url::fromRoute('stacks.admin.ajax_undo', [], ['query' => $undo_link_options]),
'#attributes' => [
'class' => ['use-ajax', 'undo-remove-widget'],
'data-dialog-type' => 'modal',
],
];
$response->addCommand(new HtmlCommand('#widget-form-' . $delta, $element));
return $response;
}
/**
* Undo delete.
* @return \Drupal\Core\Ajax\AjaxResponse
*/
static public function ajaxFormUndoDelete() {
$delta = (int)$_GET['delta'];
$response = new AjaxResponse();
$wrapper_id = "widget-form-{$delta}";
$widget_instance_id = $_GET['widget_instance_id'];
if ($widget_instance_id) {
// Using refactored function to build widget fields (see FormWidgetType.php).
$element = FormWidgetType::getWidgetInstanceField($wrapper_id, $widget_instance_id, ['query' => $_GET]);
}
$response->addCommand(new HtmlCommand('#widget-form-' . $delta, $element));
$response->addCommand(new UndoWidgetDeleteCommand('#widget-instance-' . $delta, $widget_instance_id));
return $response;
}
static public function ajaxFormEdit($nid = "", $id = "") {
$widget_instance = WidgetInstanceEntity::load($id);
$widget_entity = $widget_instance->getWidgetEntity();
$form = \Drupal::service('entity_type.manager')
->getFormObject('widget_entity', 'default')
->setEntity($widget_entity);
$build = \Drupal::formBuilder()->getForm($form);
// Add AJAX handlers for submission/deletion
$build['actions']['submit']['#attributes']['class'][] = 'use-ajax-submit';
$build['actions']['submit']['#process'] = [];
if (\Drupal::request()->query->get('stacks_dialog')) {
$build['#prefix'] = '<div id="edit-widget-dialog-wrapper">';
$build['#suffix'] = '</div>';
}
return $build;
}
/**
* Closes front-end editor dialog.
* @return \Drupal\Core\Ajax\AjaxResponse
*/
static public function ajaxModalClose($nid, $id, $field_name, $delta) {
// Get entity
$widget_instance = WidgetInstanceEntity::load($id);
$widget_entity = $widget_instance->getWidgetEntity();
$widget_data = new WidgetData();
// TODO: EXTEND THE ENTITY LOADING TO MORE THAN NODES.
// Get node
$node = \Drupal::entityTypeManager()->getStorage('node')->load($nid);
// Clearing node cache for this specific content.
// TODO: find alternatives on clearing node cache
// TODO: fix widget reload
$node->save();
// Field properties
$field_properties = [];
if (isset($field_name) && isset($delta)) {
$field_properties = [
'field_name' => $field_name,
'delta' => $delta,
];
}
// Get build array and return plain HTML.
$build = $widget_data->output($node, $widget_instance, $widget_entity, 'content', $field_properties);
$markup = \Drupal::service('renderer')->renderRoot($build);
// Call AJAX commands to close the Modal window and update the widget wrapper.
$response = new AjaxResponse();
$response->addCommand(new CloseDialogCommand('#edit-widget-dialog'));
// Building selector based on contextual menu information
$language = \Drupal::languageManager()->getCurrentLanguage()->getId();
$selector = '[data-contextual-id="stacks:nid='.$nid.'&id='.$id.'&field_name='.$field_name.'&delta='.$delta.':langcode='.$language.'"]';
$response->addCommand(new ReplaceWidgetCommand($selector, $markup));
// Dismissing Drupal messages regarding the updates
\Drupal::messenger()->deleteAll();
return $response;
}
/**
* Opens front-end editor deletion modal.
* @return \Drupal\Core\Ajax\AjaxResponse
*/
static public function ajaxFormDeleteFromContent($nid = "", $id = "") {
$form = new WidgetEntityDeleteFromContentForm();
$build = \Drupal::formBuilder()->getForm($form,
[
'#extra' => [
'nid' => $nid,
'id' => $id,
]
]
);
// $build['#extra'] = [
// 'nid' => $nid,
// 'id' => $id,
// ];
return $build;
}
static public function ajaxFormCancel() {
$delta = (int)$_GET['delta'];
$response = new AjaxResponse();
$response->addCommand(new CancelWidgetCommand('#widget-form-' . $delta));
return $response;
}
static public function dGridPreview(&$form, $form_state) {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$response = new AjaxResponse();
$ief_values = $form_state->getValue('inline_entity_form');
$content_types = [];
foreach ($ief_values['field_cfeed_content_types'] as $ct) {
$content_types[] = $ct['target_id'];
}
// Verify if taxonomy terms are applicable
$taxonomy_terms_valid = false;
$show_taxonomy_terms_error_msg = false;
if (count($content_types) && is_array($ief_values['field_cfeed_taxonomy_terms']) && count($ief_values['field_cfeed_taxonomy_terms'])) {
$show_taxonomy_terms_error_msg = true;
$tag_fields = self::getTaxonomyFieldsForContentTypes($content_types);
if (count($tag_fields)) {
$taxonomy_terms_valid = true;
}
}
$content_types = join('+', $content_types);
if($content_types == '') $content_types = 'all';
$taxonomy_terms = [];
if ($taxonomy_terms_valid) {
foreach($ief_values['field_cfeed_taxonomy_terms'] as $ct) {
$taxonomy_terms[] = $ct['target_id'];
}
}
$taxonomy_terms = join('+', $taxonomy_terms);
if($taxonomy_terms == '') $taxonomy_terms = 'all';
$items_per_page = intval(isset($ief_values['field_cfeed_results_per_page'][0]['value']) ? $ief_values['field_cfeed_results_per_page'][0]['value'] : 0);
$items_per_page = ($items_per_page == 0 ? 10 : $items_per_page);
$args = [];
$args[] = $content_types;
$args[] = $taxonomy_terms;
$view = Views::getView('stacks_content_feed_preview');
$view->setDisplay('default');
$view->preview = TRUE;
$view->preExecute($args);
$tsort = isset($ief_values['field_cfeed_order'][0]['value']) ? $ief_values['field_cfeed_order'][0]['value'] : 'title_asc';
if (preg_match('/(.*)_(asc|desc)/', $tsort, $matches)) {
// TODO: Match what's happening on the database query so that it doesn't use different logic.
// This will need to also alter the default content feed views and making sure that the
// field key used for field_cfeed_order is part of the sort handler
foreach ($view->display_handler->handlers['sort'] as $k => $sort) {
if (preg_match('/' . preg_quote($matches[1]) . '/', $k)) {
$view->display_handler->handlers['sort'][$k]->options['order'] = strtoupper($matches[2]);
}
else {
unset($view->display_handler->handlers['sort'][$k]);
}
}
}
$view->setItemsPerPage($items_per_page);
$result = $view->display_handler->preview();
$view->postExecute();
$htmlout = $renderer->render($result);
if (!$taxonomy_terms_valid && $show_taxonomy_terms_error_msg) {
$renderer_array = [
'#type' => 'container',
'#attributes' => [
'class' => [
'messages',
'messages--error',
],
'aria-label' => 'Error message',
'role' => 'contentinfo',
],
'div' => [
'#type' => 'html_tag',
'#tag' => 'div',
'#value' => t('Selected content types do not have suitable taxonomy term fields. Ignoring taxonomy terms filter.'),
'#attributes' => [
'role' => 'alert',
],
],
];
$htmlout = $renderer->render($renderer_array) . $htmlout;
}
$response->addCommand(new HtmlCommand('#contentfeed-grid-content-preview', ['#markup' => $htmlout]));
return $response;
}
/**
* Returns all taxonomy fields by content content type.
*
* @param $contentType
* @return array
*/
private static function getTaxonomyFieldsForContentTypes($contentTypes) {
$tag_fields = [];
if (is_array($contentTypes) && count($contentTypes)) {
foreach ($contentTypes as $content_type) {
$fields = \Drupal::service('entity_field.manager')->getFieldDefinitions('node', $content_type);
foreach ($fields as $field_name => $field) {
if ($field->getType() == 'entity_reference') {
$field_settings = $field->getSettings();
if ($field_settings['target_type'] == 'taxonomy_term') {
if(!empty($field_settings['handler_settings']['target_bundles'])) {
foreach ($field_settings['handler_settings']['target_bundles'] as $bundle) {
// Add this field to the array.
if (!isset($tag_fields[$bundle])) {
$tag_fields[$bundle] = [];
}
$tag_fields[$bundle][] = $field_name;
}
}
}
}
}
}
}
return $tag_fields;
}
}
