diff --git a/config/schema/webform_views.schema.yml b/config/schema/webform_views.schema.yml index 3d026d1..748074c 100644 --- a/config/schema/webform_views.schema.yml +++ b/config/schema/webform_views.schema.yml @@ -1,3 +1,7 @@ # Schema definition for select filter. views.filter.webform_submission_select_filter: type: views.filter.in_operator + +# Schema definition for composite field filter. +views.filter.webform_submission_composite_field_filter: + type: views.filter.string diff --git a/src/Plugin/views/field/WebformSubmissionCompositeField.php b/src/Plugin/views/field/WebformSubmissionCompositeField.php index 91b5758..ebf1cf3 100644 --- a/src/Plugin/views/field/WebformSubmissionCompositeField.php +++ b/src/Plugin/views/field/WebformSubmissionCompositeField.php @@ -2,15 +2,10 @@ namespace Drupal\webform_views\Plugin\views\field; -use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Form\FormStateInterface; -use Drupal\views\Plugin\views\field\FieldPluginBase; use Drupal\views\ResultRow; -use Drupal\webform\Plugin\WebformElementManagerInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Webform submission field. + * Webform submission composite field. * * @ViewsField("webform_submission_composite_field") */ @@ -20,43 +15,36 @@ class WebformSubmissionCompositeField extends WebformSubmissionField { * {@inheritdoc} */ public function render(ResultRow $values) { - if ($values->_entity->access('view')) { - $view_builder = $this->entityTypeManager->getViewBuilder('webform_submission'); + /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ + $webform_submission = $this->getEntity($values); - /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ - $webform_submission = $values->_entity; + if ($webform_submission->access('view')) { $webform = $webform_submission->getWebform(); - $elements = $webform->getElementsInitialized(); - if (!isset($elements[$this->definition['webform_submission_field']])) { - $elements = $webform->getElementsInitializedAndFlattened(); - } - $excluded_elements = $elements; - unset($excluded_elements[$this->definition['webform_submission_field']]); + // Get format and element key. + $format = $this->options['webform_element_format']; + $element_key = $this->definition['webform_submission_field']; + $composite_key = $this->definition['webform_submission_property']; + + // Get element and element handler plugin. + $element = $webform->getElement($element_key, TRUE); + if (!$element) { + return []; + } - // Hiding the title, since it is probably included on the views level. - $elements[$this->definition['webform_submission_field']]['#title_display'] = 'invisible'; - $elements[$this->definition['webform_submission_field']]['#format'] = $this->options['webform_element_format']; + // Set the format. + $element['#format'] = $format; - $result = $view_builder->buildElements($elements, $webform_submission, [ - 'excluded_elements' => $excluded_elements, - ]); + $composite_element = $element['#webform_composite_elements'][$composite_key]; + $composite_element['#webform_key'] = $element['#webform_key']; + $options = [ + 'composite_key' => $composite_key, + ]; - $return = $result[$this->definition['webform_submission_field']]['#value'][$this->definition['webform_submission_property']]; - $return['#template'] = '{{ value }}'; - return $return; + return $this->webformElementManager->invokeMethod('formatHtml', $composite_element, $webform_submission, $options); } return []; } - /** - * {@inheritdoc} - */ - public function clickSort($order) { - $webform_submission_data_alias = $this->ensureMyTable(); - $params = $this->options['group_type'] != 'group' ? ['function' => $this->options['group_type']] : []; - $this->query->addOrderBy($webform_submission_data_alias, 'value', $order, '', $params); - } - } diff --git a/src/Plugin/views/field/WebformSubmissionField.php b/src/Plugin/views/field/WebformSubmissionField.php index 692ab3e..a61a4e2 100644 --- a/src/Plugin/views/field/WebformSubmissionField.php +++ b/src/Plugin/views/field/WebformSubmissionField.php @@ -85,9 +85,6 @@ class WebformSubmissionField extends FieldPluginBase { $webform_submission = $this->getEntity($values); if ($webform_submission->access('view')) { - /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */ - $element_manager = \Drupal::service('plugin.manager.webform.element'); - $webform = $webform_submission->getWebform(); // Get format and element key. @@ -104,7 +101,7 @@ class WebformSubmissionField extends FieldPluginBase { $element['#format'] = $format; // Get element handler and get the element's HTML render array. - $element_handler = $element_manager->getElementInstance($element); + $element_handler = $this->webformElementManager->getElementInstance($element); return $element_handler->formatHtml($element, $webform_submission); } diff --git a/src/Plugin/views/filter/WebformSubmissionCompositeFieldFilter.php b/src/Plugin/views/filter/WebformSubmissionCompositeFieldFilter.php index b6802b6..4f0e673 100644 --- a/src/Plugin/views/filter/WebformSubmissionCompositeFieldFilter.php +++ b/src/Plugin/views/filter/WebformSubmissionCompositeFieldFilter.php @@ -11,173 +11,11 @@ use Drupal\views\Plugin\views\filter\StringFilter; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Filter based on value of a webform submission. + * Filter based on value of a composite of a webform submission. * * @ViewsFilter("webform_submission_composite_field_filter") */ -class WebformSubmissionCompositeFieldFilter extends StringFilter { - - /** - * Constant that denotes using webform element type for value form. - * - * @var string - */ - const ELEMENT_TYPE = 'element'; - - /** - * @var EntityTypeManagerInterface - */ - protected $entityTypeManager; - - /** - * @var ElementInfoManagerInterface - */ - protected $elementInfoManager; - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('entity_type.manager'), - $container->get('plugin.manager.element_info') - ); - } - - /** - * WebformSubmissionFieldFilter constructor. - */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, ElementInfoManagerInterface $element_info_manager) { - parent::__construct($configuration, $plugin_id, $plugin_definition); - - $this->entityTypeManager = $entity_type_manager; - $this->elementInfoManager = $element_info_manager; - } - - /** - * {@inheritdoc} - */ - public function operatorForm(&$form, FormStateInterface $form_state) { - parent::operatorForm($form, $form_state); - - if (isset($form['operator'])) { - $do_ajax = TRUE; - - // We must guess the future #parents for this operator here. - if (!$this->isExposed()) { - $parents = ['options', 'operator']; - } - elseif (!$this->isAGroup()) { - // We cannot determine which row we are building, so we prefer to hold - // off ajax. - $do_ajax = FALSE; - $parents = []; - } - elseif ($this->options['expose']['use_operator']) { - $do_ajax = FALSE; - $parents = [$this->options['expose']['operator_id']]; - } - else { - // We shouldn't really fall into here. If we do, it means there is yet - // another unknown use case of this method. - $do_ajax = FALSE; - $parents = []; - } - $operator = NestedArray::getValue($form_state->getUserInput(), $parents); - if (!$operator || !isset($this->operators()[$operator])) { - $operator = $this->operator; - } - - if ($do_ajax) { - $process = $this->getFormElementProperty($form['operator'], '#process', []); - // We need to run before ajax process stuff since we dynamically insert - // the wrapper value for ajax. - array_unshift($process, [self::class, 'processOperatorForm']); - - $form['operator']['#ajax'] = [ - 'callback' => [self::class, 'ajaxValueForm'], - 'wrapper' => 'this-will-be-set-up-in-process', - ]; - $form['operator']['#process'] = $process; - } - else { - // We are in the scenario where Ajax will break. So we fall to some - // inoffensive (with relaxed validation) operator. - // See https://www.drupal.org/node/2804457 and - // https://www.drupal.org/node/2842525 for the bugs that force us to do - // it this way. - $operator = 'contains'; - } - - $form_state->set(['webform_views', 'filter_operator'], $operator); - } - } - - /** - * {@inheritdoc} - */ - public function valueForm(&$form, FormStateInterface $form_state) { - $webform = $this->entityTypeManager->getStorage('webform')->load($this->definition['webform_id']); - - $element = $webform->getElementInitialized($this->definition['webform_submission_field']); - foreach ($element['#webform_composite_elements'] as $composite_key => $composite_element) { - if ($this->definition['webform_submission_property'] != $composite_key) { - unset($element['#webform_composite_elements'][$composite_key]); - } - } - $element['#default_value'] = $this->value; - $element['#required'] = FALSE; - - $operator = $form_state->get(['webform_views', 'filter_operator']) ?: $this->operator; - $operator_definition = $this->operators()[$operator]; - // Swap the type of value if the current operator dictates doing so. - if (isset($operator_definition['webform_views_element_type']) && $operator_definition['webform_views_element_type'] != self::ELEMENT_TYPE) { - $element['#type'] = $operator_definition['webform_views_element_type']; - } - - // Wrap the value with a container that will be used for AJAX. - $html_id = Html::getUniqueId($this->pluginId); - $form_state->set(['webform_views', 'filter_value_form_wrapper_id'], $html_id); - - $theme_wrappers = $this->getFormElementProperty($element, '#theme_wrappers', []); - $theme_wrappers['container'] = ['#attributes' => ['id' => $html_id]]; - $element['#theme_wrappers'] = $theme_wrappers; - - $process = $this->getFormElementProperty($element, '#process', []); - // We wanna run as the 1st process since we might change the type of the - // element and thus the ongoing processes may become obsolete. - array_unshift($process, [self::class, 'processValueForm']); - $element['#process'] = $process; - - // We will need the definition of operators in the process callback and at - // that moment we will in static context without access to methods of this - // object. So we thoughtfully attach definition of all operators to the form - // element itself. - $element['#webform_views_filter']['operators'] = $this->operators(); - - $form['value'] = $element; - } - - /** - * {@inheritdoc} - */ - public function acceptExposedInput($input) { - if (parent::acceptExposedInput($input)) { - if (empty($this->options['exposed'])) { - return TRUE; - } - - if (!empty($this->options['expose']['identifier'])) { - $value = $input[$this->options['expose']['identifier']]; - - return (bool) $value; - } - } - return FALSE; - } +class WebformSubmissionCompositeFieldFilter extends WebformSubmissionFieldFilter { /** * {@inheritdoc} @@ -185,86 +23,16 @@ class WebformSubmissionCompositeFieldFilter extends StringFilter { function operators() { $operators = parent::operators(); - // We additionally mark each operator as whether it should use element form - // or just a text field. For example, when you filter by email, generally - // you want to use #type => 'email', i.e. the element form, but when you do - // "contains" or "regex" or similar operator, you want to have just a text - // field. - $operator_map = [ - '=' => self::ELEMENT_TYPE, - '!=' => self::ELEMENT_TYPE, - 'contains' => 'textfield', - 'word' => 'textfield', - 'allwords' => 'textfield', - 'starts' => 'textfield', - 'not_starts' => 'textfield', - 'ends' => 'textfield', - 'not_ends' => 'textfield', - 'not' => 'textfield', - 'shorterthan' => 'number', - 'longerthan' => 'number', - 'regular_expression' => 'textfield', - ]; - + // Replace all occurrences of "use the element type itself" with + // "textfield". At the moment we do not support more complex composites than + // plain text field. foreach ($operators as $k => $v) { - if (isset($operator_map[$k])) { - $operators[$k]['webform_views_element_type'] = $operator_map[$k]; + if ($operators[$k]['webform_views_element_type'] == WebformSubmissionFieldFilter::ELEMENT_TYPE) { + $operators[$k]['webform_views_element_type'] = 'textfield'; } } return $operators; } - /** - * Form process for ::operatorForm() method. - */ - public static function processOperatorForm($element, FormStateInterface $form_state, $form) { - $element['#ajax']['wrapper'] = $form_state->get(['webform_views', 'filter_value_form_wrapper_id']); - return $element; - } - - /** - * Form process for ::valueForm() method. - */ - public static function processValueForm($element, FormStateInterface $form_state, $form) { - // Store the location of value form within the whole $form so ajax callback - // has better time finding it. - $form_state->set(['webform_views', 'filter_value_form_array_parents'], $element['#array_parents']); - - return $element; - } - - /** - * Ajax callback for operator form element. - */ - public static function ajaxValueForm($form, FormStateInterface $form_state) { - $value_form = NestedArray::getValue($form, $form_state->get(['webform_views', 'filter_value_form_array_parents'])); - - // Views sets its #prefix and #suffix for CSS purposes. - unset($value_form['#prefix'], $value_form['#suffix']); - - return $value_form; - } - - /** - * Extract a property of a form element. - * - * @param array $element - * Form element whose property to extract - * @param string $property - * Property to extract - * @param mixed $default - * Default value to use when the property is not defined in the provided - * element - * - * @return mixed - * The property value extracted from form element - */ - protected function getFormElementProperty($element, $property, $default) { - if (isset($element[$property])) { - return $element[$property]; - } - - return $this->elementInfoManager->getInfoProperty($element['#type'], $property, $default); - } } diff --git a/src/Plugin/views/sort/WebformSubmissionCompositeFieldSort.php b/src/Plugin/views/sort/WebformSubmissionCompositeFieldSort.php deleted file mode 100644 index 8ee2a4f..0000000 --- a/src/Plugin/views/sort/WebformSubmissionCompositeFieldSort.php +++ /dev/null @@ -1,14 +0,0 @@ -isInput($element) && !$element_plugin->hasMultipleValues($element)) { - $views_data['sort'] = [ - 'id' => 'webform_submission_composite_field_sort', - 'real field' => 'value', - ]; - } + public function getViewsData($element, WebformInterface $webform) { + $data = parent::getViewsData($element, $webform); + + // Additionally enrich the data with sub-properties of this composite + // element. + $table_alias = 'webform_submission_field_' . $webform->id() . '_' . $element['#webform_key']; + $element_title = (isset($element['#title']) && $element['#title']) ? $element['#title'] : $element['#webform_key']; + $element_plugin = $this->webformElementManager->getElementInstance($element); + + if (isset($element['#webform_composite_elements'])) { + $composite_elements = array_keys($element['#webform_composite_elements']); + foreach ($composite_elements as $composite_key) { + $data[$table_alias . '__' . $composite_key]['table']['group'] = $this->t('Webform @webform submission data', [ + '@webform' => $webform->label(), + ]); + + $data[$table_alias . '__' . $composite_key]['table']['join'][$this->entityType->getBaseTable()] = [ + 'table' => 'webform_submission_data', + 'field' => 'sid', + 'left_field' => 'sid', + 'extra' => [ + ['field' => 'name', 'value' => $element['#webform_key']], + ['field' => 'property', 'value' => $composite_key], + ], + ]; - if ($element_plugin->isInput($element) && !$element_plugin->hasMultipleValues($element)) { - $views_data['filter'] = [ - 'id' => 'webform_submission_composite_field_filter', - 'real field' => 'value', - ]; + $title = $element_title . ': ' . $element['#webform_composite_elements'][$composite_key]['#title']; + $data[$table_alias . '__' . $composite_key]['webform_submission_value'] = [ + 'title' => Html::escape($title), + 'help' => $this->t('Value of the field %field property in webform %webform submission.', [ + '%field' => $element_title, + '%webform' => $webform->label(), + ]), + ]; + + foreach ($this->getCompositeViewsData($element_plugin, $element, $composite_key) as $k => $v) { + $v += [ + 'webform_id' => $webform->id(), + 'webform_submission_field' => $element['#webform_key'], + 'webform_submission_property' => $composite_key, + ]; + $data[$table_alias . '__' . $composite_key]['webform_submission_value'][$k] = $v; + } + } } + return $data; + } + + /** + * Generate views data for a given composite key of a webform element. + * + * @param \Drupal\webform\Plugin\WebformElementInterface $element_plugin + * Webform element plugin whose views data definition is requested + * @param array $element + * Webform element whose views data definition is requested + * @param string $composite_key + * Composite key for which views data to generate + * + * @return array + * Views data definition array that corresponds to the given webform + * composite key + */ + protected function getCompositeViewsData(WebformElementInterface $element_plugin, array $element, $composite_key) { + $views_data = []; + + $views_data['field'] = [ + 'id' => 'webform_submission_composite_field', + 'real field' => $this->entityType->getKey('id'), + 'click sortable' => TRUE, + 'multiple' => $element_plugin->hasMultipleValues($element), + ]; + + $views_data['sort'] = [ + 'id' => 'webform_submission_field_sort', + 'real field' => 'value', + ]; + + $views_data['filter'] = [ + 'id' => 'webform_submission_composite_field_filter', + 'real field' => 'value', + ]; + return $views_data; } diff --git a/src/WebformElementViews/WebformElementViewsAbstract.php b/src/WebformElementViews/WebformElementViewsAbstract.php index 7fdd949..ea39996 100644 --- a/src/WebformElementViews/WebformElementViewsAbstract.php +++ b/src/WebformElementViews/WebformElementViewsAbstract.php @@ -90,43 +90,6 @@ abstract class WebformElementViewsAbstract implements WebformElementViewsInterfa $data[$table_alias]['webform_submission_value'][$k] = $v; } - if (isset($element['#webform_composite_elements'])) { - $composite_elements = array_keys($element['#webform_composite_elements']); - foreach ($composite_elements as $composite_key) { - $data[$table_alias . '__' . $composite_key]['table']['group'] = $this->t('Webform @webform submission data', [ - '@webform' => $webform->label(), - ]); - - $data[$table_alias . '__' . $composite_key]['table']['join'][$this->entityType->getBaseTable()] = [ - 'table' => 'webform_submission_data', - 'field' => 'sid', - 'left_field' => 'sid', - 'extra' => [ - ['field' => 'name', 'value' => $element['#webform_key']], - ['field' => 'property', 'value' => $composite_key], - ], - ]; - - $data[$table_alias . '__' . $composite_key]['webform_submission_value'] = [ - 'title' => Html::escape("$element_title: $composite_key"), - 'help' => $this->t('Value of the field %field property in webform %webform submission.', [ - '%field' => $element_title, - '%webform' => $webform->label(), - ]), - ]; - - foreach ($this->getElementViewsData($element_plugin, $element) as $k => $v) { - $v += [ - 'webform_id' => $webform->id(), - 'webform_submission_field' => $element['#webform_key'], - 'webform_submission_property' => $composite_key - ]; - $data[$table_alias . '__' . $composite_key]['webform_submission_value'][$k] = $v; - $data[$table_alias . '__' . $composite_key]['webform_submission_value']['field']['id'] = 'webform_submission_composite_field'; - } - } - } - return $data; } diff --git a/webform_views.module b/webform_views.module index 6e7e4b4..89ffb04 100644 --- a/webform_views.module +++ b/webform_views.module @@ -55,6 +55,9 @@ function webform_views_webform_element_info_alter(array &$elements) { if (is_subclass_of($v['class'], \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase::class, TRUE)) { $views_handler = \Drupal\webform_views\WebformElementViews\WebformManagedFileViews::class; } + elseif (is_subclass_of($v['class'], \Drupal\webform\Plugin\WebformElement\WebformCompositeBase::class)) { + $views_handler = \Drupal\webform_views\WebformElementViews\WebformCompositeViews::class; + } switch ($k) { case 'select': @@ -75,9 +78,6 @@ function webform_views_webform_element_info_alter(array &$elements) { default: if (!$views_handler) { - if (get_parent_class($elements[$k]['class']) == "Drupal\\webform\\Plugin\\WebformElement\\WebformCompositeBase") { - $views_handler = \Drupal\webform_views\WebformElementViews\WebformCompositeViews::class; - } $views_handler = \Drupal\webform_views\WebformElementViews\WebformDefaultViews::class; } break;