diff --git a/css/scenebuilder_format_strawberryfield.css b/css/scenebuilder_format_strawberryfield.css new file mode 100644 index 0000000..9fd0ccd --- /dev/null +++ b/css/scenebuilder_format_strawberryfield.css @@ -0,0 +1,40 @@ +.hotspot_marker_wrapper { + background-color: deepskyblue; + border-radius: 50%; + position: absolute; + top: 0px; + left: 0px; + display: none; + opacity: 0; + -moz-transition: opacity .3s ease-in-out; + -webkit-transition: opacity .3s ease-in-out; + -o-transition: opacity .3s ease-in-out; + -ms-transition: opacity .3s ease-in-out; + transition: opacity .3s ease-in-out; + z-index: 1 +} +.hotspot_editor_marker { + border-radius: 50%; + background-color: deepskyblue; + width: 150px; + height: 150px; + position: absolute; + left:-75px; + top:-75px; + opacity: 0; + animation: scaleIn 4s infinite cubic-bezier(.36, .11, .89, .32); +} +@keyframes scaleIn { + from { + transform: scale(.5, .5); + opacity: .5; + } + to { + transform: scale(2.5, 2.5); + opacity: 0; + } +} + + + + diff --git a/js/hidenodeaction-webform_strawberryfield.js b/js/hidenodeaction-webform_strawberryfield.js index 95ef0aa..dd5b9e1 100644 --- a/js/hidenodeaction-webform_strawberryfield.js +++ b/js/hidenodeaction-webform_strawberryfield.js @@ -21,9 +21,9 @@ if ($('.path-node fieldset[data-strawberryfield-selector="strawberry-webform-widget"]').length) { if ($('.webform-confirmation',context).length) { // Exclude webform own edit-actions containter - $('.path-node div[data-drupal-selector="edit-actions"]').not('.webform-actions').show(); + $('.path-node .node-form div[data-drupal-selector="edit-actions"]').not('.webform-actions').show(); } else if ($('div.field--widget-strawberryfield-webform-inline-widget').length) { - $('.path-node div[data-drupal-selector="edit-actions"]').not('.webform-actions').hide(); + $('.path-node .node-form div[data-drupal-selector="edit-actions"]').not('.webform-actions').hide(); } @@ -31,14 +31,14 @@ if ($moderationstate.length) { var $select = $moderationstate.on('change', function () { - $('.path-node div[data-drupal-selector="edit-actions"]').not('.webform-actions').show(); + $('.path-node .node-form div[data-drupal-selector="edit-actions"]').not('.webform-actions').show(); }); } var $nodetitle = $('input[data-drupal-selector="edit-title-0-value"]', context).once('show-hide-actions'); if ($nodetitle.length) { var $select = $nodetitle.on('input', function () { - $('.path-node div[data-drupal-selector="edit-actions"]').not('.webform-actions').show(); + $('.path-node .node-form div[data-drupal-selector="edit-actions"]').not('.webform-actions').show(); }); } diff --git a/js/scenebuilder-pannellum_strawberryfield.js b/js/scenebuilder-pannellum_strawberryfield.js new file mode 100644 index 0000000..e8591a9 --- /dev/null +++ b/js/scenebuilder-pannellum_strawberryfield.js @@ -0,0 +1,111 @@ +(function ($, Drupal, drupalSettings, pannellum) { + + + + function marker(event, $marker, $container) { + var pos = mousePosition(event,$container); + $marker.css("left", pos.x + 'px'); + $marker.css("top", pos.y + 'px'); + $marker.css("opacity", '1.0'); + $marker.css("display", 'block'); + $marker.fadeIn('slow'); + $marker.fadeOut('slow'); + } + + + function mousePosition(event,$container) { + var bounds = $container.getBoundingClientRect(); + var pos = {}; + // pageX / pageY needed for iOS + pos.x = (event.clientX || event.pageX) - bounds.left; + pos.y = (event.clientY || event.pageY) - bounds.top; + return pos; + } + + Drupal.AjaxCommands.prototype.webform_strawberryfield_pannellum_editor_addHotSpot = function(ajax, response, status) { + console.log('adding hotspot'); + // we need to find the first '.strawberry-panorama-item' id that is child of data-drupal-selector="edit-panorama-tour-hotspots" + console.log(response.selector); + $targetScene = $(response.selector).find('.strawberry-panorama-item').attr("id"); + console.log($targetScene); + if (response.hasOwnProperty('hotspot')) { + $scene = Drupal.FormatStrawberryfieldPanoramas.panoramas.get($targetScene); + // add click handlers for new Hotspots if they have an URL. + // Empty URLs are handled by Drupal.FormatStrawberryfieldhotspotPopUp() + console.log(response); + if (response.hotspot.hasOwnProperty('URL')) { + response.hotspot.clickHandlerFunc = Drupal.FormatStrawberryfieldhotspotPopUp; + response.hotspot.clickHandlerArgs = response.hotspot.URL; + } + + $scene.panorama.addHotSpot(response.hotspot); + } + }; + + + Drupal.behaviors.webform_strawberryfield_pannellum_editor = { + attach: function(context, settings) { + $('.strawberry-panorama-item[data-iiif-image]').once('attache_pne') + .each(function (index, value) { + var hotspots = []; + // Get the node uuid for this element + var element_id = $(this).attr("id"); + console.log('Checking for loaded Panoramatour builder Hotspots'); + console.log(drupalSettings.webform_strawberryfield.WebformPanoramaTour); + // Feed with existing Hotspots comming from the Webform Element data. + for (var parentselector in drupalSettings.webform_strawberryfield.WebformPanoramaTour) { + if (Object.prototype.hasOwnProperty.call(drupalSettings.webform_strawberryfield.WebformPanoramaTour, parentselector)) { + $targetScene = $("[data-webform_strawberryfield-selector='" + parentselector + "']").find('.strawberry-panorama-item').attr("id"); + console.log(parentselector); + console.log($targetScene); + $scene = Drupal.FormatStrawberryfieldPanoramas.panoramas.get($targetScene); + if ((typeof $scene !== 'undefined')) { + if (drupalSettings.webform_strawberryfield.WebformPanoramaTour[parentselector]!== null + && typeof drupalSettings.webform_strawberryfield.WebformPanoramaTour[parentselector][Symbol.iterator] === 'function' + ) { + drupalSettings.webform_strawberryfield.WebformPanoramaTour[parentselector].forEach(function (hotspotdata, key) { + console.log(hotspotdata); + if (hotspotdata.hasOwnProperty('URL')) { + hotspotdata.clickHandlerFunc = Drupal.FormatStrawberryfieldhotspotPopUp; + hotspotdata.clickHandlerArgs = hotspotdata.URL; + } + $scene.panorama.addHotSpot(hotspotdata); + }); + } + } + } + } + + // Check if we got some data passed via Drupal settings. + if (typeof(drupalSettings.format_strawberryfield.pannellum[element_id]) != 'undefined') { + console.log('initializing Panellum Panorama Builder') + console.log(Drupal.FormatStrawberryfieldPanoramas.panoramas); + Drupal.FormatStrawberryfieldPanoramas.panoramas.forEach(function(item, key) { + + var element_id_marker = element_id + '_marker'; + var $newmarker = $( "
"); + + $("#" +element_id+ " .pnlm-ui").append( $newmarker ); + + item.panorama.on('mousedown', function clicker(e) { + + $hotspot_cors = item.panorama.mouseEventToCoords(e); + console.log(item.panorama.getHfov()); + var $jquerycontainer = $(item.panorama.getContainer()); + + $button_container = $jquerycontainer.closest("[data-drupal-selector='edit-panorama-tour-hotspots-temp']"); + + $button_container.find("[ data-drupal-hotspot-property='yaw']").val($hotspot_cors[1]); + $button_container.find("[ data-drupal-hotspot-property='pitch']").val($hotspot_cors[0]); + $button_container.find("[ data-drupal-hotspot-property='hfov']").val(item.panorama.getHfov()); + + marker(e,$newmarker,item.panorama.getContainer()); + + } + ); + }); + } + + })}} + +})(jQuery, Drupal, drupalSettings, window.pannellum); diff --git a/src/Ajax/AddHotSpotCommand.php b/src/Ajax/AddHotSpotCommand.php new file mode 100644 index 0000000..2d7f105 --- /dev/null +++ b/src/Ajax/AddHotSpotCommand.php @@ -0,0 +1,63 @@ +selector = $selector; + $this->hotspot = $hotspot; + $this->sceneid = $sceneid; + + } + + /** + * Implements Drupal\Core\Ajax\CommandInterface:render(). + */ + public function render() { + + return [ + 'command' => 'webform_strawberryfield_pannellum_editor_addHotSpot', + 'selector' => $this->selector, + 'hotspot' => $this->hotspot, + 'sceneid' => $this->sceneid, + ]; + } + +} + diff --git a/src/Element/WebformNominatim.php b/src/Element/WebformNominatim.php index 37a2902..34769c9 100644 --- a/src/Element/WebformNominatim.php +++ b/src/Element/WebformNominatim.php @@ -160,7 +160,7 @@ public static function processWebformComposite( 'display_name' => t('Display Name'), 'category' => t('Category'), ]; - + $table_options = []; foreach($nominatim_features as $key => $feature) { $table_options[$key+1] = [ @@ -411,8 +411,8 @@ public static function nominatimTableSubmit( // Lat and Long are in http://en.wikipedia.org/wiki/en:WGS-84 $values = [ 'value' => $nominatim_features[$selected_option]->label, - 'lat' => $nominatim_features[$selected_option]->value->geometry->coordinates[0], - 'lng' => $nominatim_features[$selected_option]->value->geometry->coordinates[1], + 'lat' => $nominatim_features[$selected_option]->value->geometry->coordinates[1], + 'lng' => $nominatim_features[$selected_option]->value->geometry->coordinates[0], 'category' => $nominatim_features[$selected_option]->value->properties->category, 'display_name' => $nominatim_features[$selected_option]->label, 'osm_id' => $nominatim_features[$selected_option]->value->properties->osm_id, diff --git a/src/Element/WebformPanoramaTour.php b/src/Element/WebformPanoramaTour.php new file mode 100644 index 0000000..a2c0418 --- /dev/null +++ b/src/Element/WebformPanoramaTour.php @@ -0,0 +1,1185 @@ + TRUE, + '#access' => TRUE, + '#process' => [ + [$class, 'processWebformComposite'], + [$class, 'processAjaxForm'], + ], + '#pre_render' => [ + [$class, 'preRenderWebformPanoramaTourElement'], + ], + '#title_display' => 'invisible', + '#required' => FALSE, + '#flexbox' => TRUE, + '#theme' => 'webform_metadata_panoramatour', + ]; + + return $info; + } + + + /** + * Render API callback: Hides display of the upload or remove controls. + * + * Upload controls are hidden when a file is already uploaded. Remove controls + * are hidden when there is no file attached. Controls are hidden here instead + * of in \Drupal\file\Element\ManagedFile::processManagedFile(), because + * #access for these buttons depends on the managed_file element's #value. See + * the documentation of \Drupal\Core\Form\FormBuilderInterface::doBuildForm() + * for more detailed information about the relationship between #process, + * #value, and #access. + * + * Because #access is set here, it affects display only and does not prevent + * JavaScript or other untrusted code from submitting the form as though + * access were enabled. The form processing functions for these elements + * should not assume that the buttons can't be "clicked" just because they are + * not displayed. + * + * @see \Drupal\file\Element\ManagedFile::processManagedFile() + * @see \Drupal\Core\Form\FormBuilderInterface::doBuildForm() + */ + public static function preRenderWebformPanoramaTourElement($element) { + return $element; + } + + /** + * {@inheritdoc} + */ + public static function getCompositeElements(array $element) { + + $elements = []; + $elements['allscenes'] = [ + '#type' => 'hidden', + ]; + return $elements; + } + + /** + * {@inheritdoc} + */ + public static function processWebformComposite( + &$element, + FormStateInterface $form_state, + &$complete_form + ) { + + $element = parent::processWebformComposite($element, $form_state, $complete_form); + + // Just in case someone wants to trick us + // This is already disabled on the Config Form for the Element + unset($element['#multiple']); + $element_name = $element['#name']; + + $all_scenes = []; + + $all_scenes_key = $element_name . '-allscenes'; + $all_scenes_from_form_state = !empty($form_state->getValue([$element['#name'],'allscenes'])) ? json_decode($form_state->getValue([$element['#name'],'allscenes']),TRUE) : []; + + $all_scenes_from_form_value = !empty($form_state->get($all_scenes_key)) ? $form_state->get($all_scenes_key) : []; + + + // Basically. First try to get it from the defaults set by the ::valuecallback(). + // This will populate Form state value. + // But once that happens, the #value elements does not survive. + // So we store it in a form state variable. And keep setting it. + $all_scenes = !empty($all_scenes_from_form_value) ? $all_scenes_from_form_value : $all_scenes_from_form_state; + + if (!empty($all_scenes)) { + $form_state->set($all_scenes_key,$all_scenes ); + } + + // Double set it. One for the input, one for the form state. + // Reason is we have a complex ::valuecallback() here + // and when used in the widget a form inside a form + // This reasonably deals with both needs. + $element['allscenes']['#default_value'] = json_encode($all_scenes,true); + $form_state->setValue([$element['#name'],'allscenes'],json_encode($all_scenes,TRUE)); + + $currentscene = $form_state->getValue([$element['#name'], 'currentscene']); + + $sceneid = NULL; + + if (!$currentscene && !empty($all_scenes)) { + $scene = reset($all_scenes); + $sceneid = $scene['scene']; + + } else { + $sceneid = $currentscene; + $form_state->setValue([$element_name, 'scene'],$sceneid); + } + $hotspot_list = []; + + + foreach ($all_scenes as $scene) { + // Fetching from full array + if (isset($scene['scene']) && $scene['scene'] == $sceneid) { + $hotspot_list = $scene['hotspots']; + } + } + + // We need this button to validate. important + // NEVER add '#limit_validation_errors' => [], + $element['scene'] = [ + '#type' => 'entity_autocomplete', + '#title' => t('Select a Scene'), + '#target_type' => 'node', + '#selection_handler' => 'solr_views', + '#selection_settings' => [ + 'view' => [ + 'view_name' => 'ado_selection_by_type', + 'display_name' => 'entity_reference_solr_2', + 'arguments' => ['Panorama'] + ], + ], + '#limit_validation_errors' => [$element['#parents']], + ]; + + $element['hotspots'] = [ + '#type' => 'value' + ]; + + $element['select_button'] = [ + '#title' => 'Select Scene', + '#type' => 'submit', + '#value' => t('Select Scene'), + '#name' => $element['#name'] . '_select_button', + '#submit' => [[get_called_class(), 'selectSceneSubmit']], + '#ajax' => [ + 'callback' => [get_called_class(), 'selectSceneCallBack'], + ], + '#button_type' => 'default', + '#visible' => 'true', + '#limit_validation_errors' => [$element['#parents']], + ]; + + $element['hotspots_temp'] = [ + '#weight' => 4, + '#type' => 'fieldset', + '#attributes' => [ + 'data-drupal-selector' => $element['#name'] . '-hotspots', + 'data-webform_strawberryfield-selector' => $element['#name'] . '-hotspots', + ], + '#attached' => [ + 'library' => [ + 'format_strawberryfield/pannellum', + 'webform_strawberryfield/scenebuilder_pannellum_strawberry', + 'core/drupal.dialog.ajax', + ], + 'drupalSettings' => [ + 'webform_strawberryfield' => [ + 'WebformPanoramaTour' => [ + $element['#name'] . '-hotspots' => + $hotspot_list + ], + ] + ] + ], + ]; + + // If we have a currently selected scene + if ($sceneid) { + $nodeid = $sceneid; + + if ($form_state->getValue([$element['#name'], 'hotspots_temp', 'ado'])) { + + $othernodeid = EntityAutocomplete::extractEntityIdFromAutocompleteInput($nodeid); + // We have to do the same when saving the actual Hotspot. + + $element['hotspots_temp']['entities'] = [ + '#type' => 'value', + '#default_value' => $othernodeid, + ]; + } + $vb = \Drupal::entityTypeManager()->getViewBuilder( + 'node' + ); // Drupal\node\NodeViewBuilder + + //@TODO we need viewmode to be configurable! + //@TODO We could also generate a view mode on the fly. + //$viewmode = 'digital_object_with_pannellum_panorama_'; + + $node = \Drupal::entityTypeManager()->getStorage('node')->load( + $nodeid + ); + + $all_scenes_nodeids = []; + if ($node) { + // Will contain all currently loaded scenes as an Select option array. + $options = []; + // If we have multiple Scenes,deal with it. + if (!empty($all_scenes) && is_array($all_scenes)) { + foreach($all_scenes as $key => $scene) { + if (isset($scene['scene'])) { + $all_scenes_nodeids[$key] = $scene['scene']; + } + } + $all_scene_nodes = \Drupal::entityTypeManager()->getStorage('node')->loadMultiple($all_scenes_nodeids); + + foreach ($all_scene_nodes as $entity) { + $options[$entity->id()] = $entity->label(); + } + + $element['currentscene']['#weight'] = 2; + $element['currentscene'] = [ + '#title' => t('Editing Scene'), + '#type' => 'select', + '#options' => $options, + '#default_value' => $node->id(), + '#ajax' => [ + 'callback' => [get_called_class(), 'changeSceneCallBack'], + 'event' => 'change', + ], + '#submit' => [[get_called_class(), 'changeSceneSubmit']], + ]; + /*// Hide the select button, at least visually + // because it defines the main submit + $element['select_button'] = [ + '#attributes' => ['class' => ['js-hide']] + ];*/ + + + } + + $nodeview = $vb->view($node); + $element['hotspots_temp']['node'] = $nodeview; + + $element['hotspots_temp']['node']['#weight'] = -10; + $element['hotspots_temp']['node']['#prefix'] = '
'; + $element['hotspots_temp']['node']['#attributes']['class'] = ['col-8']; + $element['hotspots_temp']['added_hotspots'] = [ + '#type' => 'details', + '#attributes' => [ + 'data-drupal-loaded-node-hotspot-table' => $nodeid, + 'class' => ['row'], + ], + '#weight' => 11, + ]; + + $element['hotspots_temp']['label'] = [ + '#prefix' => '
', + '#title' => t('The label to display on mouse over'), + '#type' => 'textfield', + '#size' => '12', + '#attributes' => [ + 'data-drupal-loaded-node' => $nodeid, + 'data-drupal-hotspot-property' => 'label', + ], + ]; + + $element['hotspots_temp']['yaw'] = [ + '#title' => t('Hotspot Yaw'), + '#type' => 'textfield', + '#size' => '6', + '#attributes' => [ + 'data-drupal-loaded-node' => $nodeid, + 'data-drupal-hotspot-property' => 'yaw', + ], + ]; + $element['hotspots_temp']['hfov'] = [ + '#title' => t('hfov'), + '#type' => 'textfield', + '#size' => '6', + '#attributes' => [ + 'data-drupal-loaded-node' => $nodeid, + 'data-drupal-hotspot-property' => 'hfov', + ], + ]; + $element['hotspots_temp']['pitch'] = [ + '#title' => t('Hotspot Pitch'), + '#type' => 'textfield', + '#size' => '6', + '#attributes' => [ + 'data-drupal-loaded-node' => $nodeid, + 'data-drupal-hotspot-property' => 'pitch', + ], + ]; + + $element['hotspots_temp']['type'] = [ + '#title' => t('Hotspot Type'), + '#type' => 'select', + '#options' => [ + 'text' => 'Text', + 'url' => 'An External URL', + 'ado' => 'Another Digital Object', + 'scene' => 'Another Panorama Scene', + ], + '#attributes' => [ + 'data-drupal-loaded-node' => $nodeid, + 'data-drupal-hotspot-property' => 'type', + ], + ]; + $element['hotspots_temp']['url'] = [ + '#title' => t('URL to open on Hotspot Click'), + '#type' => 'url', + '#size' => '12', + '#attributes' => [ + 'data-drupal-loaded-node' => $nodeid, + 'data-drupal-hotspot-property' => 'url', + ], + '#states' => [ + 'visible' => [ + ':input[name="'.$element['#name'].'[hotspots_temp][type]"]' => array('value' => 'url'), + ], + ] + ]; + $optionscenes = is_array($options) ? $options : []; + // Remove from linkable scenes the current loaded one + unset($optionscenes[$nodeid]); + + $element['hotspots_temp']['adoscene'] = [ + '#title' => t('Linkable Scenes'), + '#type' => 'select', + "#empty_option" => t('- Select a loaded Scene -'), + '#options' => $optionscenes, + '#states' => [ + 'visible' => [ + ':input[name="'.$element['#name'].'[hotspots_temp][type]"]' => array('value' => 'scene'), + ], + ], + '#attributes' => [ + 'data-drupal-loaded-node' => $nodeid, + 'data-drupal-hotspot-property' => 'type', + ], + ]; + + //@TODO expose arguments to the Webform element config UI. + $element['hotspots_temp']['ado'] = [ + '#type' => 'entity_autocomplete', + '#title' => t('Select a Digital Object that will open on Hotspot Click'), + '#target_type' => 'node', + '#selection_handler' => 'solr_views', + '#selection_settings' => [ + 'view' => [ + 'view_name' => 'ado_selection_by_type', + 'display_name' => 'entity_reference_solr_2', + 'arguments' => ['Image'], + ], + ], + '#attributes' => [ + 'data-drupal-loaded-node' => $nodeid, + 'data-drupal-hotspot-property' => 'ado', + ], + '#states' => [ + 'visible' => [ + ':input[name="'.$element['#name'].'[hotspots_temp][type]"]' => array('value' => 'ado'), + ], + ] + ]; + + + // To make sure menu variables are passed + // we limit validation errors to those elements + + $element['hotspots_temp']['add_hotspot'] = [ + '#prefix' => '
', + '#type' => 'submit', + '#value' => t('Add Hotspot'), + '#name' => $element['#name'] . '_addhotspot_button', + '#submit' => [[get_called_class(), 'addHotspotSubmit']], + '#ajax' => [ + 'callback' => [get_called_class(), 'addHotSpotCallBack'], + ], + '#button_type' => 'default', + '#visible' => 'true', + '#limit_validation_errors' => [$element['#parents']], + ]; + + + $element['hotspots_temp']['set_sceneorientation'] = [ + '#type' => 'submit', + '#value' => t('Set Initial Scene Orientation'), + '#name' => $element['#name'] . '_setsceneorientation_button', + '#submit' => [[get_called_class(), 'setSceneOrientation']], + '#ajax' => [ + 'callback' => [get_called_class(), 'setSceneOrientationCallBack'], + ], + '#button_type' => 'default', + '#visible' => 'true', + '#limit_validation_errors' => [$element['#parents']], + ]; + + $element['hotspots_temp']['delete_scene'] = [ + '#type' => 'submit', + '#value' => t('Delete this Scene'), + '#name' => $element['#name'] . '_deletescene_button', + '#submit' => [[get_called_class(), 'deleteScene']], + '#ajax' => [ + 'callback' => [get_called_class(), 'deleteSceneCallBack'], + ], + '#button_type' => 'default', + '#visible' => 'true', + '#limit_validation_errors' => FALSE, + '#attributes' => [ + 'class' => ['btn-warning'] + ], + '#suffix' => '
' // Closes the btn group, row and the col + ]; + + + $element['hotspots_temp']['added_hotspots'] = [ + '#type' => 'details', + '#attributes' => [ + 'data-drupal-loaded-node-hotspot-table' => $nodeid + ], + '#weight' => 11, + ]; + + if (!empty($hotspot_list)) { + + $table_header = [ + 'coordinates' => t('Hotspot Coordinates'), + 'type' => t('Type'), + 'label' => t('Label'), + 'operations' => t('Operations'), + ]; + $table_options = []; + foreach ($hotspot_list as $key => $hotspot) { + if (is_array($hotspot)) { + $hotspot = (object) $hotspot; + } + // Key will be in element['#name'].'_'.count($hotspot_list)+1; + $table_options[$key] = [ + 'coordinates' => $hotspot->yaw . "," . $hotspot->pitch, + 'type' => $hotspot->type, + 'label' => isset($hotspot->text) ? $hotspot->text : t('no name'), + 'operations' => [ + 'data' => [ + '#type' => 'submit', + '#value' => t('Delete'), + ], + ] + ]; + } + + $element['hotspots_temp']['added_hotspots'] = [ + '#prefix'=> '
', + '#suffix'=> '
', + '#title' => t('Hotspots in this scene'), + '#type' => 'table', + '#name' => $element['#name'] . '_added_hotspots', + '#header' => $table_header, + '#rows' => $table_options, + '#empty' => t('No Hotspots yet for this Scene'), + '#weight' => 11, + '#attributes' => [ + 'data-drupal-loaded-node-hotspot-table' => $nodeid + ], + ]; + } + } + } + // TODO. + $element['#element_validate'][] = [get_called_class(), 'validatePanoramaTourElement']; + + return $element; + } + + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */ + $element_manager = \Drupal::service('plugin.manager.webform.element'); + $composite_elements = static::getCompositeElements($element); + $composite_elements = WebformElementHelper::getFlattened($composite_elements); + + // Get default value for inputs. + $default_value = []; + foreach ($composite_elements as $composite_key => $composite_element) { + $element_plugin = $element_manager->getElementInstance($composite_element); + if ($element_plugin->isInput($composite_element)) { + $default_value[$composite_key] = ''; + } + } + if ($input === FALSE) { + if (empty($element['#default_value']) || !is_array($element['#default_value'])) { + $to_return = $element['#default_value'] = []; + } + else { + $to_return = $element['#default_value']; + $to_return['allscenes'] = array_filter($to_return, function($k) { + return is_integer($k); + }, ARRAY_FILTER_USE_KEY); + if (is_array($to_return['allscenes'])) { + $to_return['allscenes'] = json_encode($to_return['allscenes']); + } + } + + return $to_return + $default_value; + } + + return (is_array($input)) ? $input + $default_value : $default_value; + + } + + + + + + + /** + * Main Submit Handler for the Select Scene Submit call. + * + * @param array $form + * @param \Drupal\Core\Form\FormStateInterface $form_state + */ + public static function selectSceneSubmit( + array &$form, + FormStateInterface $form_state + ) { + $button = $form_state->getTriggeringElement(); + + $input = $form_state->getUserInput(); + // Hack. No explanation. + $main_element_parents = array_slice($button['#array_parents'], 0, -1); + + $top_element = NestedArray::getValue($form, $main_element_parents); + + if (isset($input['_triggering_element_name']) && + $input['_triggering_element_name'] == $top_element['#name'].'_addhotspot_button' + && empty($form_state->get('hotspot_custom_errors')) + ) { + static::addHotspotSubmit( + $form, + $form_state + ); + } + elseif (isset($input['_triggering_element_name']) && + $input['_triggering_element_name'] == $top_element['#name'].'_setsceneorientation_button' + && empty($form_state->get('hotspot_custom_errors')) + ) { + static::setSceneOrientation( + $form, + $form_state + ); + } + elseif (isset($input['_triggering_element_name']) && + $input['_triggering_element_name'] == $top_element['#name'].'_deletescene_button' + && empty($form_state->get('hotspot_custom_errors')) + ) { + static::deleteScene( + $form, + $form_state + ); + } + else { + // Try first with the new scene button, if no value, go back to scene button. + $current_scene = $form_state->getValue([$top_element['#name'], 'scene']); + $current_scene = $current_scene ? $current_scene : $form_state->getValue( + [$top_element['#name'], 'currentscene'] + ); + $all_scenes_key = $top_element['#name'] . '-allscenes'; + $alreadythere = FALSE; + if ($current_scene) { + $all_scenes = $form_state->get($all_scenes_key); + if (is_array($all_scenes)) { + foreach ($all_scenes as $scene) { + if (isset($scene['scene']) && $scene['scene'] == $current_scene) { + $alreadythere = TRUE; + break; + } + } + } + if (!$alreadythere) { + $all_scenes[] = [ + 'scene' => $current_scene, + 'hotspots' => [], + ]; + + $form_state->setValue([$top_element['#name'], 'allscenes'],json_encode($all_scenes)); + $form_state->set($all_scenes_key, $all_scenes); + $form_state->setValue([$top_element['#name'], 'currentscene'], $current_scene); + } + } + } + + // Rebuild the form. + $form_state->setRebuild(TRUE); + } + + /** + * @param array $form + * @param \Drupal\Core\Form\FormStateInterface $form_state + * + * @return \Drupal\Core\Ajax\AjaxResponse + */ + public static function selectSceneCallBack( + array $form, + FormStateInterface $form_state + ) { + $button = $form_state->getTriggeringElement(); + $element = NestedArray::getValue( + $form, + array_slice($button['#array_parents'], 0, -2) + ); + + $response = new AjaxResponse(); + $data_selector = $element['#attributes']['data-drupal-selector']; + $response->addCommand( + new ReplaceCommand( + '[data-drupal-selector="' . $data_selector . '"]', + $element + ) + ); + return $response; + } + + /** + * Submit Handler for the Select Scene Submit call. + * + * @param array $form + * @param \Drupal\Core\Form\FormStateInterface $form_state + */ + public static function changeSceneSubmit( + array &$form, + FormStateInterface $form_state + ) { + // Rebuild the form. + $form_state->setRebuild(TRUE); + } + + /** + * @param array $form + * @param \Drupal\Core\Form\FormStateInterface $form_state + * + * @return \Drupal\Core\Ajax\AjaxResponse + */ + public static function changeSceneCallBack( + array $form, + FormStateInterface $form_state + ) { + $button = $form_state->getTriggeringElement(); + $element = NestedArray::getValue( + $form, + array_slice($button['#array_parents'], 0, -1) + ); + $response = new AjaxResponse(); + $element_name = $element['#name']; + $data_selector = $element['hotspots_temp']['#attributes']['data-webform_strawberryfield-selector']; + + + // Now update the JS settings + if ($form_state->getValue([$element_name, 'scene'])) { + $all_scenes_key = $element_name . '-allscenes'; + $allscenes = $form_state->get($all_scenes_key); + $current_scene = $form_state->getValue([$element_name, 'scene']); + $existing_objects = []; + foreach ($allscenes as $key => &$scene) { + if (isset($scene['scene']) && $scene['scene'] == $current_scene) { + $existing_objects = $scene['hotspots']; + break; + } + } + + $settingsclear = [ + 'webform_strawberryfield' => [ + 'WebformPanoramaTour' => [ + $element_name . '-hotspots' => + NULL + ], + ] + ]; + $settings = [ + 'webform_strawberryfield' => [ + 'WebformPanoramaTour' => [ + $element_name . '-hotspots' => + $existing_objects + ], + ] + ]; + // Why twice? well because merge is deep merge. Gosh JS! + // And merge = FALSE clears even my brain settings... + $response->addCommand(new SettingsCommand($settingsclear, TRUE)); + $response->addCommand(new SettingsCommand($settings, TRUE)); + + } + // And now replace the container + $response->addCommand( + new ReplaceCommand( + '[data-webform_strawberryfield-selector="' . $data_selector . '"]', + $element['hotspots_temp'] + ) + ); + return $response; + } + + /** + * Submit Handler for adding a Hotspot. + * + * @param array $form + * @param \Drupal\Core\Form\FormStateInterface $form_state + */ + public static function addHotspotSubmit( + array &$form, + FormStateInterface $form_state + ) { + + + $button = $form_state->getTriggeringElement(); + $hot_spot_values_parents = array_slice($button['#parents'], 0, -1); + $element_name = $hot_spot_values_parents[0]; + $all_scenes_key = $element_name . '-allscenes'; + + $allscenes = !empty($form_state->getValue([$element_name,'allscenes'])) ? json_decode($form_state->getValue([$element_name,'allscenes']),TRUE) : []; + + if ($form_state->getValue([$element_name, 'currentscene']) + && $allscenes) { + $current_scene = $form_state->getValue([$element_name, 'currentscene']); + $scene_key = 0; + $existing_objects = []; + foreach ($allscenes as $key => &$scene) { + if (isset($scene['scene']) && $scene['scene'] == $current_scene + ) { + $scene_key = (int) $key; + $existing_objects = $scene['hotspots']; + break; + } + } + $hotspot = new \stdClass; + $hotspot->pitch = $form_state->getValue( + [$element_name, 'hotspots_temp', 'pitch'] + ); + $hotspot->yaw = $form_state->getValue( + [$element_name, 'hotspots_temp', 'yaw'] + ); + $hotspot->type = $form_state->getValue( + [$element_name, 'hotspots_temp', 'type'] + ); + $hotspot->text = $form_state->getValue( + [$element_name, 'hotspots_temp', 'label'] + ); + $hotspot->id = $element_name . '_' . $current_scene . '_' .(count($existing_objects) + 1); + if ($hotspot->type == 'url') { + $hotspot->URL = $hotspot->url; + $hotspot->type = 'info'; + } + if ($hotspot->type == 'ado') { + $nodeid = $form_state->getValue( + [$element_name, 'hotspots_temp', 'ado'] + ); + // Now the fun part. Since this autocomplete is not part of the process + // chain we never get the value transformed into id. + // So. we do it directly + $nodeid = EntityAutocomplete::extractEntityIdFromAutocompleteInput( + $nodeid + ); + $url = \Drupal\Core\Url::fromRoute( + 'entity.node.canonical', + ['node' => $nodeid], + [] + ); + $url = $url->toString(); + $hotspot->type = 'info'; + + $hotspot->URL = $url; + } + + if ($hotspot->type == 'scene') { + $sceneid = $form_state->getValue( + [$element_name, 'hotspots_temp', 'adoscene'] + ); + + $hotspot->type = 'scene'; + $hotspot->sceneId = "{$sceneid}"; + } + + if ($hotspot->type == 'text') { + $hotspot->text = $hotspot->text; + $hotspot->type = 'info'; + } + + $existing_objects[] = (array) $hotspot; + + // @TODO make sure people don't add twice the same coordinates! + + // Push hotspots there + + $allscenes[$scene_key]['hotspots'] = $existing_objects; + $form_state->set($all_scenes_key, $allscenes); + $form_state->setValue([$element_name, 'allscenes'],json_encode($allscenes)); + + + } else { + // Do we alert the user? Form needs to be restarted + static::messenger()->addError(t('Something bad happened with the Tour builder, sadly you will have you restart your session.')); + // We could set a form_state value and render it when the form rebuilds? + } + + $form_state->setRebuild(TRUE); + } + + /** + * @param array $form + * @param \Drupal\Core\Form\FormStateInterface $form_state + * + * @return \Drupal\Core\Ajax\AjaxResponse + */ + public static function addHotSpotCallBack( + array $form, + FormStateInterface $form_state + ) { + $button = $form_state->getTriggeringElement(); + + $element = NestedArray::getValue( + $form, + array_slice($button['#array_parents'], 0, -2) + ); + $element_name = $element['#name']; + $response = new AjaxResponse(); + $data_selector = $element['hotspots_temp']['added_hotspots']['#attributes']['data-drupal-loaded-node-hotspot-table']; + $existing_objects = []; + $current_scene = $form_state->getValue([$element_name, 'currentscene']); + if ($current_scene) { + $allscenes = $form_state->getValue([$element_name, 'allscenes']); + $allscenes = json_decode($allscenes, TRUE); + $current_scene = $form_state->getValue([$element_name, 'scene']); + $scene_key = 0; + $existing_objects = []; + foreach ($allscenes as $key => &$scene) { + if ($scene['scene'] == $current_scene + ) { + $scene_key = $key; + $existing_objects = $scene['hotspots']; + } + } + } + if (count($existing_objects) > 0) { + + $data_selector2 = $element['hotspots_temp']['#attributes']['data-drupal-selector']; + + $response->addCommand( + new AddHotSpotCommand( + '[data-drupal-selector="' . $data_selector2 . '"]', + end($existing_objects), + 'webform_strawberryfield_pannellum_editor_addHotSpot' + ) + ); + } + $response->addCommand( + new ReplaceCommand( + '[data-drupal-loaded-node-hotspot-table="' . $data_selector . '"]', + $element['hotspots_temp']['added_hotspots'] + ) + ); + return $response; + } + + /** + * Submit Handler for Settin the Scene Orientation. + * + * @param array $form + * @param \Drupal\Core\Form\FormStateInterface $form_state + */ + public static function setSceneOrientation( + array &$form, + FormStateInterface $form_state + ) { + $button = $form_state->getTriggeringElement(); + $hot_spot_values_parents = array_slice($button['#parents'], 0, -1); + + $element_name = $hot_spot_values_parents[0]; + if ($form_state->getValue([$element_name, 'scene'])) { + $all_scenes_key = $element_name . '-allscenes'; + $allscenes = $form_state->get($all_scenes_key); + $current_scene = $form_state->getValue([$element_name, 'scene']); + $scene_key = 0; + $existing_objects = []; + foreach ($allscenes as $key => &$scene) { + if ($scene['scene'] == $form_state->getValue( + [$element_name, 'scene'] + )) { + $scene_key = $key; + break; + } + } + + $allscenes[$scene_key]['hfov'] =$form_state->getValue( + [$element_name, 'hotspots_temp', 'hfov']); + $allscenes[$scene_key]['pitch'] =$form_state->getValue( + [$element_name, 'hotspots_temp', 'pitch']); + $allscenes[$scene_key]['yaw'] =$form_state->getValue( + [$element_name, 'hotspots_temp', 'yaw']); + + $form_state->set($all_scenes_key, $allscenes); + $form_state->setValue([$element_name, 'allscenes'],json_encode($allscenes)); + } + + + + // This is strange but needed. + // If we are creating a new panorama, addhotspot submit button + // uses the scene submit (because this is nested) and hidden on initialize + // but if we start with data, its called directly. + // So we have the setRebuild on the parent caller + // \Drupal\webform_strawberryfield\Element\WebformPanoramaTour::selectSceneSubmit + // and also here. all good + $form_state->setRebuild(TRUE); + } + + /** + * @param array $form + * @param \Drupal\Core\Form\FormStateInterface $form_state + * + * @return \Drupal\Core\Ajax\AjaxResponse + */ + public static function setSceneOrientationCallBack( + array $form, + FormStateInterface $form_state + ) { + $button = $form_state->getTriggeringElement(); + $element = NestedArray::getValue( + $form, + array_slice($button['#array_parents'], 0, -1) + ); + error_log('setSceneOrientationCallBack'); + $response = new AjaxResponse(); + $element_name = $element['#name']; + $data_selector = $element['hotspots_temp']['#attributes']['data-webform_strawberryfield-selector']; + $element['hotspots_temp']['#title'] = 'Hotspots processed via ajax for this Scene'; + + // And now replace the container + $response->addCommand( + new ReplaceCommand( + '[data-webform_strawberryfield-selector="' . $data_selector . '"]', + $element['hotspots_temp'] + ) + ); + return $response; + } + + /** + * Submit Handler for Setting the Scene Orientation. + * + * @param array $form + * @param \Drupal\Core\Form\FormStateInterface $form_state + */ + public static function deleteScene( + array &$form, + FormStateInterface $form_state + ) { + + error_log('deleteScene'); + $button = $form_state->getTriggeringElement(); + $hot_spot_values_parents = array_slice($button['#parents'], 0, -1); + + $element_name = $hot_spot_values_parents[0]; + if ($form_state->getValue([$element_name, 'scene'])) { + $all_scenes_key = $element_name . '-allscenes'; + $allscenes = $form_state->get($all_scenes_key); + $current_scene = $form_state->getValue([$element_name, 'scene']); + $scene_key = 0; + $existing_objects = []; + foreach ($allscenes as $key => &$scene) { + if ($scene['scene'] == $form_state->getValue( + [$element_name, 'scene'] + )) { + $scene_key = $key; + break; + } + } + unset($allscenes[$scene_key]); + // which will reorder keys + $allscenes = array_merge($allscenes); + // remove any hotspot pointing to this scene + foreach ($allscenes as &$scene) { + $scene['hotspots'] = array_filter($scene['hotspots'], function($e) use($scene_key) { + $e = (array) $e; + return (!isset($e["sceneId"]) || $e["sceneId"]!=$scene_key); + }); + } + $form_state->set($all_scenes_key, $allscenes); + $form_state->setValue([$element_name, 'allscenes'],json_encode($allscenes)); + if (!empty($allscenes)) { + $firstscene = reset($allscenes); + $firstsceneid = $firstscene['scene']; + $form_state->setValue([$element_name, 'scene'],$firstsceneid); + } else { + $form_state->setValue([$element_name, 'scene'],NULL); + } + + \Drupal::messenger()->addMessage(t('The Scene was removed')); + error_log('done removing Scene and hotspots'); + } + // This is strange but needed. + // If we are creating a new panorama, addhotspot submit button + // uses the scene submit (because this is nested) and hidden on initialize + // but if we start with data, its called directly. + // So we have the setRebuild on the parent caller + // \Drupal\webform_strawberryfield\Element\WebformPanoramaTour::selectSceneSubmit + // and also here. all good + $form_state->setRebuild(TRUE); + } + + /** + * @param array $form + * @param \Drupal\Core\Form\FormStateInterface $form_state + * + * @return \Drupal\Core\Ajax\AjaxResponse + */ + public static function deleteSceneCallBack( + array $form, + FormStateInterface $form_state + ) { + $button = $form_state->getTriggeringElement(); + $element = NestedArray::getValue( + $form, + array_slice($button['#array_parents'], 0, -2) + ); + error_log('deleteSceneCallBack'); + $response = new AjaxResponse(); + $element_name = $element['#name']; + $data_selector = $element['#attributes']['data-drupal-selector']; + + + $settingsclear = [ + 'webform_strawberryfield' => [ + 'WebformPanoramaTour' => [ + $element_name . '-hotspots' => + NULL + ], + ] + ]; + $response->addCommand(new SettingsCommand($settingsclear, TRUE)); + + // Now update the JS settings + if ($form_state->getValue([$element_name, 'scene'])) { + $all_scenes_key = $element_name . '-allscenes'; + $allscenes = $form_state->get($all_scenes_key); + $current_scene = $form_state->getValue([$element_name, 'scene']); + $existing_objects = []; + foreach ($allscenes as $key => &$scene) { + if ($scene['scene'] == $current_scene) { + $existing_objects = $scene['hotspots']; + break; + } + } + + $settings = [ + 'webform_strawberryfield' => [ + 'WebformPanoramaTour' => [ + $element_name . '-hotspots' => + $existing_objects + ], + ] + ]; + error_log('attaching replacement Drupal settings for the viewer after delete'); + error_log(print_r($settings, TRUE)); + // Why twice? well because merge is deep merge. Gosh JS! + // And merge = FALSE clears even my brain settings... + $response->addCommand(new SettingsCommand($settings, TRUE)); + } + + // And now replace the container + $response->addCommand( + new ReplaceCommand( + '[data-drupal-selector="' . $data_selector . '"]', + $element + ) + ); + + return $response; + } + + + /** + * Validates a composite element. + */ + public static function validateWebformComposite(&$element, FormStateInterface $form_state, &$complete_form) { + // IMPORTANT: Must get values from the $form_states since sub-elements + // may call $form_state->setValueForElement() via their validation hook. + // @see \Drupal\webform\Element\WebformEmailConfirm::validateWebformEmailConfirm + // @see \Drupal\webform\Element\WebformOtherBase::validateWebformOther + $value = NestedArray::getValue($form_state->getValues(), $element['#parents']); + // Only validate composite elements that are visible. + $has_access = (!isset($element['#access']) || $element['#access'] === TRUE); + if ($has_access) { + // Validate required composite elements. + $composite_elements = static::getCompositeElements($element); + $composite_elements = WebformElementHelper::getFlattened($composite_elements); + foreach ($composite_elements as $composite_key => $composite_element) { + $is_required = !empty($element[$composite_key]['#required']); + $is_empty = (isset($value[$composite_key]) && $value[$composite_key] === ''); + if ($is_required && $is_empty) { + WebformElementHelper::setRequiredError($element[$composite_key], $form_state); + } + } + } + + // Clear empty composites value. + if (empty(array_filter($value))) { + $element['#value'] = NULL; + $form_state->setValueForElement($element, NULL); + } + } + + public static function validatePanoramaTourElement( + &$element, + FormStateInterface $form_state, + &$complete_form + ) { + + if ($triggering = $form_state->getTriggeringElement()) { + if (reset($triggering['#parents']) == $element['#name']) { + // Means it was something inside the button + } + else { + error_log('Clear our redundant values'); + $form_state->unsetValue([$element['#name'], 'scene']); + $form_state->unsetValue([$element['#name'], 'hotspots']); + $form_state->unsetValue([$element['#name'], 'hotspots_temp']); + $form_state->unsetValue([$element['#name'], 'select_button']); + $form_state->unsetValue([$element['#name'], 'select_button']); + // sets that actual expanded array back into the element value + // but only when the validation is triggered by the main form, + // like in a next button action or a save one. + $value = NestedArray::getValue($form_state->getValues(), $element['#parents']); + // Let's try to get our direct form value into the main for element value + // if its not there, lets use our input. Feels safer since people won't + // have the chance to alter input via HTML. + // Inverse of that the process function does. + $all_scenes_key = $element['#name'] . '-allscenes'; + $all_scenes_from_form_state = !empty($form_state->getValue([$element['#name'],'allscenes'])) ? json_decode($form_state->getValue([$element['#name'],'allscenes']),TRUE) : []; + $all_scenes_from_form_value = !empty($form_state->get($all_scenes_key)) ? $form_state->get($all_scenes_key) : []; + // Basically. First try to get it from the defaults set by the ::valuecallback(). + // This will populate Form state value. + // But once that happens, the #value elements does not survive. + // So we store it in a form state variable. And keep setting it. + $all_scenes = !empty($all_scenes_from_form_value) ? $all_scenes_from_form_value : $all_scenes_from_form_state; + $form_state->setValueForElement($element, $all_scenes); + } + } + } + +} diff --git a/src/Element/WebformWithOverride.php b/src/Element/WebformWithOverride.php index 7d6f8f4..02d6662 100644 --- a/src/Element/WebformWithOverride.php +++ b/src/Element/WebformWithOverride.php @@ -65,11 +65,16 @@ public static function preRenderWebformElement($element) { $new_settings = $element['#override']; $webform->setSettingsOverride($new_settings); $values['strawberryfield:override'] = $new_settings; - } // Build the webform. $element['webform_build'] = $webform->getSubmissionForm($values); + // Adds this marker so we can suggest another template. + // Where we can get rid of the internal 'form' rendering + // Allowing DOM to set the so needed classes for the wrapper + // So states and other WEbform AJAX libraries can work correctly. + // We don't want a full blown preprocess (yet). + $element['webform_build']['#attributes']['data-webform-inline-fieldwidget'] = 'true'; // Set custom form action. if (!empty($element['#action'])) { diff --git a/src/Plugin/EntityReferenceSelection/ViewsSolrSelection.php b/src/Plugin/EntityReferenceSelection/ViewsSolrSelection.php index e3b2744..80090ba 100644 --- a/src/Plugin/EntityReferenceSelection/ViewsSolrSelection.php +++ b/src/Plugin/EntityReferenceSelection/ViewsSolrSelection.php @@ -11,6 +11,7 @@ use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait; use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginBase; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Entity\Plugin\DataType\EntityAdapter; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; @@ -19,6 +20,9 @@ use Drupal\views\Views; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\Core\Language\LanguageInterface; +use Drupal\Core\Render\RendererInterface; +use Drupal\Component\Utility\Xss; +use Drupal\views\Render\ViewsRenderPipelineMarkup; /** * Plugin implementation of the 'selection' entity_reference. @@ -68,6 +72,13 @@ class ViewsSolrSelection extends SelectionPluginBase implements ContainerFactory */ protected $currentUser; + /** + * The renderer. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + /** * Constructs a new ViewsSelection object. * @@ -90,13 +101,22 @@ public function __construct( $plugin_definition, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, - AccountInterface $current_user + AccountInterface $current_user, + RendererInterface $renderer = NULL ) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->entityTypeManager = $entity_type_manager; $this->moduleHandler = $module_handler; $this->currentUser = $current_user; + if (!$renderer) { + @trigger_error( + 'Calling ViewsSelection::__construct() with the $renderer argument is supported in drupal:8.7.0 and will be required before drupal:9.0.0.', + E_USER_DEPRECATED + ); + $renderer = \Drupal::service('renderer'); + } + $this->renderer = $renderer; } /** @@ -114,7 +134,8 @@ public static function create( $plugin_definition, $container->get('entity_type.manager'), $container->get('module_handler'), - $container->get('current_user') + $container->get('current_user'), + $container->get('renderer') ); } @@ -298,24 +319,95 @@ public function getReferenceableEntities( $match_operator = 'CONTAINS', $limit = 0 ) { + $entities = []; + if ($display_execution_results = $this->getDisplayExecutionResults( + $match, + $match_operator, + $limit + )) { + $entities = $this->stripAdminAndAnchorTagsFromResults( + $display_execution_results + ); + } + + return $entities; + } + + /** + * Fetches the results of executing the display. + * + * @param string|null $match + * (Optional) Text to match the label against. Defaults to NULL. + * @param string $match_operator + * (Optional) The operation the matching should be done with. Defaults + * to "CONTAINS". + * @param int $limit + * Limit the query to a given number of items. Defaults to 0, which + * indicates no limiting. + * @param array|null $ids + * Array of entity IDs. Defaults to NULL. + * + * @return array + * The results. + */ + protected function getDisplayExecutionResults( + string $match = NULL, + string $match_operator = 'CONTAINS', + int $limit = 0, + array $ids = NULL + ): array { $display_name = $this->getConfiguration()['view']['display_name']; $arguments = $this->getConfiguration()['view']['arguments']; - $result = []; + $results = []; if ($this->initializeView($match, $match_operator, $limit)) { - $result = $this->view->executeDisplay($display_name, $arguments); + $results = $this->view->executeDisplay($display_name, $arguments); + } + return $results ?? []; + } + + /** + * Strips all admin and anchor tags from a result list. + * + * These results are usually displayed in an autocomplete field, which is + * surrounded by anchor tags. Most tags are allowed inside anchor tags, except + * for other anchor tags. + * + * @param array $results + * The result list. + * + * @return array + * The provided result list with anchor tags removed. + */ + protected function stripAdminAndAnchorTagsFromResults(array $results): array { + $allowed_tags = Xss::getAdminTagList(); + if (($key = array_search('a', $allowed_tags)) !== FALSE) { + unset($allowed_tags[$key]); } - $return = []; - if ($result) { - foreach ($this->view->result as $row) { - /* @var $entityadapter \Drupal\Core\Entity\Plugin\DataType\EntityAdapter */ - $entityadapter = $row->_object; + $stripped_results = []; + + foreach ($results as $id => $row) { + /* @var $entityadapter EntityAdapter */ + if (isset($row['#row'])) { + // Means field render or default + $entityadapter = $row['#row']->_object; $entity = $entityadapter->getValue(); - $return[$entity->bundle()][$entity->id()] = $entity->label(); } + elseif (isset($row['#node'])) { + $entity = $row['#node']; + } + else { + // We don't know what we got, but its no content entity. + break; + } + + $stripped_results[$entity->bundle()][$entity->id( + )] = ViewsRenderPipelineMarkup::create( + Xss::filter($this->renderer->renderPlain($row), $allowed_tags) + ); } - return $return; + return $stripped_results; } /** @@ -333,6 +425,8 @@ public function countReferenceableEntities( * {@inheritdoc} */ public function validateReferenceableEntities(array $ids) { + // This class differs from the SQL implemententation + // Because we need to transform Solr entity:node/9:en into 9 $display_name = $this->getConfiguration()['view']['display_name']; $arguments = $this->getConfiguration()['view']['arguments']; $result = []; diff --git a/src/Plugin/WebformElement/WebformNominatim.php b/src/Plugin/WebformElement/WebformNominatim.php index 27a2bf4..1fdd9c7 100644 --- a/src/Plugin/WebformElement/WebformNominatim.php +++ b/src/Plugin/WebformElement/WebformNominatim.php @@ -56,8 +56,7 @@ protected function formatHtmlItem(array $element, WebformSubmissionInterface $we if ($format == 'map') { $location = $value['location']; - dpm($location); - $center = urlencode($value['location']); + $center = urlencode($value['location']); // To build an OpenStreetmap link we need, e.g // "osm_type": "way", // "osm_id": 270859746, diff --git a/src/Plugin/WebformElement/WebformPanoramaTour.php b/src/Plugin/WebformElement/WebformPanoramaTour.php new file mode 100644 index 0000000..50caede --- /dev/null +++ b/src/Plugin/WebformElement/WebformPanoramaTour.php @@ -0,0 +1,78 @@ +elementManager->isExcluded('webform_metadata_panoramatour') ? $this->t('Panorama Multi Scene Builder') : parent::getPluginLabel(); + } + + /** + * {@inheritdoc} + */ + protected function formatHtmlItemValue(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + return $this->formatTextItemValue($element, $webform_submission, $options); + } + + /** + * {@inheritdoc} + */ + protected function formatTextItemValue(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + $value = $this->getValue($element, $webform_submission, $options); + // No preview for now + // Just too complex + // @TODO next iteration can force a Formatter to be used. + $lines = []; + return $lines; + } + + public function supportsMultipleValues() { + // Make sure people can not change this value. + return FALSE; + } + + public function getDefaultProperties() { + $defaults = parent::getDefaultProperties(); + unset($defaults['multiple']); + return $defaults; + } + + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['element']['multiple']['#disabled'] = TRUE; + $form['element']['multiple']['#description'] = '' . $this->t('You can only build one Tour with this Webform Element. But it supports multiple Scenes') . ''; + // Disable Multiple Elements option + return $form; + } + + +} diff --git a/src/Plugin/WebformHandler/strawberryFieldharvester.php b/src/Plugin/WebformHandler/strawberryFieldharvester.php index 55d2d02..e780577 100644 --- a/src/Plugin/WebformHandler/strawberryFieldharvester.php +++ b/src/Plugin/WebformHandler/strawberryFieldharvester.php @@ -37,8 +37,8 @@ * submission = \Drupal\webform\Plugin\WebformHandlerInterface::SUBMISSION_OPTIONAL, * ) */ -class strawberryFieldharvester extends WebformHandlerBase -{ +class strawberryFieldharvester extends WebformHandlerBase { + /** * @var bool */ @@ -55,6 +55,7 @@ class strawberryFieldharvester extends WebformHandlerBase * @var \Drupal\webform\WebformTokenManagerInterface */ protected $tokenManager; + /** * @var \Drupal\Core\File\FileSystemInterface */ @@ -64,10 +65,12 @@ class strawberryFieldharvester extends WebformHandlerBase * @var \Drupal\file\FileUsage\FileUsageInterface */ protected $fileUsage; + /** * @var \Drupal\Component\Transliteration\TransliterationInterface */ protected $transliteration; + /** * @var \Drupal\Core\Language\LanguageManagerInterface */ @@ -83,6 +86,7 @@ class strawberryFieldharvester extends WebformHandlerBase /** * strawberryFieldharvester constructor. + * * @param array $configuration * @param $plugin_id * @param $plugin_definition @@ -96,8 +100,29 @@ class strawberryFieldharvester extends WebformHandlerBase * @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, LoggerChannelFactoryInterface $logger_factory, ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, WebformSubmissionConditionsValidatorInterface $conditions_validator, WebformTokenManagerInterface $token_manager, FileSystemInterface $file_system, FileUsageInterface $file_usage, TransliterationInterface $transliteration, LanguageManagerInterface $language_manager) { - parent::__construct($configuration, $plugin_id, $plugin_definition, $logger_factory, $config_factory, $entity_type_manager, $conditions_validator); + public function __construct( + array $configuration, + $plugin_id, + $plugin_definition, + LoggerChannelFactoryInterface $logger_factory, + ConfigFactoryInterface $config_factory, + EntityTypeManagerInterface $entity_type_manager, + WebformSubmissionConditionsValidatorInterface $conditions_validator, + WebformTokenManagerInterface $token_manager, + FileSystemInterface $file_system, + FileUsageInterface $file_usage, + TransliterationInterface $transliteration, + LanguageManagerInterface $language_manager + ) { + parent::__construct( + $configuration, + $plugin_id, + $plugin_definition, + $logger_factory, + $config_factory, + $entity_type_manager, + $conditions_validator + ); $this->entityTypeManager = $entity_type_manager; $this->tokenManager = $token_manager; $this->fileSystem = $file_system; @@ -110,7 +135,12 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition /** * {@inheritdoc} */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + public static function create( + ContainerInterface $container, + array $configuration, + $plugin_id, + $plugin_definition + ) { return new static( $configuration, $plugin_id, @@ -131,27 +161,25 @@ public static function create(ContainerInterface $container, array $configuratio /** * @return bool */ - public function isWidgetDriven(): bool - { + public function isWidgetDriven(): bool { return $this->isWidgetDriven; } /** * @param bool $isWidgetDriven */ - public function setIsWidgetDriven(bool $isWidgetDriven): void - { + public function setIsWidgetDriven(bool $isWidgetDriven): void { $this->isWidgetDriven = $isWidgetDriven; } - /** * {@inheritdoc} */ - public function postLoad(WebformSubmissionInterface $webform_submission) - { - parent::postLoad($webform_submission); // TODO: Change the autogenerated stub + public function postLoad(WebformSubmissionInterface $webform_submission) { + parent::postLoad( + $webform_submission + ); // TODO: Change the autogenerated stub } @@ -159,12 +187,11 @@ public function postLoad(WebformSubmissionInterface $webform_submission) /** * {@inheritdoc} */ - public function defaultConfiguration() - { + public function defaultConfiguration() { // @TODO this will be sent to Esmero. return [ 'submission_url' => 'https://api.example.org/SOME/ENDPOINT', - 'upload_scheme' => 'public://' + 'upload_scheme' => 'public://', ]; } @@ -182,8 +209,10 @@ public function getSummary() { /** * {@inheritdoc} */ - public function buildConfigurationForm(array $form, FormStateInterface $form_state) - { + public function buildConfigurationForm( + array $form, + FormStateInterface $form_state + ) { $form['submission_url'] = [ '#type' => 'textfield', '#title' => $this->t('Secondary submission URL to api.example.org'), @@ -195,7 +224,9 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta $form['upload_scheme'] = [ '#type' => 'radios', '#title' => $this->t('Permanent destination for uploaded files'), - '#description' => $this->t('The Permanent URI Scheme destination for uploaded files.'), + '#description' => $this->t( + 'The Permanent URI Scheme destination for uploaded files.' + ), '#default_value' => $this->configuration['upload_scheme'], '#required' => TRUE, '#options' => $scheme_options, @@ -204,8 +235,10 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta return $form; } - public function submitConfigurationForm(array &$form, FormStateInterface $form_state) - { + public function submitConfigurationForm( + array &$form, + FormStateInterface $form_state + ) { parent::submitConfigurationForm($form, $form_state); $this->applyFormStateToConfiguration($form_state); } @@ -213,8 +246,7 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s /** * {@inheritdoc} */ - public function preSave(WebformSubmissionInterface $webform_submission) - { + public function preSave(WebformSubmissionInterface $webform_submission) { $values = $webform_submission->getData(); $cleanvalues = $values; @@ -224,23 +256,38 @@ public function preSave(WebformSubmissionInterface $webform_submission) // Check which elements carry files around $allelements = $webform_submission->getWebform()->getElementsManagedFiles(); foreach ($allelements as $element) { - $originalelement = $webform_submission->getWebform()->getElement($element); - // Track what fields map to file entities. - $entity_mapping_structure['entity:file'][] = $originalelement['#webform_key']; - // Process each managed files field. - $processedcleanvaluesforfield = $this->processFileField($originalelement, $webform_submission, $cleanvalues); - // Merge since different fields can contribute to same as:filetype structure. - $processedcleanvalues = array_merge_recursive($processedcleanvalues, $processedcleanvaluesforfield); - } + $originalelement = $webform_submission->getWebform()->getElement( + $element + ); + // Track what fields map to file entities. + $entity_mapping_structure['entity:file'][] = $originalelement['#webform_key']; + // Process each managed files field. + $processedcleanvaluesforfield = $this->processFileField( + $originalelement, + $webform_submission, + $cleanvalues + ); + // Merge since different fields can contribute to same as:filetype structure. + $processedcleanvalues = array_merge_recursive( + $processedcleanvalues, + $processedcleanvaluesforfield + ); + } // Check also which elements carry entity references around // @see https://www.drupal.org/project/webform/issues/3067958 if (isset($entity_mapping_structure['entity:node'])) { //@TODO change this stub. Get every element that extends Drupal\webform\Plugin\WebformElementEntityReferenceInterface() - $entity_mapping_structure['entity:node'] = array_unique($entity_mapping_structure['entity:node'],SORT_STRING); + $entity_mapping_structure['entity:node'] = array_unique( + $entity_mapping_structure['entity:node'], + SORT_STRING + ); } if (isset($entity_mapping_structure['entity:file'])) { - $entity_mapping_structure['entity:file'] = array_unique($entity_mapping_structure['entity:file'],SORT_STRING); + $entity_mapping_structure['entity:file'] = array_unique( + $entity_mapping_structure['entity:file'], + SORT_STRING + ); } // Distribute all processed AS values for each field into its final JSON // Structure, e.g as:image, as:application, as:documents, etc. @@ -267,7 +314,7 @@ public function preSave(WebformSubmissionInterface $webform_submission) // of the strawberry_field_widget_state_id if the submission is also saved $webform_submission->setData($cleanvalues); - $cleanvalues = json_encode( $cleanvalues, JSON_PRETTY_PRINT); + $cleanvalues = json_encode($cleanvalues, JSON_PRETTY_PRINT); try { $tempstore->set( @@ -275,21 +322,36 @@ public function preSave(WebformSubmissionInterface $webform_submission) $cleanvalues ); } catch (TempStoreException $e) { - $this->messenger()->addError($this->t('Sorry, we have issues writing metadata to your session storage. Please reload this form and/or contact your system admin.')); - $this->loggerFactory->get('archipelago')->error('Webform @webformid can not write to temp storage! with error @message. Attempted Metadata input was
%data
', [ - '@webformid' => $this->getWebform()->id(), - '%data' => print_r($webform_submission->getData(), TRUE), - '@error' => $e->getMessage(), - ]); + $this->messenger()->addError( + $this->t( + 'Sorry, we have issues writing metadata to your session storage. Please reload this form and/or contact your system admin.' + ) + ); + $this->loggerFactory->get('archipelago')->error( + 'Webform @webformid can not write to temp storage! with error @message. Attempted Metadata input was
%data
', + [ + '@webformid' => $this->getWebform()->id(), + '%data' => print_r($webform_submission->getData(), TRUE), + '@error' => $e->getMessage(), + ] + ); } - } elseif ($this->IsWidgetDriven()) { - $this->messenger()->addError($this->t('We lost TV reception in the middle of the match...Please contact your system admin.')); - $this->loggerFactory->get('archipelago')->error('Webform @webformid lost connection to temp storage and its Widget!. No Widget State id present. Attempted Metadata input was
%data
', [ - '@webformid' => $this->getWebform()->id(), - '%data' => print_r($webform_submission->getData(), TRUE), - ]); + } + elseif ($this->IsWidgetDriven()) { + $this->messenger()->addError( + $this->t( + 'We lost TV reception in the middle of the match...Please contact your system admin.' + ) + ); + $this->loggerFactory->get('archipelago')->error( + 'Webform @webformid lost connection to temp storage and its Widget!. No Widget State id present. Attempted Metadata input was
%data
', + [ + '@webformid' => $this->getWebform()->id(), + '%data' => print_r($webform_submission->getData(), TRUE), + ] + ); } parent::preSave($webform_submission); // TODO: Change the autogenerated stub @@ -298,41 +360,59 @@ public function preSave(WebformSubmissionInterface $webform_submission) /** * {@inheritdoc} */ - public function validateForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) - { + public function validateForm( + array &$form, + FormStateInterface $form_state, + WebformSubmissionInterface $webform_submission + ) { $values = $webform_submission->getData(); - if ((!isset($values["strawberry_field_widget_state_id"]) || empty($values["strawberry_field_widget_state_id"])) && $this->IsWidgetDriven()) { - $this->messenger()->addError($this->t('Sorry, we have issues reading your session storage identifier. Error was logged. Please reload this form and/or contact your system admin.')); - - $this->loggerFactory->get('archipelago')->error('Webform @webformid lost connection to temp storage!. No Widget State id present. Attempted Metadata input was
%data
', [ - '@webformid' => $this->getWebform()->id(), - '%data' => print_r($webform_submission->getData(), TRUE), - ]); + if ((!isset($values["strawberry_field_widget_state_id"]) || empty($values["strawberry_field_widget_state_id"])) && $this->IsWidgetDriven( + )) { + $this->messenger()->addError( + $this->t( + 'Sorry, we have issues reading your session storage identifier. Error was logged. Please reload this form and/or contact your system admin.' + ) + ); + + $this->loggerFactory->get('archipelago')->error( + 'Webform @webformid lost connection to temp storage!. No Widget State id present. Attempted Metadata input was
%data
', + [ + '@webformid' => $this->getWebform()->id(), + '%data' => print_r($webform_submission->getData(), TRUE), + ] + ); } // All data is available here $webform_submission->getData())); // @TODO what should be validated here? - parent::validateForm($form, $form_state, $webform_submission); // TODO: Change the autogenerated stub + parent::validateForm( + $form, + $form_state, + $webform_submission + ); // TODO: Change the autogenerated stub } /** * {@inheritdoc} */ - public function submitForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) - { + public function submitForm( + array &$form, + FormStateInterface $form_state, + WebformSubmissionInterface $webform_submission + ) { $values = $webform_submission->getData(); if (isset($values["strawberry_field_widget_state_id"])) { - $this->setIsWidgetDriven(true); + $this->setIsWidgetDriven(TRUE); } - // @TODO add a full-blown values cleaner - // @TODO add the webform name used to create this as additional KEY - // @TODO make sure widget can read that too. - // @If Widget != setup form, ask for User feedback - // @TODO, i need to alter node submit handler to add also the - // Entities full URL as an @id to the top of the saved JSON. - // FUN! + // @TODO add a full-blown values cleaner + // @TODO add the webform name used to create this as additional KEY + // @TODO make sure widget can read that too. + // @If Widget != setup form, ask for User feedback + // @TODO, i need to alter node submit handler to add also the + // Entities full URL as an @id to the top of the saved JSON. + // FUN! // Get the URL to post the data to. // @todo esmero a.k.a as Fedora-mockingbird $post_url = $this->configuration['submission_url']; @@ -349,7 +429,11 @@ public function submitForm(array &$form, FormStateInterface $form_state, Webform * @return array * Associative array keyed by AS type with binary info. */ - public function processFileField(array $element, WebformSubmissionInterface $webform_submission, $cleanvalues) { + public function processFileField( + array $element, + WebformSubmissionInterface $webform_submission, + $cleanvalues + ) { $key = $element['#webform_key']; $type = $element['#type']; @@ -361,7 +445,9 @@ public function processFileField(array $element, WebformSubmissionInterface $web $fids = (is_array($value)) ? $value : [$value]; $original_value = isset($original_data[$key]) ? $original_data[$key] : []; - $original_fids = (is_array($original_value)) ? $original_value : [$original_value]; + $original_fids = (is_array( + $original_value + )) ? $original_value : [$original_value]; // Delete the old file uploads? // @TODO build some cleanup logic here. Could be moved to attached field hook. @@ -381,7 +467,8 @@ public function processFileField(array $element, WebformSubmissionInterface $web } /* @see \Drupal\strawberryfield\StrawberryfieldFilePersisterService */ - $processedAsValues = \Drupal::service('strawberryfield.file_persister')->generateAsFileStructure($fids, $key, $cleanvalues); + $processedAsValues = \Drupal::service('strawberryfield.file_persister') + ->generateAsFileStructure($fids, $key, $cleanvalues); return $processedAsValues; } @@ -389,7 +476,11 @@ public function processFileField(array $element, WebformSubmissionInterface $web /** * {@inheritdoc} */ - public function confirmForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) { + public function confirmForm( + array &$form, + FormStateInterface $form_state, + WebformSubmissionInterface $webform_submission + ) { // We really want to avoid being redirected. This is how it is done. //@TODO manage file upload if there is no submission save handler //@ see \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase::postSave @@ -400,12 +491,12 @@ public function confirmForm(array &$form, FormStateInterface $form_state, Webfor /** * {@inheritdoc} */ - public function preprocessConfirmation(array &$variables) - { + public function preprocessConfirmation(array &$variables) { if ($this->isWidgetDriven()) { unset($variables['back']); } } + /** * {@inheritdoc} */ @@ -444,7 +535,8 @@ protected function getUriSchemeForManagedFile(array $element) { if (isset($element['#uri_scheme'])) { return $element['#uri_scheme']; } - $scheme_options = \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase::getVisibleStreamWrappers(); + $scheme_options = \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase::getVisibleStreamWrappers( + ); if (isset($scheme_options['private'])) { return 'private'; } diff --git a/templates/webform-metadata-panoramatour.html.twig b/templates/webform-metadata-panoramatour.html.twig new file mode 100644 index 0000000..2d35edb --- /dev/null +++ b/templates/webform-metadata-panoramatour.html.twig @@ -0,0 +1,17 @@ +{# +/** + * @file + * Default theme implementation of a Nominatim metadata composite webform element. + * + * Available variables: + * - content: The Panoramatour webform element to be output. + * - sceneselect: The select form/select dropdown user to choose a Panorama Object + * - hotspotui: All the Inputs used on the UI + * - hotspotbuttons: All the buttons used for doing stuff on loaded scenes + * - node: the actual loaded Panorama Viewer + * @see template_preprocess_webform_metadata_panoramatour() + * + * @ingroup themeable + */ +#} +{{ content }} \ No newline at end of file diff --git a/templates/webform_strawberryfield-inlinefieldwidget.html.twig b/templates/webform_strawberryfield-inlinefieldwidget.html.twig new file mode 100644 index 0000000..93c0a2d --- /dev/null +++ b/templates/webform_strawberryfield-inlinefieldwidget.html.twig @@ -0,0 +1,28 @@ +{# +/** + * @file + * Theme implementation for a 'webform' element overriden by our module + * + * This is an copy of the webform.html.twig theme_wrapper but changeas + * form element for a div, so it can live in HTML DOM inside an Entity Form + * as a Widget + * 'title_prefix' and 'title_suffix' variables needed for + * + * Available variables + * - attributes: A list of HTML attributes for the wrapper element. + * - children: The child elements of the webform. + * - title_prefix: Additional output populated by modules, intended to be + * displayed in front of the main title tag that appears in the template. + * - title_suffix: Additional output populated by modules, intended to be + * displayed after the main title tag that appears in the template. + * + * @see template_preprocess_webform() + * + * @ingroup themeable + */ +#} + + {{ title_prefix }} + {{ children }} + {{ title_suffix }} + \ No newline at end of file diff --git a/webform_strawberryfield.libraries.yml b/webform_strawberryfield.libraries.yml index eae0373..08ad1f9 100644 --- a/webform_strawberryfield.libraries.yml +++ b/webform_strawberryfield.libraries.yml @@ -10,4 +10,17 @@ webform_strawberryfield.nodeactions.toggle: - core/jquery - core/drupal - core/drupalSettings - - core/drupal.form \ No newline at end of file + - core/drupal.form +scenebuilder_pannellum_strawberry: + version: 1.0 + js: + js/scenebuilder-pannellum_strawberryfield.js: {minified: false} + css: + component: + css/scenebuilder_format_strawberryfield.css: {} + dependencies: + - core/jquery + - core/drupal + - core/drupalSettings + - format_strawberryfield/pannellum + - format_strawberryfield/iiif_pannellum_strawberry \ No newline at end of file diff --git a/webform_strawberryfield.module b/webform_strawberryfield.module index 054bf55..089d9ce 100644 --- a/webform_strawberryfield.module +++ b/webform_strawberryfield.module @@ -9,6 +9,10 @@ use Drupal\Core\Url; use Drupal\webform\WebformSubmissionForm; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\file\Entity\File; +use Drupal\format_strawberryfield\Tools\IiifHelper; +use Drupal\Core\StreamWrapper\StreamWrapperInterface; +use Drupal\Core\File\FileSystemInterface; +use Drupal\image\Entity\ImageStyle; /** * Alters a webform to help with embbeding in a node crud context. @@ -215,10 +219,18 @@ function webform_strawberryfield_form_node_form_alter(&$form, FormStateInterface */ function webform_strawberryfield_theme() { $info = [ - 'webform_metadata_nominatim' => [ - 'render element' => 'element', - ] - ]; + 'webform_metadata_nominatim' => [ + 'render element' => 'element', + ], + 'webform_metadata_panoramatour' => [ + 'render element' => 'element', + ], + 'webform_inline_fieldwidget_form' => [ + 'variables' => [], + 'template' => 'webform_strawberryfield-inlinefieldwidget', + 'base hook' => 'webform' + ], + ]; return $info; } @@ -233,12 +245,290 @@ function webform_strawberryfield_theme() { */ function template_preprocess_webform_metadata_nominatim(array &$variables) { $variables['content'] = $variables['element']; + unset($variables['content']['value']); unset($variables['content']['nominatim']); unset($variables['content']['feature']); $variables['fetchbox'] = $variables['element']['value']; $variables['fetchbox_button'] = $variables['element']['nominatim']; $variables['fetchbox_table'] = $variables['element']['feature']; +} + +/** + * Prepares variables for location composite element templates. + * + * Default template: webform-metadata-panoramatour.html.twig. + * + * @param array $variables + * An associative array containing: + * - element: An associative array containing the properties of the element. + */ +function template_preprocess_webform_metadata_panoramatour(array &$variables) { + $variables['content'] = $variables['element']; +} + +/** + * Implements hook_theme_suggestions_HOOK_alter() for webforms. + * + * @param $suggestions + * @param array $variables + */ +function webform_strawberryfield_theme_suggestions_webform_alter(&$suggestions, array $variables) { + // Add our own webform suggestion based on a given data attribute set by + // \Drupal\webform_strawberryfield\Element\WebformWithOverride::preRenderWebformElement + $element = $variables['element']; + // This is in particular for 'webform_inline_fieldwidget' element + if (isset($element['#attributes']['data-webform-inline-fieldwidget'])) { + $suggestions = ['webform_inline_fieldwidget_form', 'webform_inline_fieldwidget_form__' . $element['#webform_id']]; + } +} + +/** + * Implements hook_preprocess_HOOK() + * + * @see template_preprocess_webform_element_image_file() + * + * @param array $variables + * An associative array containing the following key: + * - element: The webform element. + * - value: The content for the element. + * - options Associative array of options for element. + * - file: The element's File object. + * - style_name: An image style name. + * - format: Image formatting (link or modal) + */ +function webform_strawberryfield_preprocess_webform_element_image_file( + array &$variables +) { + + if (!empty($variables['file'])) { + // TODO do we need a setting for this? + $max_width = 320; + + //@TODO this is quite a copy of what Format_strawberry does. We should + // Move all that logic to helper methods into \Drupal\format_strawberryfield\Tools\IiifHelper + /** @var \Drupal\file\FileInterface $file */ + $file = $variables['file']; + + $variables['image']['#type'] = 'container'; + unset($variables['image']['#theme']); + $variables['image']['singleimage']['#attributes'] = $variables['image']['#attributes']; + unset($variables['image']['#uri']); + unset($variables['image']['#attributes']); + + $style_name = $variables['style_name']; + $format = $variables['format']; + + $uri = $file->getFileUri(); + $url = Url::fromUri(file_create_url($uri)); + if (!$file->isTemporary()) { + + $iiifserversettings = \Drupal::config( + 'format_strawberryfield.iiif_settings' + ); + $iiifhelper = new IiifHelper( + $iiifserversettings->get('pub_server_url'), + $iiifserversettings->get('int_server_url') + ); + // Deal with Drupal 8.8.x v/s 8.7 + if (method_exists( + \Drupal::service('stream_wrapper_manager'), + 'getTarget' + )) { + $iiifidentifier = urlencode( + \Drupal::service('stream_wrapper_manager')->getTarget( + $file->getFileUri() + ) + ); + } + else { + $iiifidentifier = urlencode(file_uri_target($file->getFileUri())); + } + + if ($iiifidentifier == NULL || empty($iiifidentifier)) { + // Nothing to do, lets leave this untouched. + return; + + } + + $iiifpublicinfojson = $iiifhelper->getPublicInfoJson($iiifidentifier); + $iiifsizes = $iiifhelper->getImageSizes($iiifidentifier); + + if (!$iiifsizes) { + $message = t( + 'We could not fetch Image sizes from IIIF @url', + [ + '@url' => $iiifpublicinfojson, + ] + ); + \Drupal::logger('webform_strawberryfield')->warning($message); + // Nothing to do, lets leave this untouched. + return; + } + else { + //@see \template_preprocess_image for further theme_image() attributes. + // Look. This one uses the public accesible base URL. That is how world works. + $iiifserverthumb = "{$iiifserversettings->get('pub_server_url')}/{$iiifidentifier}" . "/full/{$max_width},/0/default.jpg"; + $url = $iiifserverthumb; + } + } + else { + // Its a temporary file, just uploaded, IIIF can not see it yet + + $route_parameters = [ + 'uuid' => $file->uuid(), + 'format' => 'default.' . pathinfo( + $file->getFilename(), + PATHINFO_EXTENSION + ), + ]; + $publicurl = Url::fromRoute( + 'format_strawberryfield.tempiiifbinary', + $route_parameters + ); + $url = $publicurl->toString(); + // Extract EXIF and show it to the user! + /** @var \Drupal\Core\File\FileSystem $file_system */ + $scheme = \Drupal::service('file_system')->uriScheme($uri); + $templocation = NULL; + // If the file isn't stored locally make a temporary copy. + if (!isset( + \Drupal::service('stream_wrapper_manager') + ->getWrappers(StreamWrapperInterface::LOCAL)[$scheme] + )) { + // Local stream. + $cache_key = md5($uri); + $templocation = \Drupal::service('file_system')->copy( + $uri, + 'temporary://sbr_' . $cache_key . '_' . basename($uri), + FileSystemInterface::EXISTS_REPLACE + ); + $templocation = \Drupal::service('file_system')->realpath( + $templocation + ); + } + else { + $templocation = \Drupal::service('file_system')->realpath( + $file->getFileUri() + ); + } + if ($templocation) { + $result = exec( + 'exiftool -json -q ' . escapeshellcmd($templocation), + $output, + $status + ); + if ($status != 0) { + \Drupal::service('messenger') + ->addMessage( + t('Ups. We could not get EXIF from your file. Sorry.') + ); + } + else { + $more_str = implode('', $output); + $json = json_decode($more_str, TRUE); + $json_error = json_last_error(); + //This will end with all data in an [0] index. + if ($json_error == JSON_ERROR_NONE && count($json)) { + $rows = []; + foreach ($json[0] as $key => $value) { + if (!in_array($key, ['Directory', 'SourceFile'])) { + $rows[] = [$key, $value]; + } + } + $more = [ + '#type' => 'table', + '#caption' => t('EXIF Data'), + '#header' => [t('Property'), t('Value')], + '#rows' => $rows, + ]; + } + else { + $more = 'Sorry, we could not fetch EXIF data for this file'; + } + + $variables['image']['exif'] = [ + '#theme' => 'webform_element_more', + '#more' => $more, + '#more_title' => t('Exif'), + ]; + } + } + } + + $extension = pathinfo($uri, PATHINFO_EXTENSION); + $is_image = in_array( + $extension, + ['gif', 'png', 'jpg', 'jpeg', 'jp2', 'tiff'] + ); + + // Build image. + if ($is_image && \Drupal::moduleHandler()->moduleExists( + 'image' + ) && $style_name && ImageStyle::load($style_name)) { + $variables['image']['singleimage'] = [ + '#theme' => 'image_style', + '#style_name' => $variables['style_name'], + ]; + } + else { + // Note: The 'image' template uses root-relative paths. + // The 'image' is preprocessed to use absolute URLs. + // @see webform_preprocess_image(). + $variables['image']['singleimage'] = [ + '#theme' => 'image', + ]; + } + // Change the class from webform-image-file to webform-strawberryfield-image-file + // to avoid webform_preprocess_image() messing up our IIIF link + // by appending the current global. + // @TODO Style element here feels like a hack. We can do better + $variables['image']['singleimage'] += [ + '#uri' => $url, + '#attributes' => [ + 'class' => ['webform-strawberryfield-image-file'], + 'alt' => $file->getFilename(), + 'title' => $file->getFilename(), + 'style' => "max-width:{$max_width}px;height:auto", + ], + ]; + + // For the Results table always display the file name as a tooltip. + if (strpos( + \Drupal::routeMatch()->getRouteName(), + 'webform.results_submissions' + ) !== FALSE) { + $variables['attached']['library'][] = 'webform/webform.tooltip'; + $variables['image']['singleimage']['#attributes']['class'][] = 'js-webform-tooltip-link'; + } + + // Wrap 'image' in a link/modal. + if ($format && $format != 'image') { + $variables['image']['singleimage'] = [ + '#type' => 'link', + '#title' => $variables['image'], + '#url' => $url, + ]; + switch ($format) { + case 'modal': + $variables['image']['singleimage'] += [ + '#attributes' => [ + 'class' => [ + 'js-webform-image-file-modal', + 'webform-image-file-modal', + ], + ], + '#attached' => ['library' => ['webform/webform.element.image_file.modal']], + ]; + break; + + case 'link': + $variables['image']['singleimage'] += ['#attributes' => ['class' => ['webform-image-file-link']]]; + break; + } + } + + } +} -} \ No newline at end of file