diff --git a/composer.json b/composer.json index 4ace769..21c3f68 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,5 @@ "minimum-stability": "dev", "require": { "drupal/ctools": ">=3.0.0-beta1", - "drupal/panels": ">=4.0.0-alpha1", - "drupal/panels_ipe": ">=4.0.0-alpha1" } } diff --git a/config/schema/flexilayout_builder.schema.yml b/config/schema/flexilayout_builder.schema.yml new file mode 100644 index 0000000..1be1ef9 --- /dev/null +++ b/config/schema/flexilayout_builder.schema.yml @@ -0,0 +1,15 @@ +core.entity_view_display.*.*.*.third_party.panelizer: + type: mapping + label: 'Per-view-mode Flexible Layout Builder settings' + mapping: + static_context: + type: sequence + label: Static context list + sequence: + - type: ctools.context + label: 'Static Context' + relationships: + type: sequence + sequence: + - type: ctools.relationship + label: 'Relationship' diff --git a/config/schema/panelizer.schema.yml b/config/schema/panelizer.schema.yml deleted file mode 100644 index 8b873a9..0000000 --- a/config/schema/panelizer.schema.yml +++ /dev/null @@ -1,33 +0,0 @@ -# Config schema for Panelizer. -panelizer.default.display: - type: display_variant.plugin.panels_variant - label: 'Panelizer display default' - mapping: - static_context: - type: ctools.context - label: 'Contexts' - pattern: - type: string - label: 'Pattern plugin ID' - -core.entity_view_display.*.*.*.third_party.panelizer: - type: mapping - mapping: - enable: - type: boolean - description: 'Is Panelizer enabled?' - custom: - type: boolean - description: 'Are custom overrides allowed?' - allow: - type: boolean - description: 'Is choosing between available defaults during entity creation allowed?' - default: - type: string - description: 'Default display ID' - displays: - type: sequence - description: 'Default displays' - sequence: - type: panelizer.default.display - description: 'Default displays' diff --git a/css/panelizer.admin.css b/css/panelizer.admin.css deleted file mode 100644 index 1aa3794..0000000 --- a/css/panelizer.admin.css +++ /dev/null @@ -1,113 +0,0 @@ -/** - * @file - * Styles for Panelizer wizard admin. - */ - -/* Narrow screens */ - -.panelizer-wizard-tree, -.panelizer-wizard-form { - box-sizing: border-box; -} - -/** - * Wizard actions across the top. - */ -.panelizer-wizard-actions { - text-align: right; /* LTR */ -} -.panelizer-wizard-actions ul.inline, -.panelizer-wizard-actions ul.inline li { - display: inline-block; - margin: 0; -} -.panelizer-wizard-actions ul.inline { - border-top: 1px solid black; - border-left: 1px solid black; -} -.panelizer-wizard-actions ul.inline li { - border-right: 1px solid black; - padding: .5em; -} - -/** - * The tree of wizard steps. - */ -.panelizer-wizard-tree ul { - margin: 0; - padding: 0; - list-style: none; -} -.panelizer-wizard-tree ul > li > ul { - margin-left: 1em; -} -.panelizer-wizard-tree > ul { - border: 1px solid black; - padding-bottom: .5em; - margin-bottom: 20px; -} -.panelizer-wizard-tree li { - border-bottom: 1px solid black; - padding: .5em; - padding-right: 0; -} -.panelizer-wizard-tree li:last-child { - border-bottom: 0; - padding-bottom: 0; -} - -/** - * The wizard form. - */ -.panelizer-wizard-form { - border: 1px solid black; - padding: 1em; - margin-bottom: 20px; -} - -/* Wide screens */ -@media - screen and (min-width: 780px), - (orientation: landscape) and (min-device-height: 780px) { - - /** - * Overall layout. - */ - .panelizer-wizard-tree { - float: left; /* LTR */ - width: 20%; - } - .panelizer-wizard-form { - float: left; /* LTR */ - width: 80%; - } - .panelizer-wizard-form-actions { - margin-left: 20%; /* LTR */ - } - - /** - * Make the borders look nice. - */ - .panelizer-wizard-tree > ul { - border-right: 0; /* LTR */ - } - .panelizer-wizard-form { - min-height: 400px; - } - - /** - * Right-to-left support. - */ - [dir="rtl"] .panelizer-wizard-tree, - [dir="rtl"] .panelizer-wizard-form { - float: right; - } - [dir="rtl"] .panelizer-wizard-form-actions { - margin-left: 0; - margin-right: 20%; - } - [dir="rtl"] .panelizer-wizard-tree > ul { - border-right: 1px solid black; - border-left: 0; - } -} diff --git a/css/panels_ipe.css b/css/panels_ipe.css deleted file mode 100644 index c5e26d2..0000000 --- a/css/panels_ipe.css +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @file - * Stylesheet for Panelizer IPE customizations. - */ - -.panelizer-ipe-save-button { - display: inline-block; - margin: 10px; -} - -.ipe-icon.ipe-icon-revert:before { - /* @todo: Make a new icon for revert! */ - content: "\e90a"; -} - -/* Show the cancel title so it easier to tell the difference from revert. */ -[data-tab-id="cancel"] .ipe-tab-title { - display: inline; -} - diff --git a/js/panelizer-default-form.js b/js/panelizer-default-form.js deleted file mode 100644 index 76aafe8..0000000 --- a/js/panelizer-default-form.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @file - * Javascript for the Panelizer defaults page. - */ -(function ($) { - Drupal.behaviors.panelizer_default_form = { - attach: function (context, settings) { - var $panelizer_checkbox = $(':input[name="panelizer[enable]"]'); - - function update_form() { - var $core_form = $('#field-display-overview-wrapper'); - if ($panelizer_checkbox.is(':checked')) { - $core_form.fadeOut(); - } - else { - $core_form.fadeIn(); - } - } - - $panelizer_checkbox.once('panelizer-default-form').click(update_form); - update_form(); - } - }; -})(jQuery); diff --git a/js/panels_ipe/panels_ipe.js b/js/panels_ipe/panels_ipe.js deleted file mode 100644 index 36af774..0000000 --- a/js/panels_ipe/panels_ipe.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @file - * Entry point for the Panelizer IPE customizations. - */ - -(function ($, _, Backbone, Drupal) { - - 'use strict'; - - Drupal.panelizer = Drupal.panelizer || {}; - - /** - * @namespace - */ - Drupal.panelizer.panels_ipe = {}; - - /** - * Make customizations to the Panels IPE for Panelizer. - */ - Backbone.on('PanelsIPEInitialized', function() { - // Disable the normal save event. - Drupal.panels_ipe.app_view.stopListening(Drupal.panels_ipe.app.get('saveTab'), 'change:active'); - - // Add a new revert tab model. - if (drupalSettings.panelizer.user_permission.revert) { - var revert_tab = new Drupal.panels_ipe.TabModel({title: 'Revert to default', id: 'revert'}); - Drupal.panels_ipe.app_view.tabsView.collection.add(revert_tab); - - // @todo: Put this into a proper view? - Drupal.panels_ipe.app_view.listenTo(revert_tab, 'change:active', function () { - var entity = drupalSettings.panelizer.entity; - if (revert_tab.get('active') && !revert_tab.get('loading')) { - if (confirm(Drupal.t('Are you sure you want to revert to default layout? All your layout changes will be lost for this node.'))) { - // Remove our changes and refresh the page. - revert_tab.set({loading: true}); - $.ajax({ - url: drupalSettings.path.baseUrl + 'admin/panelizer/panels_ipe/' + entity.entity_type_id + '/' + entity.entity_id + '/' + entity.view_mode + '/revert_to_default', - data: {}, - type: 'POST' - }).done(function (data) { - location.reload(); - }); - } - else { - revert_tab.set('active', false, {silent: true}); - } - } - }); - - // Hide the 'Revert to default' button if we're already on a default. - if (drupalSettings.panels_ipe.panels_display.storage_type == 'panelizer_default') { - revert_tab.set({hidden: true}); - } - } - - // Hide the 'Revert to default' button if the user does not have permission. - // if (!drupalSettings.panelizer.user_permission.revert) { - // revert_tab.set({hidden: true}); - // } - - // Add a new view for the save button to the TabsView. - var tabs = { - model: Drupal.panels_ipe.app_view.model, - tabsView: Drupal.panels_ipe.app_view.tabsView, - }; - if (typeof revert_tab !== 'undefined') { - tabs.revertTab = revert_tab; - } - Drupal.panels_ipe.app_view.tabsView.tabViews['save'] = new Drupal.panelizer.panels_ipe.SaveTabView(tabs); - }); - -}(jQuery, _, Backbone, Drupal)); diff --git a/js/panels_ipe/views/SaveTabView.js b/js/panels_ipe/views/SaveTabView.js deleted file mode 100644 index 547c8ea..0000000 --- a/js/panels_ipe/views/SaveTabView.js +++ /dev/null @@ -1,124 +0,0 @@ -/** - * @file - * Contains Drupal.panelizer.panels_ipe.SaveTabView. - */ - -(function ($, _, Backbone, Drupal) { - 'use strict'; - - Drupal.panelizer.panels_ipe.SaveTabView = Backbone.View.extend(/** @lends Drupal.panelizer.panels_ipe.SaveTabView# */{ - - /** - * @type {function} - */ - template: function() { - return ''; - }, - - /** - * @type {Drupal.panels_ipe.AppModel} - */ - model: null, - - /** - * @type {Drupal.panels_ipe.TabsView} - */ - tabsView: null, - - /** - * @type {Drupal.panels_ipe.TabModel} - */ - revertTab: null, - - /** - * @type {object} - */ - events: { - }, - - /** - * @type {function} - */ - onClick: function () { - if (this.model.get('saveTab').get('active')) { - // Always save as a custom override. - this.saveCustom(); - } - }, - - /** - * @type {function} - */ - saveCustom: function () { - this._save('panelizer_field'); - }, - - /** - * @type {function} - */ - _save: function (storage_type) { - var self = this, - layout = this.model.get('layout'); - - // Give the backend enough information to save in the correct way. - layout.set('panelizer_save_as', storage_type); - layout.set('panelizer_entity', drupalSettings.panelizer.entity); - - if (this.model.get('saveTab').get('active')) { - // Save the Layout and disable the tab. - this.model.get('saveTab').set({loading: true, active: false}); - this.tabsView.render(); - layout.save().done(function () { - self.model.get('saveTab').set({loading: false}); - self.model.set('unsaved', false); - - // Change the storage type and id for the next save. - drupalSettings.panels_ipe.panels_display.storage_type = storage_type; - drupalSettings.panels_ipe.panels_display.storage_id = drupalSettings.panelizer.entity[storage_type + '_storage_id']; - Drupal.panels_ipe.setUrlRoot(drupalSettings); - - // Show/hide the revert to default tab. - if (self.revertTab) { - self.revertTab.set({hidden: storage_type === 'panelizer_default'}); - } - self.tabsView.render(); - }); - } - }, - - /** - * @constructs - * - * @augments Backbone.View - * - * @param {Object} options - * An object containing the following keys: - * @param {Drupal.panels_ipe.AppModel} options.model - * The app state model. - * @param {Drupal.panels_ipe.TabsView} options.tabsView - * The app view. - * @param {Drupal.panels_ipe.TabModel} options.revertTab - * The revert tab. - */ - initialize: function (options) { - this.model = options.model; - this.tabsView = options.tabsView; - this.revertTab = options.revertTab; - - this.listenTo(this.model.get('saveTab'), 'change:active', this.onClick); - }, - - /** - * Renders the selection menu for picking Layouts. - * - * @return {Drupal.panelizer.panels_ipe.SaveTabView} - * Return this, for chaining. - */ - render: function () { - this.$el.html(this.template()); - return this; - } - - }); - -}(jQuery, _, Backbone, Drupal)); diff --git a/panelizer.api.php b/panelizer.api.php deleted file mode 100644 index 6d60a6e..0000000 --- a/panelizer.api.php +++ /dev/null @@ -1,30 +0,0 @@ -bundle() == 'page') { - $view_mode = 'my_custom_view_mode'; - } -} - -/** - * @} End of "addtogroup hooks". - */ \ No newline at end of file diff --git a/panelizer.info.yml b/panelizer.info.yml index eb30fde..17bafbb 100644 --- a/panelizer.info.yml +++ b/panelizer.info.yml @@ -1,16 +1,12 @@ type: module name: Panelizer -description: 'Allow any entity view mode to be rendered using a Panels display.' +description: 'Provide panels-like features to layout builder' package: Layout version: VERSION core: 8.x dependencies: # Core. - drupal:layout_discovery - # @todo Break the dependency on Field UI. - - drupal:field_ui + - drupal:layout_builder # Contrib - ctools:ctools_block - - panels:panels - # @todo Allow editing without the IPE. - - panels:panels_ipe diff --git a/panelizer.install b/panelizer.install deleted file mode 100644 index ba3925d..0000000 --- a/panelizer.install +++ /dev/null @@ -1,33 +0,0 @@ -listAll('core.entity_view_display.') as $entity_display_name) { - $entity_display = $config_factory->getEditable($entity_display_name); - if ($displays = $entity_display->get('third_party_settings.panelizer.displays')) { - foreach ($displays as $display_name => $display) { - if (isset($display['layout'])) { - $new_layout = panels_convert_plugin_ids_to_layout_discovery($display['layout']); - if ($new_layout) { - $displays[$display_name]['layout'] = $new_layout; - } - } - } - $entity_display - ->set('third_party_settings.panelizer.displays', $displays) - // Mark the resulting configuration as trusted data. This avoids issues - // with future schema changes. - ->save(TRUE); - } - } -} diff --git a/panelizer.libraries.yml b/panelizer.libraries.yml deleted file mode 100644 index 50b9c76..0000000 --- a/panelizer.libraries.yml +++ /dev/null @@ -1,24 +0,0 @@ -panelizer_default_form: - version: VERSION - js: - js/panelizer-default-form.js: {} - dependencies: - - core/jquery - - core/jquery.once -panels_ipe: - version: VERSION - js: - # Core. - js/panels_ipe/panels_ipe.js: {} - # Views. - js/panels_ipe/views/SaveTabView.js: {} - css: - component: - css/panels_ipe.css: {} - dependencies: - - panels_ipe/panels_ipe -wizard_admin: - version: VERSION - css: - layout: - css/panelizer.admin.css: {} diff --git a/panelizer.links.action.yml b/panelizer.links.action.yml deleted file mode 100644 index 6ed51fe..0000000 --- a/panelizer.links.action.yml +++ /dev/null @@ -1,5 +0,0 @@ -panelizer.default.add: - route_name: panelizer.wizard.add - title: 'Add a new Panelizer default display' - class: \Drupal\panelizer\Menu\AddDefaultLocalAction - deriver: \Drupal\panelizer\Plugin\AddDefaultLinkDeriver diff --git a/panelizer.module b/panelizer.module index badc90d..512ba33 100644 --- a/panelizer.module +++ b/panelizer.module @@ -1,407 +1,167 @@ [ - 'render element' => 'element', - ], - 'panelizer_wizard_form' => [ - 'render element' => 'form', - ], - 'panelizer_wizard_tree' => [ - 'variables' => [ - 'wizard' => NULL, - 'cached_values' => [], - 'tree' => [], - 'divider' => ' ยป ', - 'step' => NULL, - ], - ], - ]; +function panelizer_entity_type_alter(array &$entity_types) { + $entity_types['entity_view_display'] + ->setClass(FlexibleLayoutBuilderEntityViewDisplay::class); } /** - * Implements hook_entity_type_alter(). + * Implements hook_element_info_alter + * + * The pre_render callback added here adds a "Manage Context" link to the interface + * that allows the addition of static contexts and relationships. + * + * @param array $info */ -function panelizer_entity_type_alter(array &$entity_types) { - /** @var \Drupal\panelizer\Plugin\PanelizerEntityManager $panelizer_manager */ - $panelizer_manager = \Drupal::service('plugin.manager.panelizer_entity'); - - // Replace the entity view builder on any entity where we have a Panelizer - // entity plugin and the entity itself has a view builder. - foreach ($panelizer_manager->getDefinitions() as $entity_type_id => $panelizer_info) { - if (isset($entity_types[$entity_type_id]) && $entity_types[$entity_type_id]->hasHandlerClass('view_builder')) { - $entity_types[$entity_type_id]->setHandlerClass('fallback_view_builder', $entity_types[$entity_type_id]->getHandlerClass('view_builder')); - $entity_types[$entity_type_id]->setHandlerClass('view_builder', '\Drupal\panelizer\PanelizerEntityViewBuilder'); - } - } +function panelizer_element_info_alter(array &$info) { + $info['layout_builder']['#pre_render'][] = 'panelizer_layout_builder_context_control_pre_render'; } /** - * Implements hook_panels_build_alter(). + * Implements hook_layout_builder_section_storage(). + * + * We change the default section storage class to make sure we can store layout + * wide third party settings. */ -function panelizer_panels_build_alter(&$build, PanelsDisplayVariant $panels_display) { - $builder = $panels_display->getBuilder(); - $storage_type = $panels_display->getStorageType(); - - $is_panelizer = $builder->getPluginId() == 'ipe' && - in_array($storage_type, ['panelizer_default', 'panelizer_field']) && - isset($build['#attached']) && - isset($build['#attached']['library']) && - in_array('panels_ipe/panels_ipe', $build['#attached']['library']); - - // Add our Javascript customizations for the IPE. - if ($is_panelizer) { - $build['#attached']['library'][] = 'panelizer/panels_ipe'; - - /** @var \Drupal\Core\Entity\EntityInterface $entity */ - $entity = $panels_display->getContexts()['@panelizer.entity_context:entity']->getContextValue(); - $revision_id = ($entity instanceof RevisionableInterface && $entity->getEntityType()->isRevisionable()) && !$entity->isDefaultRevision() ? $entity->getRevisionId() : NULL; - list (,, $view_mode) = explode(':', $panels_display->getStorageId()); - - // Get the default storage id, if we're looking at a panelizer default or - // the panelizer field contains a revision. - if (sizeof(explode(':', $panels_display->getStorageId())) == 4) { - list(, , , $default) = explode(':', $panels_display->getStorageId()); - } - else { - $default = NULL; - } - if ($panels_display->getStorageType() == 'panelizer_field') { - $panelizer_default_storage_id = rtrim(implode(':', [$entity->getEntityTypeId(), $entity->bundle(), $view_mode, $default]), ':'); - } - else { - $panelizer_default_storage_id = $panels_display->getStorageId(); - } - - // Get the special, internal default storage id that includes the entity id, - // which will allow us to correctly set the contexts on the Panels display. - $panelizer_default_internal_storage_id = '*' . rtrim(implode(':', [$entity->getEntityTypeId(), $entity->id(), $view_mode, $default]), ':'); - - // Get the custom storage id (omitting revision id if this is the default - // revision). - $panelizer_field_storage_id_parts = [$entity->getEntityTypeId(), $entity->id(), $view_mode]; - if ($revision_id) { - $panelizer_field_storage_id_parts[] = $revision_id; - } - $panelizer_field_storage_id = implode(':', $panelizer_field_storage_id_parts); - - /** @var \Drupal\panelizer\PanelizerInterface $panelizer */ - $panelizer = \Drupal::service('panelizer'); - - $build['#attached']['drupalSettings']['panelizer']['entity'] = [ - 'entity_type_id' => $entity->getEntityTypeId(), - 'entity_id' => $entity->id(), - 'view_mode' => $view_mode, - 'revision_id' => $revision_id, - 'panelizer_default_storage_id' => $panelizer->hasDefaultPermission('change content', $entity->getEntityTypeId(), $entity->bundle(), $view_mode, $default) ? $panelizer_default_internal_storage_id : FALSE, - 'panelizer_field_storage_id' => $panelizer->hasEntityPermission('change content', $entity, $view_mode) ? $panelizer_field_storage_id : FALSE, - 'panelizer_default_real_storage_id' => $panelizer_default_storage_id, - ]; - - // Whether or not the current user has access to the "revert to default" - // action in the IPE; any user with the 'administer panelizer' will also - // have access. - $build['#attached']['drupalSettings']['panelizer']['user_permission']['revert'] = $panelizer->hasEntityPermission('revert to default', $entity, $view_mode); - - // Whether or not the current user has access to the "set as default" action - // in the IPE; any user with the 'administer panelizer' will also have - // access. - $user = \Drupal::currentUser(); - $build['#attached']['drupalSettings']['panelizer']['user_permission']['save_default'] = $user->hasPermission('set panelizer default') || $user->hasPermission('administer panelizer'); +function panelizer_layout_builder_section_storage_alter(&$definitions) { + $definitions['defaults']->setClass(DefaultsSectionStorage::class); + $definitions['overrides']->setClass(OverridesSectionStorage::class); +} - if ($panels_display->getStorageType() == 'panelizer_field') { - // If using panelizer_field, we change the storage id to match what we put - // together here because it'll have the revision id omitted in the right - // situation. - $build['#attached']['drupalSettings']['panels_ipe']['panels_display']['storage_id'] = $panelizer_field_storage_id; - } - else { - // If using panelizer_default, we need to switch to a the special, - // internal storage id. - $build['#attached']['drupalSettings']['panels_ipe']['panels_display']['storage_id'] = $panelizer_default_internal_storage_id; - } +/** + * Implements hook_builder_module_implements_alter(). + */ +function panelizer_module_implements_alter(&$implementations, $hook) { + if ($hook === 'entity_type_alter') { + // Ensure that this module's implementation of hook_entity_type_alter() runs + // last so that our entity class is used for entity_view_Display entities. + $group = $implementations['panelizer']; + unset($implementations['panelizer']); + $implementations['panelizer'] = $group; } } /** - * Implements hook_panels_ipe_panels_display_presave(). + * Pre render the layout builder interface to add the context controls. */ -function panelizer_panels_ipe_panels_display_presave(PanelsDisplayVariant $panels_display, array $layout_model) { - if (empty($layout_model['panelizer_save_as'])) { - return; +function panelizer_layout_builder_context_control_pre_render($element) { + $section_storage = $element['#section_storage']; + if (!($section_storage instanceof LayoutThirdPartySettingsInterface)) { + return $element; } - // See if the user requested changing the storage type. - $current_storage = $panels_display->getStorageType(); - $panelizer_save_as = $layout_model['panelizer_save_as']; - if ($current_storage !== $panelizer_save_as) { - $panelizer_entity = $layout_model['panelizer_entity']; - - // When actually saving, we want to use the real storage id for me the - // Panelizer default. - $panelizer_entity['panelizer_default_storage_id'] = $panelizer_entity['panelizer_default_real_storage_id']; - - // If we were custom and now we want to save to the default, we need to - // save specially to the Panelizer field so that we can tell it we're on - // a default. - if ($panelizer_save_as == 'panelizer_default') { - /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */ - $entity_type_manager = \Drupal::service("entity_type.manager"); - $storage = $entity_type_manager->getStorage($panelizer_entity['entity_type_id']); - $entity = $storage->load($panelizer_entity['entity_id']); - if ($entity instanceof FieldableEntityInterface) { - /** @var \Drupal\panelizer\PanelizerInterface $panelizer */ - $panelizer = \Drupal::service('panelizer'); - list (,,, $default_name) = explode(':', $panelizer_entity['panelizer_default_storage_id']); - $panelizer->setPanelsDisplay($entity, $panelizer_entity['view_mode'], $default_name); - } - } - - // We need to generate a new UUID if we're creating a custom display. - if ($current_storage == 'panelizer_default' && $panelizer_save_as == 'panelizer_field') { - $configuration = $panels_display->getConfiguration(); - $configuration['uuid'] = \Drupal::service('uuid')->generate(); - $panels_display->setConfiguration($configuration); - } + $element['layout_builder']['#weight'] = 100; + $element['context_controls'] = [ + '#type' => 'container', + '#weight' => -100, + '#attributes' => [ + 'id' => 'layout-builder--context-controls', + 'class' => ['layout-builder--context-controls'], + ], + ]; + $element['context_controls']['manage_context'] = [ + '#type' => 'link', + '#title' => t('Manage Context'), + '#url' => Url::fromRoute('panelizer.view_context', [ + 'section_storage_type' => $section_storage->getStorageType(), + 'section_storage' => $section_storage->getStorageId(), + ]), + '#attributes' => [ + 'class' => ['use-ajax'], + 'data-dialog-type' => 'dialog', + 'data-dialog-renderer' => 'off_canvas', + ], + ]; - // Set the new storage information. - $panels_display->setStorage($panelizer_save_as, $panelizer_entity[$panelizer_save_as . '_storage_id']); - } + return $element; } /** * Implements hook_form_FORM_ID_alter(). */ -function panelizer_form_entity_view_display_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) { - /** @var \Drupal\Core\Entity\EntityFormInterface $form_object */ - $form_object = $form_state->getFormObject(); - /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */ - $display = $form_object->getEntity(); - - /** @var \Drupal\panelizer\Plugin\PanelizerEntityManager $panelizer_manager */ - $panelizer_manager = \Drupal::service('plugin.manager.panelizer_entity'); - /** @var \Drupal\panelizer\PanelizerInterface $panelizer */ - $panelizer = \Drupal::service('panelizer'); - - $entity_type_id = $display->getTargetEntityTypeId(); - $bundle = $display->getTargetBundle(); - $mode = $display->getMode(); - - if ($panelizer_manager->hasDefinition($entity_type_id)) { - $settings = $panelizer->getPanelizerSettings($entity_type_id, $bundle, $mode, $display); - - // Always put the field table below the Panelizer options. - $form['fields']['#weight'] = 10; - - $form['panelizer'] = [ - '#tree' => TRUE, - ]; - $form['panelizer']['enable'] = [ - '#type' => 'checkbox', - '#title' => t('Panelize this view mode'), - '#default_value' => isset($settings['enable']) ? $settings['enable'] : FALSE, - ]; - - $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id); - - $form['panelizer']['options'] = [ - '#type' => 'details', - '#open' => TRUE, - '#title' => t('Panelizer options'), - '#states' => [ - 'visible' => [ - '#edit-panelizer-enable' => ['checked' => TRUE], - ], - ], - '#parents' => ['panelizer'], - ]; - - $form['panelizer']['options']['custom'] = [ - '#type' => 'checkbox', - '#title' => t('Allow each @entity to have its display customized', [ - '@entity' => $entity_type->getSingularLabel(), - ]), - '#default_value' => isset($settings['custom']) ? $settings['custom'] : FALSE, - ]; - $form['panelizer']['options']['allow'] = [ - '#type' => 'checkbox', - '#title' => t('Allow users to select which display to use'), - '#description' => t('When multiple default displays are available for a view mode it can be useful to allow content creators to choose which display to use for an individual @entity.', [ - '@entity' => $entity_type->getSingularLabel(), - ]) . '
' . t('To enable the choice to be enabled by users, make sure the panelizer field is visible in the Manage form display'), - '#default_value' => isset($settings['allow']) ? $settings['allow'] : FALSE, - ]; - - // If this display mode is panelized, then show the available displays in a - // table. - if (!empty($settings['enable'])) { - $form['#cache']['tags'][] = "panelizer_default:$entity_type_id:$bundle:$mode"; - $form['panelizer']['displays'] = [ - '#type' => 'table', - '#caption' => t('Default displays available for this view mode'), - '#header' => [t('Name'), t('Use as default'), t('Operations')], - ]; - foreach ($display->getThirdPartySetting('panelizer', 'displays', []) as $machine_name => $panels_display) { - // Reset operations when in the foreach loop. - $operations = []; - $display_name = $machine_name; - $machine_name ="{$entity_type_id}__{$bundle}__{$mode}__$machine_name"; - $operations['edit'] = [ - 'title' => t('Edit'), - 'url' => Url::fromRoute('panelizer.wizard.edit', ['machine_name' => $machine_name]), - ]; - if ($settings['default'] != $display_name) { - $operations['set_default'] = [ - 'title' => t('Use as default'), - 'url' => Url::fromRoute('panelizer.default.select', ['machine_name' => $machine_name]), - ]; - $operations['delete'] = [ - 'title' => t('Delete'), - 'url' => Url::fromRoute('panelizer.default.delete', ['machine_name' => $machine_name]), - ]; - } - $form['panelizer']['displays'][$machine_name] = [ - 'label' => ['#markup' => $panels_display['label']], - 'default' => ['#markup' => $settings['default'] == $display_name ? '✓' : ''], - 'operations' => [ - 'data' => [ - '#type' => 'operations', - '#links' => $operations, - ] - ] - ]; - } - $form['fields']['#access'] = FALSE; - } - - $form['#attached']['library'][] = 'panelizer/panelizer_default_form'; - $form['actions']['submit']['#submit'][] = 'panelizer_form_entity_view_display_edit_submit'; - } -} - -function panelizer_form_entity_view_display_edit_submit(&$form, FormStateInterface $form_state) { - $rebuild = FALSE; - /** @var \Drupal\Core\Entity\EntityFormInterface $form_object */ - $form_object = $form_state->getFormObject(); - /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */ - $display = $form_object->getEntity(); - /** @var \Drupal\panelizer\Plugin\PanelizerEntityManager $panelizer_manager */ - $panelizer_manager = \Drupal::service('plugin.manager.panelizer_entity'); - /** @var \Drupal\panelizer\Panelizer $panelizer */ - $panelizer = \Drupal::service('panelizer'); - - if ($panelizer_manager->hasDefinition($display->getTargetEntityTypeId())) { - $settings = $panelizer->getPanelizerSettings($display->getTargetEntityTypeId(), $display->getTargetBundle(), $display->getMode(), $display); - if ($settings['enable'] != $form_state->getValue(['panelizer', 'enable'])) { - $rebuild = TRUE; - } - $settings['enable'] = $form_state->getValue(['panelizer', 'enable']); - $settings['custom'] = $form_state->getValue(['panelizer', 'custom']); - $settings['allow'] = $form_state->getValue(['panelizer', 'allow']); - $panelizer->setPanelizerSettings($display->getTargetEntityTypeId(), $display->getTargetBundle(), $display->getMode(), $settings, $display); - if ($rebuild) { - \Drupal::service('router.builder')->rebuild(); - /** @var \Drupal\Core\Menu\LocalActionManager $local_action_manager */ - $local_action_manager = \Drupal::service('plugin.manager.menu.local_action'); - $local_action_manager->clearCachedDefinitions(); - // Manually reinitialize these. - $local_action_manager->getDefinitions(); - \Drupal::service('cache.render')->invalidateAll(); - } - } +function panelizer_form_layout_builder_add_block_alter(array &$form, FormStateInterface $form_state) { + /** @var \Drupal\layout_builder\SectionStorageInterface $section_storage */ + list($section_storage, $delta, $region, $plugin_id) = $form_state->getBuildInfo()['args']; + $form_state->set('section_storage', $section_storage); + $form_state->set('flb__component', $form_state->get('layout_builder__component')); + $form_state->set('flb__uuid', $form_state->get('layout_builder__component')->getUuid()); + $form_state->set('flb__delta', $delta); + + _panelizer_layout_builder_block_form_alter($form, $form_state); } /** - * Implements hook_form_FORM_ID_alter() for field_ui_field_storage_add_form(). + * Implements hook_form_FORM_ID_alter(). */ -function panelizer_form_field_ui_field_storage_add_form_alter(&$form, FormStateInterface $form_state, $form_id) { - // Hide the legacy Panelizer field. - // @todo Remove the Panelizer field entirely. - if (isset($form['add']['new_storage_type']['#options']['General']['panelizer'])) { - unset($form['add']['new_storage_type']['#options']['General']['panelizer']); - } +function panelizer_form_layout_builder_update_block_alter(array &$form, FormStateInterface $form_state) { + /** @var \Drupal\layout_builder\SectionStorageInterface $section_storage */ + list($section_storage, $delta, $region, $uuid) = $form_state->getBuildInfo()['args']; + $form_state->set('section_storage', $section_storage); + $form_state->set('flb__component', $section_storage->getSection($delta)->getComponent($uuid)); + $form_state->set('flb__uuid', $uuid); + $form_state->set('flb__delta', $delta); + + _panelizer_layout_builder_block_form_alter($form, $form_state); } /** - * Preprocess function for panelizer-wizard-tree.html.twig. + * Add magical extra things to the block form. + * + * Currently supported magic: + * - block_classes + * - field_label_overrides + * + * Still to implement: + * - Visibility rules + * - Output styles */ -function template_preprocess_panelizer_wizard_tree(&$variables) { - /** @var $wizard \Drupal\ctools\Wizard\FormWizardInterface|\Drupal\ctools\Wizard\EntityFormWizardInterface */ - $wizard = $variables['wizard']; - $cached_values = $variables['cached_values']; - $tree = $variables['tree']; - $variables['step'] = $wizard->getStep($cached_values); - - foreach ($wizard->getOperations($cached_values) as $step => $operation) { - $parameters = $wizard->getNextParameters($cached_values); - // Override step to be the step we want. - $parameters['step'] = $step; +function _panelizer_layout_builder_block_form_alter(array &$form, FormStateInterface $form_state) { + $form['actions']['#weight'] = 1000; + $form['panelizer'] = [ + '#type' => 'container', + ]; - // Fill in parents if there are breadcrumbs. - $parent =& $tree; - if (isset($operation['breadcrumbs'])) { - foreach ($operation['breadcrumbs'] as $breadcrumb) { - $breadcrumb_string = (string) $breadcrumb; - if (!isset($parent[$breadcrumb_string])) { - $parent[$breadcrumb_string] = [ - 'title' => $breadcrumb, - 'children' => [], - ]; - } - $parent =& $parent[$breadcrumb_string]['children']; - } - } + $class = $form_state->get('flb__component') ? $form_state->get('flb__component')->get('class') : ''; + $form['panelizer']['class'] = [ + '#type' => 'textfield', + '#title' => t('Block Classes'), + '#default_value' => $class, + ]; - $parent[$step] = [ - 'title' => !empty($operation['title']) ? $operation['title'] : '', - 'url' => new Url($wizard->getRouteName(), $parameters), - 'step' => $step, + /** @var \Drupal\layout_builder\SectionComponent $component */ + if (($component = $form_state->get('flb__component')) && ($component->getPlugin() instanceof EntityField || $component->getPlugin() instanceof FieldBlock)) { + $form['panelizer']['field_label_override'] = [ + '#type' => 'checkbox', + '#title' => t('Use Block Title as Field Label'), + '#default_value' => $component->get('field_label_override'), ]; } - $variables['tree'] = $tree; + array_unshift($form['#submit'], '_panelizer_layout_builder_block_form_submit'); } /** - * Preprocess function for panelizer-view-mode.html.twig - * - * Prepare variables for Panelizer view mode templates. + * Submit. */ -function template_preprocess_panelizer_view_mode(&$variables) { - $element = $variables['element']; - - // Copy values into the variables. - /** @var \Drupal\panelizer\Plugin\PanelizerEntityInterface $panelizer_plugin */ - $panelizer_plugin = $variables['panelizer_plugin'] = $element['#panelizer_plugin']; - /** @var \Drupal\panels\Plugin\DisplayVariant\PanelsDisplayVariant $panels_display */ - $panels_display = $variables['panels_display'] = $element['#panels_display']; - /** @var \Drupal\Core\Entity\EntityInterface $entity */ - $entity = $variables['entity'] = $element['#entity']; - $view_mode = $variables['view_mode'] = $element['#view_mode']; - $variables['content'] = $element['content']; - $variables['title'] = isset($element['#title']) ? $element['#title'] : ''; - - // Setup the defaults. - $variables['title_element'] = 'h2'; - $variables['entity_url'] = $entity->toUrl('canonical', [ - 'language' => $entity->language(), - ]); - - // Allow the Panelizer entity plugin to do additional preprocessing. - $panelizer_plugin->preprocessViewMode($variables, $entity, $panels_display, $view_mode); +function _panelizer_layout_builder_block_form_submit(array $form, FormStateInterface $form_state) { + $values = $form_state->getValue('panelizer'); + $component = $form_state->get('section_storage') + ->getSection($form_state->get('flb__delta')) + ->getComponent($form_state->get('flb__uuid')); + + $component->set('class', $values['class']); + $component->set('field_label_override', $values['field_label_override']); } - diff --git a/panelizer.permissions.yml b/panelizer.permissions.yml deleted file mode 100644 index 1fa7b0b..0000000 --- a/panelizer.permissions.yml +++ /dev/null @@ -1,8 +0,0 @@ -administer panelizer: - title: 'Administer Panelizer' - description: 'Fully administer Panelizer and all Panelizer settings.' -set panelizer default: - title: 'Set Panelizer Default' - description: 'Allow user to "Set as default" in the Panelizer IPE.' -permission_callbacks: - - panelizer:getPermissions diff --git a/panelizer.routing.yml b/panelizer.routing.yml index 5baf178..2fb58ca 100644 --- a/panelizer.routing.yml +++ b/panelizer.routing.yml @@ -1,89 +1,85 @@ -# Panels IPE -panelizer.panels_ipe.revert_to_default: - path: '/admin/panelizer/panels_ipe/{entity_type_id}/{entity}/{view_mode}/revert_to_default' - options: - parameters: - entity: - type: entity:{entity_type_id} - defaults: - _controller: '\Drupal\panelizer\Controller\PanelizerPanelsIPEController::revertToDefault' - requirements: - _method: 'POST' - _permission: 'access panels in-place editing' - _custom_access: '\Drupal\panelizer\Controller\PanelizerPanelsIPEController::accessRevertToDefault' - -# Wizard -panelizer.wizard.add: - path: '/admin/structure/panelizer/add/{entity_type_id}/{bundle}/{view_mode_name}' - defaults: - _wizard: '\Drupal\panelizer\Wizard\PanelizerAddWizard' - _title: 'Panelizer Wizard' - tempstore_id: 'panelizer.wizard' - requirements: - _panelizer_default_access: 'TRUE' - _permission: 'administer panelizer' - -panelizer.wizard.add.step: - path: '/admin/structure/panelizer/add/{machine_name}/{step}' +panelizer.view_context: + path: '/layout_builder/view/context/{section_storage_type}/{section_storage}' defaults: - _wizard: '\Drupal\panelizer\Wizard\PanelizerAddWizard' - _title: 'Panelizer Wizard' - tempstore_id: 'panelizer.wizard' + _controller: '\Drupal\panelizer\Controller\ViewContextController::build' requirements: - _permission: 'administer panelizer' - -panelizer.wizard.edit: - path: '/admin/structure/panelizer/edit/{machine_name}/{step}' + _permission: 'configure any layout' + _layout_builder_access: 'view' + options: + _admin_route: TRUE + parameters: + section_storage: + layout_builder_tempstore: TRUE +panelizer.add_static_context: + path: '/layout_builder/add/static_context/{section_storage_type}/{section_storage}/{data_type}' defaults: - _wizard: '\Drupal\panelizer\Wizard\PanelizerEditWizard' - _title: 'Panelizer Wizard' - tempstore_id: 'panelizer.wizard' - step: 'general' + _form: '\Drupal\panelizer\Form\AddStaticContext' requirements: - _permission: 'administer panelizer' - -panelizer.default.delete: - path: '/admin/structure/panelizer/delete/{machine_name}' + _permission: 'configure any layout' + _layout_builder_access: 'view' + options: + _admin_route: TRUE + parameters: + section_storage: + layout_builder_tempstore: TRUE +panelizer.choose_static_context: + path: '/layout_builder/choose/static_context/{section_storage_type}/{section_storage}' defaults: - _form: '\Drupal\panelizer\Form\PanelizerDefaultDelete' - _title: 'Delete panelizer default' + _controller: '\Drupal\panelizer\Controller\ChooseStaticContextController::build' requirements: - _panelizer_field_ui_view_mode_access: 'TRUE' - _custom_access: '\Drupal\panelizer\Access\PanelizerDefaultsDisplayAccess::isNotDefaultDisplay' - -panelizer.default.select: - path: '/admin/structure/panelizer/set_default/{machine_name}' + _permission: 'configure any layout' + _layout_builder_access: 'view' + options: + _admin_route: TRUE + parameters: + section_storage: + layout_builder_tempstore: TRUE +panelizer.edit_static_context: + path: '/layout_builder/edit/static_context/{section_storage_type}/{section_storage}/{machine_name}' defaults: - _form: '\Drupal\panelizer\Form\PanelizerDefaultSelect' - _title: 'Set as default' + _form: '\Drupal\panelizer\Form\EditStaticContext' requirements: - _panelizer_field_ui_view_mode_access: 'TRUE' - _custom_access: '\Drupal\panelizer\Access\PanelizerDefaultsDisplayAccess::isNotDefaultDisplay' + _permission: 'configure any layout' + _layout_builder_access: 'view' + options: + _admin_route: TRUE + parameters: + section_storage: + layout_builder_tempstore: TRUE -# Contexts -panelizer.wizard.step.context.add: - path: '/admin/panelizer/wizard/{machine_name}/contexts/add/{context_id}' +panelizer.add_relationship: + path: '/layout_builder/add/relationship/{section_storage_type}/{section_storage}/{plugin}' defaults: - _form: '\Drupal\panelizer\Form\PanelizerWizardContextConfigure' - _title: 'Add custom context' - tempstore_id: 'panelizer.wizard' + _form: '\Drupal\panelizer\Form\AddRelationship' requirements: - _permission: 'administer panelizer' - -panelizer.wizard.step.context.edit: - path: '/admin/panelizer/wizard/{machine_name}/contexts/edit/{context_id}' + _permission: 'configure any layout' + _layout_builder_access: 'view' + options: + _admin_route: TRUE + parameters: + section_storage: + layout_builder_tempstore: TRUE +panelizer.choose_relationship: + path: '/layout_builder/choose/relationship/{section_storage_type}/{section_storage}' defaults: - _form: '\Drupal\panelizer\Form\PanelizerWizardContextConfigure' - _title: 'Edit context' - tempstore_id: 'panelizer.wizard' + _controller: '\Drupal\panelizer\Controller\ChooseRelationshipController::build' requirements: - _permission: 'administer panelizer' - -panelizer.wizard.step.context.delete: - path: '/admin/panelizer/wizard/{machine_name}/context/delete/{context_id}' + _permission: 'configure any layout' + _layout_builder_access: 'view' + options: + _admin_route: TRUE + parameters: + section_storage: + layout_builder_tempstore: TRUE +panelizer.edit_relationship: + path: '/layout_builder/edit/relationship/{section_storage_type}/{section_storage}/{machine_name}' defaults: - _form: '\Drupal\panelizer\Form\PanelizerWizardContextDeleteForm' - _title: 'Delete static context' - tempstore_id: 'panelizer.wizard' + _form: '\Drupal\panelizer\Form\EditRelationship' requirements: - _permission: 'administer panelizer' + _permission: 'configure any layout' + _layout_builder_access: 'view' + options: + _admin_route: TRUE + parameters: + section_storage: + layout_builder_tempstore: TRUE diff --git a/panelizer.services.yml b/panelizer.services.yml index d36b86c..2f65af2 100644 --- a/panelizer.services.yml +++ b/panelizer.services.yml @@ -1,17 +1,6 @@ services: - access_check.panelizer.view_mode: - class: Drupal\panelizer\Access\ViewModeAccessCheck - arguments: ['@access_check.field_ui.view_mode'] + panelizer.render_block_component_subscriber: + class: Drupal\panelizer\EventSubscriber\BlockComponentRenderArray + arguments: ['@current_user'] tags: - - { name: access_check, applies_to: _panelizer_field_ui_view_mode_access } - plugin.manager.panelizer_entity: - class: Drupal\panelizer\Plugin\PanelizerEntityManager - parent: default_plugin_manager - panelizer: - class: Drupal\panelizer\Panelizer - arguments: ['@entity_type.manager', '@entity_type.bundle.info', '@entity_field.manager', '@plugin.manager.field.field_type', '@module_handler', '@current_user', '@plugin.manager.panelizer_entity', '@panels.display_manager', '@string_translation', '@ctools.context_mapper'] - panelizer.default.access: - class: Drupal\panelizer\Access\DefaultAccess - arguments: ['@panelizer'] - tags: - - { name: access_check, applies_to: _panelizer_default_access } + - { name: event_subscriber } diff --git a/panelizer_quickedit/README.txt b/panelizer_quickedit/README.txt deleted file mode 100644 index 284bb68..0000000 --- a/panelizer_quickedit/README.txt +++ /dev/null @@ -1,5 +0,0 @@ -Panelizer Quick Edit ---------- -This module contains customizations which allow Panelized Entities to be edited -inline with Quick Edit. It is separate from the Panelizer project as not all -users need this functionality. diff --git a/panelizer_quickedit/panelizer_quickedit.info.yml b/panelizer_quickedit/panelizer_quickedit.info.yml deleted file mode 100644 index a4e194d..0000000 --- a/panelizer_quickedit/panelizer_quickedit.info.yml +++ /dev/null @@ -1,9 +0,0 @@ -type: module -name: Panelizer Quick Edit -description: 'Enables Quick Edit to function normally when using Panelizer.' -package: Layout -version: VERSION -core: 8.x -dependencies: - - panelizer:panelizer - - drupal:quickedit diff --git a/panelizer_quickedit/panelizer_quickedit.module b/panelizer_quickedit/panelizer_quickedit.module deleted file mode 100644 index 44c19f0..0000000 --- a/panelizer_quickedit/panelizer_quickedit.module +++ /dev/null @@ -1,59 +0,0 @@ -getTranslationFromContext($entity, $langcode); - - // Grab the information required to re-render the entity_field block. - $temp = str_replace('panelizer-', '', $view_mode_id); - list($view_mode, $block_id) = explode('-block-id-', $temp); - - // Load the Panelizer display. - /** @var \Drupal\panelizer\PanelizerInterface $panelizer */ - $panelizer = \Drupal::service('panelizer'); - $display = $panelizer->getPanelsDisplay($entity, $view_mode); - - /** @var \Drupal\ctools_block\Plugin\Block\EntityField $plugin */ - $plugin = $display->getBlock($block_id); - - // Set the appropriate Entity context and build the plugin. - $plugin->setContextValue('entity', $entity); - $build = $plugin->build(); - - // Add our custom field view-mode in case the user wants to edit again. - $build['field']['#view_mode'] = $view_mode_id; - - return $build; -} - -/** - * Implements hook_entity_view_alter(). - */ -function panelizer_quickedit_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) { - // Explicitly add support for ctools_block by attaching a custom view-mode to - // every Block that's about to be rendered. - if (isset($build['#panels_display'])) { - // We only support known Display Builders. - $supported_plugins = ['ipe', 'standard']; - if (in_array($build['#panels_display']->getBuilder()->getPluginId(), $supported_plugins)) { - $region_names = Element::getVisibleChildren($build['content']); - foreach ($region_names as $region_name) { - $block_ids = Element::getVisibleChildren($build['content'][$region_name]); - foreach ($block_ids as $block_id) { - $block = &$build['content'][$region_name][$block_id]; - if (isset($block['#base_plugin_id']) && $block['#base_plugin_id'] === 'entity_field') { - $view_mode = 'panelizer-' . $build['#view_mode'] . '-block-id-' . $block_id; - $block['content']['field']['#view_mode'] = $view_mode; - } - } - } - } - } -} diff --git a/panelizer_quickedit/tests/src/FunctionalJavascript/PanelizerQuickEditTest.php b/panelizer_quickedit/tests/src/FunctionalJavascript/PanelizerQuickEditTest.php deleted file mode 100644 index 005668a..0000000 --- a/panelizer_quickedit/tests/src/FunctionalJavascript/PanelizerQuickEditTest.php +++ /dev/null @@ -1,159 +0,0 @@ -drupalCreateContentType(['type' => 'page', 'name' => 'Page']); - - // Add a plain text field for this content type. - FieldStorageConfig::create([ - 'field_name' => 'test_field', - 'entity_type' => 'node', - 'type' => 'string', - ])->save(); - - FieldConfig::create([ - 'field_name' => 'test_field', - 'label' => 'Test Field', - 'entity_type' => 'node', - 'bundle' => 'page', - 'required' => FALSE, - 'settings' => [], - 'description' => '', - ])->save(); - - /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $entity_form_display */ - $entity_form_display = \Drupal::entityTypeManager() - ->getStorage('entity_form_display') - ->load('node.page.default'); - $entity_form_display->setComponent('test_field')->save(); - - /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $entity_display */ - $entity_display = \Drupal::entityTypeManager() - ->getStorage('entity_view_display') - ->load('node.page.default'); - $entity_display->setComponent('test_field')->save(); - - // Create a privileged user. - $user = $this->drupalCreateUser([ - 'access contextual links', - 'access in-place editing', - 'access content', - 'administer node display', - 'administer panelizer', - 'create page content', - 'edit any page content', - ]); - $this->drupalLogin($user); - - // Enable Panelizer for Articles. - $this->drupalGet('admin/structure/types/manage/page/display'); - $this->assertResponse(200); - $edit = [ - 'panelizer[enable]' => TRUE, - ]; - $this->drupalPostForm(NULL, $edit, t('Save')); - $this->assertResponse(200); - } - - /** - * Tests Quick Editing a Panelized Node. - */ - public function testPanelizerQuickEdit() { - /** @var \Drupal\panelizer\PanelizerInterface $panelizer */ - $panelizer = \Drupal::service('panelizer'); - $displays = $panelizer->getDefaultPanelsDisplays('node', 'page', 'default'); - $display = $displays['default']; - - // Find the "test_field" block. - $block_id = FALSE; - foreach ($display->getConfiguration()['blocks'] as $block) { - if ($block['id'] === 'entity_field:node:test_field') { - $block_id = $block['uuid']; - } - } - - // Make sure we found a valid UUID. - $this->assertNotFalse($block_id); - - // Create an Article. - $node = $this->drupalCreateNode([ - 'type' => 'page', - 'test_field' => [ - 'value' => 'Change me', - ], - ]); - - // Visit the new node. - $this->drupalGet('node/' . $node->id()); - $this->assertResponse(200); - - // This is the unique ID we append to normal Quick Edit field IDs. - $panelizer_id = 'panelizer-full-block-id-' . $block_id; - - // Assemble common CSS selectors. - $entity_selector = '[data-quickedit-entity-id="node/' . $node->id() . '"]'; - $field_selector = '[data-quickedit-field-id="node/' . $node->id() . '/test_field/' . $node->language()->getId() . '/' . $panelizer_id . '"]'; - - // Wait until Quick Edit loads. - $condition = "jQuery('" . $entity_selector . " .quickedit').length > 0"; - $this->assertJsCondition($condition, 10000); - - // Initiate Quick Editing. - $this->triggerClick($entity_selector . ' [data-contextual-id] > button'); - $this->click($entity_selector . ' [data-contextual-id] .quickedit > a'); - $this->triggerClick($field_selector); - $this->assertSession()->assertWaitOnAjaxRequest(); - - // Trigger an edit with Javascript (this is a "contenteditable" element). - $this->getSession()->executeScript("jQuery('" . $field_selector . "').text('Hello world').trigger('keyup');"); - - // To prevent 403s on save, we re-set our request (cookie) state. - $this->prepareRequest(); - - // Save the change. - $this->triggerClick('.quickedit-button.action-save'); - $this->assertSession()->assertWaitOnAjaxRequest(); - - // Re-visit the node to make sure the edit worked. - $this->drupalGet('node/' . $node->id()); - $this->assertResponse(200); - $this->assertSession()->pageTextContains('Hello world'); - } - - /** - * Clicks the element with the given CSS selector using event triggers. - * - * @todo Remove when https://github.com/jcalderonzumba/gastonjs/issues/19 - * is fixed. Currently clicking anchors/buttons with nested elements is not - * possible. - * - * @param string $css_selector - * The CSS selector identifying the element to click. - */ - protected function triggerClick($css_selector) { - $this->getSession()->executeScript("jQuery('" . $css_selector . "')[0].click()"); - } - -} diff --git a/src/Access/DefaultAccess.php b/src/Access/DefaultAccess.php deleted file mode 100644 index 45af311..0000000 --- a/src/Access/DefaultAccess.php +++ /dev/null @@ -1,48 +0,0 @@ -panelizer = $panelizer; - } - - /** - * Determines access to a default Panelizer layout. - * - * @param string $entity_type_id - * The panelized entity type ID. - * @param string $bundle - * The panelized bundle ID. - * @param string $view_mode_name - * The panelized view mode ID. - * - * @return \Drupal\Core\Access\AccessResult - */ - public function access($entity_type_id, $bundle, $view_mode_name) { - $settings = $this->panelizer->getPanelizerSettings($entity_type_id, $bundle, $view_mode_name); - return $settings['enable'] ? AccessResult::allowed() : AccessResult::forbidden(); - } - -} diff --git a/src/Access/PanelizerDefaultsDisplayAccess.php b/src/Access/PanelizerDefaultsDisplayAccess.php deleted file mode 100644 index 1e76a9b..0000000 --- a/src/Access/PanelizerDefaultsDisplayAccess.php +++ /dev/null @@ -1,34 +0,0 @@ -getPanelizerSettings($entity_type, $bundle, $view_mode); - if ($settings['default'] != $default) { - $access = AccessResult::allowed(); - } - else { - $access = AccessResult::forbidden(); - } - return $access->addCacheTags(["panelizer_default:$entity_type:$bundle:$view_mode", "panelizer_default:$entity_type:$bundle:$view_mode:$default"]); - } - -} diff --git a/src/Access/PanelizerUIAccess.php b/src/Access/PanelizerUIAccess.php deleted file mode 100644 index a58bdec..0000000 --- a/src/Access/PanelizerUIAccess.php +++ /dev/null @@ -1,18 +0,0 @@ -hasPermission('administer panelizer') ? AccessResult::allowed() : AccessResult::forbidden(); - } - -} diff --git a/src/Access/ViewModeAccessCheck.php b/src/Access/ViewModeAccessCheck.php deleted file mode 100644 index bb28110..0000000 --- a/src/Access/ViewModeAccessCheck.php +++ /dev/null @@ -1,61 +0,0 @@ -accessCheck = $access_check; - } - - /** - * Adapt the panelizer defaults access check to correspond to field ui. - * - * @param \Symfony\Component\Routing\Route $route - * The original route definition. - * @param \Drupal\Core\Routing\RouteMatchInterface $route_match - * The route matched. - * @param \Drupal\Core\Session\AccountInterface $account - * The current user's account. - * @param string $machine_name - * The machine name of the panelizer default. - * - * @return \Drupal\Core\Access\AccessResultInterface - * @throws \Exception - */ - public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account, $machine_name) { - $parts = explode('__', $machine_name); - if (count($parts) != 4) { - throw new \Exception('The provided machine_name is not well formed.'); - } - list($entity_type_id, $bundle, $view_mode) = $parts; - $defaults = [ - 'entity_type_id' => $entity_type_id, - ] + $route->getDefaults(); - $route->setDefaults($defaults); - $route->setRequirement('_field_ui_view_mode_access', 'administer ' . $entity_type_id . ' display'); - return $this->accessCheck->access($route, $route_match, $account, $view_mode, $bundle); - } - -} diff --git a/src/Annotation/PanelizerEntity.php b/src/Annotation/PanelizerEntity.php deleted file mode 100644 index 1452143..0000000 --- a/src/Annotation/PanelizerEntity.php +++ /dev/null @@ -1,18 +0,0 @@ -get('plugin.manager.ctools.relationship') + ); + } + + /** + * ChooseStaticContextController constructor. + * + * @param \Drupal\ctools\Plugin\RelationshipManagerInterface $relationship_manager + */ + public function __construct(RelationshipManagerInterface $relationship_manager) { + $this->relationshipManager = $relationship_manager; + } + + /** + * Provides the UI for choosing a new static context. + * + * @param \Drupal\layout_builder\SectionStorageInterface $section_storage + * The section storage. + * + * @return array + * A render array. + */ + public function build(SectionStorageInterface $section_storage) { + $build['#title'] = $this->t('Choose a relationship'); + $build['#type'] = 'container'; + + $definitions = $this->relationshipManager->getDefinitionsForContexts($this->getAvailableContexts($section_storage)); + foreach ($definitions as $plugin_id => $definition) { + list($category,) = explode(':', $plugin_id, 2); + if ($category == $plugin_id) { + if (!empty($definition['deriver'])) { + continue; + } + else { + $category = 'other'; + } + } + + if (!isset($definitions[$category])) { + $category = 'other'; + } + + if (empty($build[$category])) { + $build[$category] = [ + '#type' => 'details', + '#title' => isset($definitions[$category]) ? $definitions[$category]['label'] : new TranslatableMarkup('Other'), + 'links' => [ + '#theme' => 'links', + ] + ]; + } + + $link = [ + 'title' => $definition['label'], + 'url' => Url::fromRoute('panelizer.add_relationship', [ + 'section_storage_type' => $section_storage->getStorageType(), + 'section_storage' => $section_storage->getStorageId(), + 'plugin' => $plugin_id, + ]), + ]; + if ($this->isAjax()) { + $link['attributes']['class'][] = 'use-ajax'; + $link['attributes']['data-dialog-type'][] = 'dialog'; + $link['attributes']['data-dialog-renderer'][] = 'off_canvas'; + } + $build[$category]['links']['#links'][] = $link; + } + + if (isset($build['other'])) { + $build['other']['#weight'] = 100; + } + + return $build; + } +} diff --git a/src/Controller/ChooseStaticContextController.php b/src/Controller/ChooseStaticContextController.php new file mode 100644 index 0000000..fa4f7cf --- /dev/null +++ b/src/Controller/ChooseStaticContextController.php @@ -0,0 +1,102 @@ +get('typed_data_manager') + ); + } + + /** + * ChooseStaticContextController constructor. + * + * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager + */ + public function __construct(TypedDataManagerInterface $typed_data_manager) { + $this->typedDataManager = $typed_data_manager; + } + + /** + * Provides the UI for choosing a new static context. + * + * @param \Drupal\layout_builder\SectionStorageInterface $section_storage + * The section storage. + * + * @return array + * A render array. + */ + public function build(SectionStorageInterface $section_storage) { + $build['#title'] = $this->t('Choose a data type'); + $build['#type'] = 'container'; + + $definitions = $this->typedDataManager->getDefinitions(); + foreach ($definitions as $data_type => $definition) { + list($category,) = explode(':', $data_type, 2); + if ($category == $data_type) { + if (!empty($definition['deriver'])) { + continue; + } + else { + $category = 'other'; + } + } + + if (!isset($definitions[$category])) { + $category = 'other'; + } + + if (empty($build[$category])) { + $build[$category] = [ + '#type' => 'details', + '#title' => isset($definitions[$category]) ? $definitions[$category]['label'] : new TranslatableMarkup('Other'), + 'links' => [ + '#theme' => 'links', + ] + ]; + } + + $link = [ + 'title' => $definition['label'], + 'url' => Url::fromRoute('panelizer.add_static_context', [ + 'section_storage_type' => $section_storage->getStorageType(), + 'section_storage' => $section_storage->getStorageId(), + 'data_type' => $data_type, + ]), + ]; + if ($this->isAjax()) { + $link['attributes']['class'][] = 'use-ajax'; + $link['attributes']['data-dialog-type'][] = 'dialog'; + $link['attributes']['data-dialog-renderer'][] = 'off_canvas'; + } + $build[$category]['links']['#links'][] = $link; + } + + if (isset($build['other'])) { + $build['other']['#weight'] = 100; + } + + return $build; + } +} diff --git a/src/Controller/PanelizerPanelsIPEController.php b/src/Controller/PanelizerPanelsIPEController.php deleted file mode 100644 index 6e14bc2..0000000 --- a/src/Controller/PanelizerPanelsIPEController.php +++ /dev/null @@ -1,99 +0,0 @@ -panelizer = $panelizer; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('panelizer') - ); - } - - /** - * Reverts an entity view mode to a particular named default. - * - * @param \Drupal\Core\Entity\FieldableEntityInterface $entity - * The entity. - * @param string $view_mode - * The view mode. - * - * @return \Symfony\Component\HttpFoundation\Response - * An empty response. - * - * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException - */ - public function revertToDefault(FieldableEntityInterface $entity, $view_mode) { - // Get the bundle specific default display as a fallback. - $settings = $this->panelizer->getPanelizerSettings($entity->getEntityTypeId(), $entity->bundle(), $view_mode); - $default = $settings['default']; - // Check the entity for a documented default to which we should revert. - if ($entity->hasField('panelizer') && $entity->panelizer->first()) { - foreach ($entity->panelizer as $item) { - if ($item->view_mode == $view_mode && !empty($item->default)) { - $default = $item->default; - break; - } - } - } - // If we somehow ended up not having a default, throw an exception. - if (empty($default)) { - throw new BadRequestHttpException("Default name to revert to must be passed!"); - } - $this->panelizer->setPanelsDisplay($entity, $view_mode, $default); - return new Response(); - } - - /** - * Custom access checker for reverting an entity view mode to a named default. - * - * @param \Drupal\Core\Entity\FieldableEntityInterface $entity - * The entity. - * @param string $view_mode - * The view mode. - * @param \Drupal\Core\Session\AccountInterface $account - * The user account. - * - * @return \Drupal\Core\Access\AccessResultInterface - * The access result. - */ - public function accessRevertToDefault(FieldableEntityInterface $entity, $view_mode, AccountInterface $account) { - return AccessResult::allowedIf($this->panelizer->hasEntityPermission('revert to default', $entity, $view_mode, $account)); - } - -} \ No newline at end of file diff --git a/src/Controller/ViewContextController.php b/src/Controller/ViewContextController.php new file mode 100644 index 0000000..f57edd5 --- /dev/null +++ b/src/Controller/ViewContextController.php @@ -0,0 +1,219 @@ +contextRepository = $context_repository; + } + + /** + * Provides the UI for listing contexts. + * + * @param \Drupal\layout_builder\SectionStorageInterface $section_storage + * The section storage. + * + * @return array + * A render array. + */ + public function build(DefaultsSectionStorageInterface $section_storage) { + $build['#title'] = $this->t('Available Contexts'); + $build['#type'] = 'container'; + + $build['provided'] = [ + '#type' => 'details', + '#title' => new TranslatableMarkup('Provided Contexts'), + 'table' => [ + '#type' => 'table', + '#header' => [ + new TranslatableMarkup('Context'), + new TranslatableMarkup('Name'), + new TranslatableMarkup('Type'), + ], + ], + ]; + + $provided_contexts = $this->getAvailableContexts($section_storage); + $static_contexts = $section_storage->getThirdPartySetting('panelizer', 'static_context') ?: []; + $relationships = $section_storage->getThirdPartySetting('panelizer', 'relationships') ?: []; + $provided_contexts = array_diff_key($provided_contexts, $static_contexts, $relationships); + foreach ($provided_contexts as $machine_name => $context) { + /** @var \Drupal\Core\Plugin\Context\ContextInterface $context */ + $row = [ + 'context' => [ + '#type' => 'markup', + '#markup' => $context->getContextDefinition()->getLabel(), + ], + 'name' => [ + '#type' => 'markup', + '#markup' => $machine_name, + ], + 'type' => [ + '#type' => 'markup', + '#markup' => $context->getContextDefinition()->getDataType(), + ], + ]; + $build['provided']['table'][$machine_name] = $row; + } + + $build['static'] = [ + '#type' => 'details', + '#title' => new TranslatableMarkup('Static Context'), + 'add_button' => [ + '#type' => 'link', + '#title' => new TranslatableMarkup('Add Static Context'), + '#url' => Url::fromRoute('panelizer.choose_static_context', [ + 'section_storage_type' => $section_storage->getStorageType(), + 'section_storage' => $section_storage->getStorageId(), + ]) + ], + 'table' => [ + '#type' => 'table', + '#header' => [ + new TranslatableMarkup('Context'), + new TranslatableMarkup('Name'), + new TranslatableMarkup('Type'), + new TranslatableMarkup('Operations'), + ], + ], + ]; + if ($this->isAjax()) { + $build['static']['add_button']['#attributes']['class'][] = 'use-ajax'; + $build['static']['add_button']['#attributes']['data-dialog-type'][] = 'dialog'; + $build['static']['add_button']['#attributes']['data-dialog-renderer'][] = 'off_canvas'; + } + foreach ($static_contexts as $machine_name => $context) { + $links = []; + $links['edit'] = [ + 'title' => new TranslatableMarkup('Edit'), + 'url' => Url::fromRoute('panelizer.edit_static_context', [ + 'section_storage_type' => $section_storage->getStorageType(), + 'section_storage' => $section_storage->getStorageId(), + 'machine_name' => $machine_name, + ]), + ]; + + if ($this->isAjax()) { + $links['edit']['attributes']['class'][] = 'use-ajax'; + $links['edit']['attributes']['data-dialog-type'][] = 'dialog'; + $links['edit']['attributes']['data-dialog-renderer'][] = 'off_canvas'; + } + + $row = [ + 'context' => [ + '#markup' => $context['label'], + ], + 'name' => [ + '#markup' => $machine_name, + ], + 'type' => [ + '#markup' => $context['type'], + ], + 'operations' => [ + '#type' => 'operations', + '#links' => $links, + ] + ]; + $build['static']['table'][$machine_name] = $row; + } + + $build['relationships'] = [ + '#type' => 'details', + '#title' => new TranslatableMarkup('Related Context'), + 'add_button' => [ + '#type' => 'link', + '#title' => new TranslatableMarkup('Add Related Context'), + '#url' => Url::fromRoute('panelizer.choose_relationship', [ + 'section_storage_type' => $section_storage->getStorageType(), + 'section_storage' => $section_storage->getStorageId(), + ]) + ], + 'table' => [ + '#type' => 'table', + '#header' => [ + new TranslatableMarkup('Relationship'), + new TranslatableMarkup('Name'), + new TranslatableMarkup('Type'), + new TranslatableMarkup('Operations'), + ], + ], + ]; + if ($this->isAjax()) { + $build['relationships']['add_button']['#attributes']['class'][] = 'use-ajax'; + $build['relationships']['add_button']['#attributes']['data-dialog-type'][] = 'dialog'; + $build['relationships']['add_button']['#attributes']['data-dialog-renderer'][] = 'off_canvas'; + } + foreach ($relationships as $machine_name => $context) { + $links = []; + $links['edit'] = [ + 'title' => new TranslatableMarkup('Edit'), + 'url' => Url::fromRoute('panelizer.edit_relationship', [ + 'section_storage_type' => $section_storage->getStorageType(), + 'section_storage' => $section_storage->getStorageId(), + 'machine_name' => $machine_name, + ]), + ]; + + if ($this->isAjax()) { + $links['edit']['attributes']['class'][] = 'use-ajax'; + $links['edit']['attributes']['data-dialog-type'][] = 'dialog'; + $links['edit']['attributes']['data-dialog-renderer'][] = 'off_canvas'; + } + + $row = [ + 'context' => [ + '#markup' => $context['label'], + ], + 'name' => [ + '#markup' => $machine_name, + ], + 'type' => [ + '#markup' => $context['type'], + ], + 'operations' => [ + '#type' => 'operations', + '#links' => $links, + ] + ]; + $build['relationships']['table'][$machine_name] = $row; + } + + // @todo: Relationships + + return $build; + } + + /** + * Instantiates a new instance of this class. + * + * This is a factory method that returns a new instance of this class. The + * factory should pass any needed dependencies into the constructor of this + * class, but not the container itself. Every call to this method must return + * a new instance of this class; that is, it may not implement a singleton. + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The service container this instance should use. + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('context.repository') + ); + } +} diff --git a/src/Entity/PanelizerLayoutBuilderEntityViewDisplay.php b/src/Entity/PanelizerLayoutBuilderEntityViewDisplay.php new file mode 100644 index 0000000..f798678 --- /dev/null +++ b/src/Entity/PanelizerLayoutBuilderEntityViewDisplay.php @@ -0,0 +1,97 @@ +getContextValues( + $this->getThirdPartySetting('panelizer', 'static_context', []) + ); + + /** @var \Drupal\ctools\Plugin\RelationshipManager $relationship_manager */ + $relationship_manager = \Drupal::service('plugin.manager.ctools.relationship'); + /** @var \Drupal\Core\Plugin\Context\ContextHandler $context_handler */ + $context_handler = \Drupal::service('context.handler'); + + foreach ($this->getThirdPartySetting('panelizer', 'relationships', []) as $machine_name => $relationship) { + /** @var \Drupal\ctools\Plugin\RelationshipInterface $plugin */ + $plugin = $relationship_manager->createInstance($relationship['plugin'], $relationship['settings'] ?: []); + $context_handler->applyContextMapping($plugin, $contexts); + + $contexts[$machine_name] = $plugin->getRelationship(); + } + + return $contexts; + } + + /** + * {@inheritdoc} + */ + protected function addSectionField($entity_type_id, $bundle, $field_name) { + parent::addSectionField($entity_type_id, $bundle, $field_name); + + // Add the layout settings field. + $settings_field_name = OverridesSectionStorage::SETTINGS_FIELD_NAME; + $field = FieldConfig::loadByName($entity_type_id, $bundle, $settings_field_name); + if (!$field) { + $field_storage = FieldStorageConfig::loadByName($entity_type_id, $settings_field_name); + if (!$field_storage) { + $field_storage = FieldStorageConfig::create([ + 'entity_type' => $entity_type_id, + 'field_name' => $settings_field_name, + 'type' => 'layout_settings', + 'locked' => TRUE, + ]); + $field_storage->setTranslatable(FALSE); + $field_storage->save(); + } + + $field = FieldConfig::create([ + 'field_storage' => $field_storage, + 'bundle' => $bundle, + 'label' => t('Layout Settings'), + ]); + $field->setTranslatable(FALSE); + $field->save(); + } + } + + /** + * {@inheritdoc} + */ + protected function removeSectionField($entity_type_id, $bundle, $field_name) { + $query = $this->entityTypeManager()->getStorage($this->getEntityTypeId())->getQuery() + ->condition('targetEntityType', $this->getTargetEntityTypeId()) + ->condition('bundle', $this->getTargetBundle()) + ->condition('mode', $this->getMode(), '<>') + ->condition('third_party_settings.layout_builder.allow_custom', TRUE); + $enabled = (bool) $query->count()->execute(); + if (!$enabled && $field = FieldConfig::loadByName($entity_type_id, $bundle, $field_name)) { + $field->delete(); + } + if (!$enabled && $field = FieldConfig::loadByName($entity_type_id, $bundle, OverridesSectionStorage::SETTINGS_FIELD_NAME)) { + $field->delete(); + } + } + +} diff --git a/src/EventSubscriber/BlockComponentRenderArray.php b/src/EventSubscriber/BlockComponentRenderArray.php new file mode 100644 index 0000000..1278cd0 --- /dev/null +++ b/src/EventSubscriber/BlockComponentRenderArray.php @@ -0,0 +1,76 @@ +currentUser = $current_user; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[LayoutBuilderEvents::SECTION_COMPONENT_BUILD_RENDER_ARRAY] = ['onBuildRender', 0]; + return $events; + } + + /** + * Builds render arrays for block plugins and sets it on the event. + * + * @param \Drupal\layout_builder\Event\SectionComponentBuildRenderArrayEvent $event + * The section component render event. + */ + public function onBuildRender(SectionComponentBuildRenderArrayEvent $event) { + $block = $event->getPlugin(); + if (!$block instanceof BlockPluginInterface) { + return; + } + + $component = $event->getComponent(); + $build = $event->getBuild(); + if ($component->get('class')) { + foreach (explode(' ', $component->get('class')) as $class) { + $build['#attributes']['class'][] = $class; + } + } + + if ($block instanceof EntityField || $block instanceof FieldBlock) { + if ($component->get('field_label_override')) { + $build['content']['field']['#title'] = $component->get('configuration')['label']; + } + } + $event->setBuild($build); + } + +} diff --git a/src/Exception/PanelizerException.php b/src/Exception/PanelizerException.php deleted file mode 100644 index 3bd76d9..0000000 --- a/src/Exception/PanelizerException.php +++ /dev/null @@ -1,9 +0,0 @@ -get('class_resolver'), + $container->get('layout_builder.tempstore_repository'), + $container->get('plugin.manager.ctools.relationship'), + $container->get('plugin_form.factory') + ); + } + + /** + * AddStaticContext constructor. + * + * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver + */ + public function __construct(ClassResolverInterface $class_resolver, LayoutTempstoreRepositoryInterface $layout_tempstore_repository, RelationshipManagerInterface $relationship_manager, PluginFormFactoryInterface $plugin_form_factory) { + $this->classResolver = $class_resolver; + $this->layoutTempstoreRepository = $layout_tempstore_repository; + $this->relationshipManager = $relationship_manager; + $this->pluginFormFactory = $plugin_form_factory; + } + + /** + * Returns a unique string identifying the form. + * + * The returned ID should be a unique string that can be a valid PHP function + * name, since it's used in hook implementation names such as + * hook_form_FORM_ID_alter(). + * + * @return string + * The unique string identifying the form. + */ + public function getFormId() { + return 'add_static_context_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, DefaultsSectionStorageInterface $section_storage = NULL, $plugin = NULL) { + return $this->doBuildForm($form, $form_state, $section_storage, 'add', $plugin); + } + + /** + * Form constructor. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * The form structure. + */ + public function doBuildForm(array $form, FormStateInterface $form_state, DefaultsSectionStorageInterface $section_storage = NULL, $op = 'edit', $plugin = NULL) { + $this->sectionStorage = $section_storage; + + if ($op == 'edit') { + $relationships = $this->sectionStorage->getThirdPartySetting('panelizer', 'relationships'); + $relationship_config = $relationships[$plugin]; + $machine_name = $plugin; + $plugin = $relationship_config['plugin']; + $this->relationship = $this->relationshipManager->createInstance($relationship_config['plugin'], $relationship_config['settings'] ?: []); + } + else { + $this->relationship = $this->relationshipManager->createInstance($plugin); + $machine_name = NULL; + } + + + $form['#tree'] = TRUE; + $form['plugin'] = [ + '#type' => 'value', + '#value' => $plugin, + ]; + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Label'), + '#required' => TRUE, + '#default_value' => isset($relationship_config['label']) ? $relationship_config['label'] : '', + ]; + $form['machine_name'] = [ + '#type' => 'machine_name', + '#title' => $this->t('Machine Name'), + '#required' => TRUE, + '#maxlength' => 128, + '#default_value' => $machine_name, + '#disabled' => !empty($machine_name), + '#machine_name' => [ + 'source' => ['label'], + 'exists' => [$this, 'contextExists'], + ], + ]; + $form['description'] = [ + '#type' => 'textarea', + '#title' => $this->t('Description'), + '#default_value' => isset($relationship_config['description']) ? $relationship_config['description'] : '', + ]; + + if ($this->relationship instanceof ConfigurableRelationshipInterface) { + $subform_state = SubformState::createForSubform($form['settings'], $form, $form_state); + $form['settings'] = []; + $form['settings'] = $this->pluginFormFactory->createInstance($this->relationship, 'configure')->buildConfigurationForm($form['settings'], $subform_state); + } + else { + $form['settings'] = [ + '#type' => 'container', + 'context_mapping' => $this->addContextAssignmentElement($this->relationship, $this->getAvailableContexts($this->sectionStorage)), + ]; + } + + $form['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Add Relationship'), + '#button_type' => 'primary', + ]; + if ($this->isAjax()) { + $form['submit']['#ajax']['callback'] = '::ajaxSubmit'; + } + + return $form; + } + + /** + * @param $value + * @param array $element + * @param \Drupal\Core\Form\FormStateInterface $form_state + * + * @return bool + */ + public function contextExists($value, array $element, FormStateInterface $form_state) { + $static_context = $this->sectionStorage->getThirdPartySetting('panelizer','relationships'); + return !empty($static_context[$value]); + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + if ($this->relationship instanceof ConfigurableRelationshipInterface) { + $subform_state = SubformState::createForSubform($form['settings'], $form, + $form_state); + $this->pluginFormFactory + ->createInstance($this->relationship, 'configure') + ->validateConfigurationForm($form['settings'], $subform_state); + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + if ($this->relationship instanceof ConfigurableRelationshipInterface) { + // Call the plugin submit handler. + $subform_state = SubformState::createForSubform( + $form['settings'], + $form, + $form_state + ); + $this->pluginFormFactory->createInstance($this->relationship, 'configure') + ->submitConfigurationForm( + $form, + $subform_state + ); + $this->relationship->setContextMapping($subform_state->getValue('context_mapping', [])); + + $configuration = $this->relationship->getConfiguration(); + } + else { + $configuration = $form_state->getValue('settings'); + } + + $values = $form_state->getValues(); + $relationships = $this->sectionStorage->getThirdPartySetting('panelizer', 'relationships'); + $relationships[$values['machine_name']] = [ + 'plugin' => $values['plugin'], + 'label' => $values['label'], + 'description' => $values['description'], + 'settings' => $configuration, + ]; + $this->sectionStorage->setThirdPartySetting('panelizer','relationships', $relationships); + + $this->layoutTempstoreRepository->set($this->sectionStorage); + $form_state->setRedirectUrl($this->sectionStorage->getLayoutBuilderUrl()); + } + + /** + * Allows the form to respond to a successful AJAX submission. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * An AJAX response. + */ + protected function successfulAjaxSubmit(array $form, FormStateInterface $form_state) { + return $this->rebuildAndClose($this->sectionStorage); + } +} diff --git a/src/Form/AddStaticContext.php b/src/Form/AddStaticContext.php new file mode 100644 index 0000000..86557d1 --- /dev/null +++ b/src/Form/AddStaticContext.php @@ -0,0 +1,190 @@ +get('class_resolver'), + $container->get('layout_builder.tempstore_repository') + ); + } + + /** + * AddStaticContext constructor. + * + * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver + */ + public function __construct(ClassResolverInterface $class_resolver, LayoutTempstoreRepositoryInterface $layout_tempstore_repository) { + $this->classResolver = $class_resolver; + $this->layoutTempstoreRepository = $layout_tempstore_repository; + } + + /** + * Returns a unique string identifying the form. + * + * The returned ID should be a unique string that can be a valid PHP function + * name, since it's used in hook implementation names such as + * hook_form_FORM_ID_alter(). + * + * @return string + * The unique string identifying the form. + */ + public function getFormId() { + return 'add_static_context_form'; + } + + /** + * Form constructor. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * The form structure. + */ + public function buildForm(array $form, FormStateInterface $form_state, DefaultsSectionStorageInterface $section_storage = NULL, $data_type = NULL) { + $this->sectionStorage = $section_storage; + + $form['data_type'] = [ + '#type' => 'value', + '#value' => $data_type, + ]; + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Label'), + '#required' => TRUE, + ]; + $form['machine_name'] = [ + '#type' => 'machine_name', + '#title' => $this->t('Machine Name'), + '#required' => TRUE, + '#maxlength' => 128, + '#machine_name' => [ + 'source' => ['label'], + 'exists' => [$this, 'contextExists'], + ], + ]; + $form['description'] = [ + '#type' => 'textarea', + '#title' => $this->t('Description'), + ]; + if (strpos($data_type, 'entity:') === 0) { + list(, $entity_type) = explode(':', $data_type); + /** @var EntityAdapter $entity */ + $form['context_value'] = [ + '#type' => 'entity_autocomplete', + '#required' => TRUE, + '#target_type' => $entity_type, + '#title' => $this->t('Select entity'), + ]; + } + else { + $form['context_value'] = [ + '#title' => $this->t('Set a context value'), + '#type' => 'textfield', + '#required' => TRUE, + ]; + } + $form['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Add Context'), + '#button_type' => 'primary', + '#submit' => [ + '::submitForm', + ], + ]; + if ($this->isAjax()) { + $form['submit']['#ajax']['callback'] = '::ajaxSubmit'; + } + + return $form; + } + + /** + * @param $value + * @param array $element + * @param \Drupal\Core\Form\FormStateInterface $form_state + * + * @return bool + */ + public function contextExists($value, array $element, FormStateInterface $form_state) { + $static_context = $this->sectionStorage->getThirdPartySetting('panelizer', 'static_context'); + return !empty($static_context[$value]); + } + + /** + * Form submission handler. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $values = $form_state->getValues(); + $static_contexts = $this->sectionStorage->getThirdPartySetting('panelizer', 'static_context'); + $static_contexts[$values['machine_name']] = [ + 'type' => $values['data_type'], + 'label' => $values['label'], + 'description' => $values['description'], + 'value' => $values['context_value'], + ]; + $this->sectionStorage->setThirdPartySetting('panelizer', 'static_context', $static_contexts); + + dpm($this->sectionStorage); + dpm($this->sectionStorage->getThirdPartySettings('panelizer')); + + $this->layoutTempstoreRepository->set($this->sectionStorage); + $form_state->setRedirectUrl($this->sectionStorage->getLayoutBuilderUrl()); + } + + /** + * Allows the form to respond to a successful AJAX submission. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * An AJAX response. + */ + protected function successfulAjaxSubmit(array $form, FormStateInterface $form_state) { + return $this->rebuildAndClose($this->sectionStorage); + } +} diff --git a/src/Form/EditRelationship.php b/src/Form/EditRelationship.php new file mode 100644 index 0000000..0c50761 --- /dev/null +++ b/src/Form/EditRelationship.php @@ -0,0 +1,18 @@ +doBuildForm($form, $form_state, $section_storage, 'edit', $machine_name); + } + +} diff --git a/src/Form/EditStaticContext.php b/src/Form/EditStaticContext.php new file mode 100644 index 0000000..b77db02 --- /dev/null +++ b/src/Form/EditStaticContext.php @@ -0,0 +1,199 @@ +get('class_resolver'), + $container->get('layout_builder.tempstore_repository') + ); + } + + /** + * AddStaticContext constructor. + * + * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver + */ + public function __construct(ClassResolverInterface $class_resolver, LayoutTempstoreRepositoryInterface $layout_tempstore_repository) { + $this->classResolver = $class_resolver; + $this->layoutTempstoreRepository = $layout_tempstore_repository; + } + + /** + * Returns a unique string identifying the form. + * + * The returned ID should be a unique string that can be a valid PHP function + * name, since it's used in hook implementation names such as + * hook_form_FORM_ID_alter(). + * + * @return string + * The unique string identifying the form. + */ + public function getFormId() { + return 'edit_static_context_form'; + } + + /** + * Form constructor. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * The form structure. + */ + public function buildForm(array $form, FormStateInterface $form_state, DefaultsSectionStorageInterface $section_storage = NULL, $machine_name = NULL) { + $this->sectionStorage = $section_storage; + $static_contexts = $this->sectionStorage->getThirdPartySetting('panelizer', 'static_context'); + $context = $static_contexts[$machine_name]; + $data_type = $context['type']; + + $form['data_type'] = [ + '#type' => 'value', + '#value' => $data_type, + ]; + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Label'), + '#required' => TRUE, + '#default_value' => $context['label'], + ]; + $form['machine_name'] = [ + '#type' => 'machine_name', + '#title' => $this->t('Machine Name'), + '#required' => TRUE, + '#default_value' => $machine_name, + '#disabled' => TRUE, + '#maxlength' => 128, + '#machine_name' => [ + 'source' => ['label'], + 'exists' => [$this, 'contextExists'], + ], + ]; + $form['description'] = [ + '#type' => 'textarea', + '#title' => $this->t('Description'), + '#default_value' => $context['description'], + ]; + if (strpos($data_type, 'entity:') === 0) { + list(, $entity_type) = explode(':', $data_type); + $context_object = new EntityLazyLoadContext( + new ContextDefinition($data_type, $context['label']), + \Drupal::service('entity.repository'), + $context['value'] + ); + /** @var EntityAdapter $entity */ + $form['context_value'] = [ + '#type' => 'entity_autocomplete', + '#required' => TRUE, + '#target_type' => $entity_type, + '#default_value' => $context_object->getContextValue(), + '#title' => $this->t('Select entity'), + ]; + } + else { + $form['context_value'] = [ + '#title' => $this->t('Set a context value'), + '#type' => 'textfield', + '#required' => TRUE, + ]; + } + $form['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Save Context'), + '#button_type' => 'primary', + ]; + if ($this->isAjax()) { + $form['submit']['#ajax']['callback'] = '::ajaxSubmit'; + } + + return $form; + } + + /** + * @param $value + * @param array $element + * @param \Drupal\Core\Form\FormStateInterface $form_state + * + * @return bool + */ + public function contextExists($value, array $element, FormStateInterface $form_state) { + $static_context = $this->sectionStorage->getThirdPartySetting('panelizer', 'static_context'); + return !empty($static_context[$value]); + } + + /** + * Form submission handler. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $values = $form_state->getValues(); + $static_contexts = $this->sectionStorage->getThirdPartySetting('panelizer', 'static_context'); + $static_contexts[$values['machine_name']] = [ + 'type' => $values['data_type'], + 'label' => $values['label'], + 'description' => $values['description'], + 'value' => $values['context_value'], + ]; + $this->sectionStorage->setThirdPartySetting('panelizer', 'static_context', $static_contexts); + + $this->layoutTempstoreRepository->set($this->sectionStorage); + } + + /** + * Allows the form to respond to a successful AJAX submission. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * An AJAX response. + */ + protected function successfulAjaxSubmit(array $form, FormStateInterface $form_state) { + return $this->rebuildAndClose($this->sectionStorage); + } +} diff --git a/src/Form/PanelizerDefaultDelete.php b/src/Form/PanelizerDefaultDelete.php deleted file mode 100644 index fb96cc5..0000000 --- a/src/Form/PanelizerDefaultDelete.php +++ /dev/null @@ -1,170 +0,0 @@ -entityTypeManager = $entity_type_manager; - $this->panelizer = $panelizer; - $this->panelsDisplayManager = $panels_display_manager; - $this->invalidator = $invalidator; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('entity_type.manager'), - $container->get('panelizer'), - $container->get('panels.display_manager'), - $container->get('cache_tags.invalidator') - ); - } - - /** - * {@inheritdoc} - */ - public function getQuestion() { - return 'Are you certain you want to delete this panelizer default?.'; - } - - /** - * {@inheritdoc} - */ - public function getCancelUrl() { - $bundle_entity_type = $this->entityTypeManager->getDefinition($this->entityTypeId)->getBundleEntityType(); - if ($this->viewMode == 'default') { - $route = "entity.entity_view_display.{$this->entityTypeId}.default"; - $arguments = [ - $bundle_entity_type => $this->bundle, - ]; - } - else { - $route = "entity.entity_view_display.{$this->entityTypeId}.view_mode"; - $arguments = [ - $bundle_entity_type => $this->bundle, - 'view_mode_name' => $this->viewMode, - ]; - } - return new Url($route, $arguments); - } - - /** - * {@inheritdoc} - */ - public function getFormId() { - return 'panelizer_default_delete'; - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state, $machine_name = NULL) { - list ( - $this->entityTypeId, - $this->bundle, - $this->viewMode, - $this->displayId - ) = explode('__', $machine_name); - - return parent::buildForm($form, $form_state); - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - $display = $this->panelizer->getEntityViewDisplay($this->entityTypeId, $this->bundle, $this->viewMode); - $displays = $this->panelizer->getDefaultPanelsDisplays($this->entityTypeId, $this->bundle, $this->viewMode, $display); - unset($displays[$this->displayId]); - foreach ($displays as $key => $value) { - $displays[$key] = $this->panelsDisplayManager->exportDisplay($value); - } - $display->setThirdPartySetting('panelizer', 'displays', $displays); - $display->save(); - $form_state->setRedirectUrl($this->getCancelUrl()); - $tag = "panelizer_default:{$this->entityTypeId}:{$this->bundle}:{$this->viewMode}:{$this->displayId}"; - $this->invalidator->invalidateTags([$tag]); - } - -} diff --git a/src/Form/PanelizerDefaultSelect.php b/src/Form/PanelizerDefaultSelect.php deleted file mode 100644 index e1c98f1..0000000 --- a/src/Form/PanelizerDefaultSelect.php +++ /dev/null @@ -1,142 +0,0 @@ -panelizer = $panelizer; - $this->invalidator = $invalidator; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('panelizer'), - $container->get('cache_tags.invalidator') - ); - } - - /** - * {@inheritdoc} - */ - public function getQuestion() { - return 'Are you certain you want to set this panelizer default as the default for this bundle?.'; - } - - /** - * {@inheritdoc} - */ - public function getCancelUrl() { - $bundle_entity_type = \Drupal::entityTypeManager()->getDefinition($this->entityTypeId)->getBundleEntityType(); - if ($this->viewMode == 'default') { - $route = "entity.entity_view_display.{$this->entityTypeId}.default"; - $arguments = [ - $bundle_entity_type => $this->bundle, - ]; - } - else { - $route = "entity.entity_view_display.{$this->entityTypeId}.view_mode"; - $arguments = [ - $bundle_entity_type => $this->bundle, - 'view_mode_name' => $this->viewMode, - ]; - } - return new Url($route, $arguments); - } - - /** - * {@inheritdoc} - */ - public function getFormId() { - return 'panelizer_default_delete'; - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state, $machine_name = NULL) { - list ( - $this->entityTypeId, - $this->bundle, - $this->viewMode, - $this->displayId - ) = explode('__', $machine_name); - - return parent::buildForm($form, $form_state); - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - $display = $this->panelizer->getEntityViewDisplay($this->entityTypeId, $this->bundle, $this->viewMode); - $settings = $this->panelizer->getPanelizerSettings($this->entityTypeId, $this->bundle, $this->viewMode, $display); - $settings['default'] = $this->displayId; - $this->panelizer->setPanelizerSettings($this->entityTypeId, $this->bundle, $this->viewMode, $settings, $display); - $form_state->setRedirectUrl($this->getCancelUrl()); - $tag = "panelizer_default:{$this->entityTypeId}:{$this->bundle}:{$this->viewMode}"; - $this->invalidator->invalidateTags([$tag]); - } - -} diff --git a/src/Form/PanelizerWizardContextConfigure.php b/src/Form/PanelizerWizardContextConfigure.php deleted file mode 100644 index a7ab050..0000000 --- a/src/Form/PanelizerWizardContextConfigure.php +++ /dev/null @@ -1,92 +0,0 @@ -contextMapper = $context_mapper; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('user.shared_tempstore'), - $container->get('ctools.context_mapper') - ); - } - - /** - * {@inheritdoc} - */ - protected function getParentRouteInfo($cached_values) { - return ['panelizer.wizard.add.step', [ - 'machine_name' => $cached_values['id'], - 'step' => 'contexts', - ]]; - } - - /** - * {@inheritdoc} - */ - protected function getContexts($cached_values) { - $static_contexts = isset($cached_values['contexts']) ? $cached_values['contexts'] : []; - $static_contexts = $this->contextMapper->getContextValues($static_contexts); - return $static_contexts; - } - - /** - * {@inheritdoc} - */ - protected function addContext($cached_values, $context_id, ContextInterface $context) { - $cached_values['contexts'][$context_id] = [ - 'label' => $context->getContextDefinition()->getLabel(), - 'type' => $context->getContextDefinition()->getDataType(), - 'description' => $context->getContextDefinition()->getDescription(), - 'value' => strpos($context->getContextDefinition()->getDataType(), 'entity:') === 0 ? $context->getContextValue()->uuid() : $context->getContextValue(), - ]; - return $cached_values; - } - - /** - * {@inheritdoc} - */ - public function contextExists($value, $element, $form_state) { - return FALSE; - } - - /** - * {@inheritdoc} - */ - protected function disableMachineName($cached_values, $machine_name) { - return !empty($cached_values['contexts'][$machine_name]); - } - -} diff --git a/src/Form/PanelizerWizardContextDeleteForm.php b/src/Form/PanelizerWizardContextDeleteForm.php deleted file mode 100644 index e4175e0..0000000 --- a/src/Form/PanelizerWizardContextDeleteForm.php +++ /dev/null @@ -1,54 +0,0 @@ -getTempstore(); - $context = $cached_values['contexts'][$this->context_id]; - return $this->t('Are you sure you want to delete the context @label?', ['@label' => $context['label']]); - } - - /** - * {@inheritdoc} - */ - public function getCancelUrl() { - $cached_values = $this->getTempstore(); - - return new Url('panelizer.wizard.add.step', [ - 'machine_name' => $cached_values['id'], - 'step' => 'contexts', - ]); - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - $cached_values = $this->getTempstore(); - $context = $cached_values['contexts'][$this->context_id]; - drupal_set_message($this->t('The static context %label has been removed.', ['%label' => $context['label']])); - unset($cached_values['contexts'][$this->context_id]); - $this->setTempstore($cached_values); - parent::submitForm($form, $form_state); - } - -} diff --git a/src/Form/PanelizerWizardContextForm.php b/src/Form/PanelizerWizardContextForm.php deleted file mode 100644 index 44b6da7..0000000 --- a/src/Form/PanelizerWizardContextForm.php +++ /dev/null @@ -1,150 +0,0 @@ -get('typed_data_manager'), - $container->get('form_builder'), - $container->get('user.shared_tempstore') - ); - } - - /** - * ManageContext constructor. - * - * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager - * The typed data manager. - * @param \Drupal\Core\Form\FormBuilderInterface $form_builder - * The form builder. - * @param \Drupal\user\SharedTempStoreFactory $tempstore_factory - * Shared user tempstore factory. - */ - public function __construct(TypedDataManagerInterface $typed_data_manager, FormBuilderInterface $form_builder, SharedTempStoreFactory $tempstore_factory) { - parent::__construct($typed_data_manager, $form_builder); - $this->tempstoreFactory = $tempstore_factory; - } - - /** - * {@inheritdoc} - */ - public function getFormId() { - return 'panelizer_wizard_context_form'; - } - - /** - * {@inheritdoc} - */ - protected function getContextClass($cached_values) { - return PanelizerWizardContextConfigure::class; - } - - /** - * {@inheritdoc} - */ - protected function getRelationshipClass($cached_values) {} - - /** - * {@inheritdoc} - */ - protected function getContextAddRoute($cached_values) { - return 'panelizer.wizard.step.context.add'; - } - - /** - * {@inheritdoc} - */ - protected function getRelationshipAddRoute($cached_values) { - return 'panelizer.wizard.step.context.add'; - } - - /** - * {@inheritdoc} - */ - protected function getContexts($cached_values) { - return $cached_values['plugin']->getPattern()->getDefaultContexts($this->tempstoreFactory, $this->getTempstoreId(), $this->machine_name); - } - - /** - * {@inheritdoc} - */ - protected function getTempstoreId() { - return 'panelizer.wizard'; - } - - /** - * {@inheritdoc} - */ - protected function getContextOperationsRouteInfo($cached_values, $machine_name, $row) { - return ['panelizer.wizard.step.context', [ - 'machine_name' => $machine_name, - 'context_id' => $row, - ]]; - } - - /** - * {@inheritdoc} - */ - protected function getRelationshipOperationsRouteInfo($cached_values, $machine_name, $row) { - return ['panelizer.wizard.step.context', [ - 'machine_name' => $machine_name, - 'context_id' => $row, - ]]; - } - - /** - * {@inheritdoc} - */ - protected function isEditableContext($cached_values, $row) { - if (!isset($cached_values['contexts'][$row])) { - return FALSE; - } - $context = $cached_values['contexts'][$row]; - return !empty($context['value']); - } - - /** - * {@inheritdoc} - */ - public function addContext(array &$form, FormStateInterface $form_state) { - $cached_values = $form_state->getTemporaryValue('wizard'); - $context = $form_state->getValue('context'); - $content = $this->formBuilder->getForm($this->getContextClass($cached_values), $context, $this->getTempstoreId(), $this->machine_name); - $content['#attached']['library'][] = 'core/drupal.dialog.ajax'; - list(, $route_parameters) = $this->getContextOperationsRouteInfo($cached_values, $this->machine_name, $context); - $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $this->url($this->getContextAddRoute($cached_values), $route_parameters, ['query' => [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]]); - $response = new AjaxResponse(); - $response->addCommand(new OpenModalDialogCommand($this->t('Add new context'), $content, ['width' => '700'])); - return $response; - } - -} diff --git a/src/Form/PanelizerWizardGeneralForm.php b/src/Form/PanelizerWizardGeneralForm.php deleted file mode 100644 index dcb84a9..0000000 --- a/src/Form/PanelizerWizardGeneralForm.php +++ /dev/null @@ -1,154 +0,0 @@ -routeMatch = $route_match; - - if ($route_match->getRouteName() == 'panelizer.wizard.add') { - $this->entityTypeId = $route_match->getParameter('entity_type_id'); - $this->bundle = $route_match->getParameter('bundle'); - $this->viewMode = $route_match->getParameter('view_mode_name'); - } - $this->panelizer = $panelizer; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('current_route_match'), - $container->get('panelizer') - ); - } - - /** - * {@inheritdoc} - */ - public function getFormId() { - return 'panelizer_wizard_general_form'; - } - - /** - * @param $machine_name - * @param $element - */ - public static function validateMachineName($machine_name, $element) { - // Attempt to load via the machine name and entity type. - if (isset($element['#machine_name']['prefix'])) { - $panelizer = \Drupal::service('panelizer'); - // Load the panels display variant. - $full_machine_name = $element['#machine_name']['prefix'] . '__' . $machine_name; - return $panelizer->getDefaultPanelsDisplayByMachineName($full_machine_name); - } - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state) { - $cached_values = $form_state->getTemporaryValue('wizard'); - /** @var \Drupal\panels\Plugin\DisplayVariant\PanelsDisplayVariant $plugin */ - $plugin = $cached_values['plugin']; - - $form_state = new FormState(); - $form_state->setValues($form_state->getValue('variant_settings', [])); - $settings = $plugin->buildConfigurationForm([], $form_state); - - // If the entity view display supports custom Panelizer layouts, force use - // of the in-place editor. Right now, there is no other way to work with - // custom layouts. - if (isset($cached_values['id'])) { - list ($this->entityTypeId, $this->bundle, $this->viewMode) = explode('__', $cached_values['id']); - } - $panelizer_settings = $this->panelizer - ->getPanelizerSettings($this->entityTypeId, $this->bundle, $this->viewMode); - - if (!empty($panelizer_settings['custom'])) { - $settings['builder']['#default_value'] = 'ipe'; - $settings['builder']['#access'] = FALSE; - } - - $settings['#tree'] = TRUE; - $form['variant_settings'] = $settings; - return $form; - } - - /** - * {@inheritdoc} - */ - public function validateForm(array &$form, FormStateInterface $form_state) { - if ($form_state->hasValue('id') && !isset($this->machine_name) && $form_state->has('machine_name_prefix')) { - $form_state->setValue('id', "{$form_state->get('machine_name_prefix')}__{$form_state->getValue('id')}"); - } - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - $cached_values = $form_state->getTemporaryValue('wizard'); - /** @var \Drupal\panels\Plugin\DisplayVariant\PanelsDisplayVariant $plugin */ - $plugin = $cached_values['plugin']; - $plugin->submitConfigurationForm($form['variant_settings'], (new FormState())->setValues($form_state->getValue('variant_settings', []))); - $configuration = $plugin->getConfiguration(); - $cached_values['plugin']->setConfiguration($configuration); - } - -} diff --git a/src/LayoutThirdPartySettingsInterface.php b/src/LayoutThirdPartySettingsInterface.php new file mode 100644 index 0000000..f5baf1a --- /dev/null +++ b/src/LayoutThirdPartySettingsInterface.php @@ -0,0 +1,10 @@ +pluginDefinition['route_parameters']['entity_type_id'] = $route_match->getCurrentRouteMatch()->getParameter('entity_type_id'); - $this->pluginDefinition['route_parameters']['bundle'] = $route_match->getCurrentRouteMatch()->getParameter('bundle'); - $this->pluginDefinition['route_parameters']['view_mode_name'] = $route_match->getCurrentRouteMatch()->getParameter('view_mode_name'); - return parent::getRouteParameters($route_match); - } - -} diff --git a/src/Panelizer.php b/src/Panelizer.php deleted file mode 100644 index 5169bb1..0000000 --- a/src/Panelizer.php +++ /dev/null @@ -1,688 +0,0 @@ -entityTypeManager = $entity_type_manager; - $this->entityTypeBundleInfo = $entity_type_bundle_info; - $this->entityFieldManager = $entity_field_manager; - $this->fieldTypeManager = $field_type_manager; - $this->moduleHandler = $module_handler; - $this->currentUser = $current_user; - $this->panelizerEntityManager = $panelizer_entity_manager; - $this->panelsManager = $panels_manager; - $this->stringTranslation = $string_translation; - $this->contextMapper = $context_mapper; - } - - /** - * Gets the Panelizer entity plugin. - * - * @param $entity_type_id - * The entity type id. - * - * @return \Drupal\panelizer\Plugin\PanelizerEntityInterface - */ - protected function getEntityPlugin($entity_type_id) { - return $this->panelizerEntityManager->createInstance($entity_type_id, []); - } - - /** - * Load a Panels Display via an ID (Machine Name). - * - * @return \Drupal\panels\Plugin\DisplayVariant\PanelsDisplayVariant|NULL - * The default Panels display with the given name if it exists; otherwise - * NULL. - */ - public function getDefaultPanelsDisplayByMachineName($full_machine_name) { - list($entity_type, $bundle, $view_mode, $machine_name) = explode('__', $full_machine_name); - /** @var \Drupal\panelizer\Panelizer $panelizer */ - // @todo this $display_id looks all wrong to me since it's the name and view_mode. - return $this->getDefaultPanelsDisplay($machine_name, $entity_type, $bundle, $view_mode); - } - - /** - * {@inheritdoc} - */ - public function getEntityViewDisplay($entity_type_id, $bundle, $view_mode) { - // Check the existence and status of: - // - the display for the view mode, - // - the 'default' display. - $candidate_ids = []; - if ($view_mode != 'default') { - $candidate_ids[] = $entity_type_id . '.' . $bundle . '.' . $view_mode; - } - $candidate_ids[] = $entity_type_id . '.' . $bundle . '.default'; - $results = \Drupal::entityQuery('entity_view_display') - ->condition('id', $candidate_ids) - ->condition('status', TRUE) - ->execute(); - - // Select the first valid candidate display, if any. - $load_id = FALSE; - foreach ($candidate_ids as $candidate_id) { - if (isset($results[$candidate_id])) { - $load_id = $candidate_id; - break; - } - } - - // Use the selected display if any, or create a fresh runtime object. - $storage = $this->entityTypeManager->getStorage('entity_view_display'); - if ($load_id) { - $display = $storage->load($load_id); - } - else { - $display = $storage->create([ - 'targetEntityType' => $entity_type_id, - 'bundle' => $bundle, - 'mode' => $view_mode, - 'status' => TRUE, - ]); - } - - // Let modules alter the display. - $display_context = [ - 'entity_type' => $entity_type_id, - 'bundle' => $bundle, - 'view_mode' => $view_mode, - ]; - $this->moduleHandler->alter('entity_view_display', $display, $display_context); - - return $display; - } - - /** - * {@inheritdoc} - */ - public function getPanelsDisplay(FieldableEntityInterface $entity, $view_mode, EntityViewDisplayInterface $display = NULL) { - $settings = $this->getPanelizerSettings($entity->getEntityTypeId(), $entity->bundle(), $view_mode, $display); - if (($settings['custom'] || $settings['allow']) && isset($entity->panelizer) && $entity->panelizer->first()) { - /** @var \Drupal\Core\Field\FieldItemInterface[] $values */ - $values = []; - foreach ($entity->panelizer as $item) { - $values[$item->view_mode] = $item; - } - if (isset($values[$view_mode])) { - $panelizer_item = $values[$view_mode]; - // Check for a customized display first and use that if present. - if (!empty($panelizer_item->panels_display)) { - // @todo: validate schema after https://www.drupal.org/node/2392057 is fixed. - return $this->panelsManager->importDisplay($panelizer_item->panels_display, FALSE); - } - // If not customized, use the specified default. - if (!empty($panelizer_item->default)) { - // If we're using this magic key use the settings default. - if ($panelizer_item->default == '__bundle_default__') { - $default = $settings['default']; - } - else { - $default = $panelizer_item->default; - // Ensure the default still exists and if not fallback sanely. - $displays = $this->getDefaultPanelsDisplays($entity->getEntityTypeId(), $entity->bundle(), $view_mode); - if (!isset($displays[$default])) { - $default = $settings['default']; - } - } - $panels_display = $this->getDefaultPanelsDisplay($default, $entity->getEntityTypeId(), $entity->bundle(), $view_mode, $display); - $this->setCacheTags($panels_display, $entity->getEntityTypeId(), $entity->bundle(), $view_mode, $display, $default, $settings); - return $panels_display; - } - } - } - // If the field has no input to give us, use the settings default. - $panels_display = $this->getDefaultPanelsDisplay($settings['default'], $entity->getEntityTypeId(), $entity->bundle(), $view_mode, $display); - $this->setCacheTags($panels_display, $entity->getEntityTypeId(), $entity->bundle(), $view_mode, $display, $settings['default'], $settings); - return $panels_display; - } - - /** - * Properly determine the cache tags for a display and set them. - * - * @param \Drupal\panels\Plugin\DisplayVariant\PanelsDisplayVariant $panels_display - * The panels display variant. - * @param string $entity_type_id - * The entity type id. - * @param string $bundle - * The bundle. - * @param string $view_mode - * The view mode. - * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface|NULL $display - * If the caller already has the correct display, it can optionally be - * passed in here so the Panelizer service doesn't have to look it up; - * otherwise, this argument can be omitted. - * @param $default - * The name of the panels display we are about to render. - * @param array $settings - * The default panelizer settings for this EntityViewDisplay. - */ - protected function setCacheTags(PanelsDisplayVariant $panels_display, $entity_type_id, $bundle, $view_mode, EntityViewDisplayInterface $display = NULL, $default, array $settings) { - if (!$display) { - $display = $this->getEntityViewDisplay($entity_type_id, $bundle, $view_mode); - } - $display_mode = $display ? $display->getMode() : ''; - - if ($default == $settings['default']) { - $tags = ["{$panels_display->getStorageType()}:{$entity_type_id}:{$bundle}:{$display_mode}"]; - } - $tags[] = "{$panels_display->getStorageType()}:{$entity_type_id}:{$bundle}:{$display_mode}:$default"; - $panels_display->addCacheTags($tags); - } - - /** - * {@inheritdoc} - */ - public function setPanelsDisplay(FieldableEntityInterface $entity, $view_mode, $default, PanelsDisplayVariant $panels_display = NULL) { - $settings = $this->getPanelizerSettings($entity->getEntityTypeId(), $entity->bundle(), $view_mode); - if (($settings['custom'] || $settings['allow']) && isset($entity->panelizer)) { - $panelizer_item = NULL; - /** @var \Drupal\Core\Field\FieldItemInterface $item */ - foreach ($entity->panelizer as $item) { - if ($item->view_mode == $view_mode) { - $panelizer_item = $item; - break; - } - } - if (!$panelizer_item) { - $panelizer_item = $this->fieldTypeManager->createFieldItem($entity->panelizer, count($entity->panelizer)); - $panelizer_item->view_mode = $view_mode; - } - - // Note: We don't call $panels_display->setStorage() here because it will - // be set internally by PanelizerFieldType::postSave() which will know - // the real revision ID of the newly saved entity. - - $panelizer_item->panels_display = $panels_display ? $this->panelsManager->exportDisplay($panels_display) : []; - $panelizer_item->default = $default; - - // Create a new revision if possible. - if ($entity instanceof RevisionableInterface && $entity->getEntityType()->isRevisionable()) { - if ($entity->isDefaultRevision()) { - $entity->setNewRevision(TRUE); - } - } - - // Updates the changed time of the entity, if necessary. - if ($entity->getEntityType()->isSubclassOf(EntityChangedInterface::class)) { - $entity->setChangedTime(REQUEST_TIME); - } - - $entity->panelizer[$panelizer_item->getName()] = $panelizer_item; - - $entity->save(); - } - else { - throw new PanelizerException("Custom overrides not enabled on this entity, bundle and view mode"); - } - } - - /** - * {@inheritdoc} - */ - public function getDefaultPanelsDisplays($entity_type_id, $bundle, $view_mode, EntityViewDisplayInterface $display = NULL) { - if (!$display) { - $display = $this->getEntityViewDisplay($entity_type_id, $bundle, $view_mode); - } - - // Get a list of all the defaults. - $display_config = $display->getThirdPartySetting('panelizer', 'displays', []); - $display_names = array_keys($display_config); - if (empty($display_names)) { - $display_names = ['default']; - } - - // Get each one individually. - $panels_displays = []; - foreach ($display_names as $name) { - if ($panels_display = $this->getDefaultPanelsDisplay($name, $entity_type_id, $bundle, $view_mode, $display)) { - $panels_displays[$name] = $panels_display; - } - } - - return $panels_displays; - } - - /** - * {@inheritdoc} - */ - public function getDefaultPanelsDisplay($name, $entity_type_id, $bundle, $view_mode, EntityViewDisplayInterface $display = NULL) { - if (!$display) { - $display = $this->getEntityViewDisplay($entity_type_id, $bundle, $view_mode); - // If we still don't find a display, then we won't find a Panelizer - // default for sure. - if (!$display) { - return NULL; - } - } - - $config = $display->getThirdPartySetting('panelizer', 'displays', []); - if (!empty($config[$name])) { - // Set a default just in case. - $config[$name]['builder'] = empty($config[$name]['builder']) ? 'standard' : $config[$name]['builder']; - // @todo: validate schema after https://www.drupal.org/node/2392057 is fixed. - $panels_display = $this->panelsManager->importDisplay($config[$name], FALSE); - } - else { - return NULL; - } - - // @todo: Should be set when written, not here! - $storage_id_parts = [ - $entity_type_id, - $bundle, - $view_mode, - $name, - ]; - $panels_display->setStorage('panelizer_default', implode(':', $storage_id_parts)); - - return $panels_display; - } - - /** - * {@inheritdoc} - */ - public function setDefaultPanelsDisplay($name, $entity_type_id, $bundle, $view_mode, PanelsDisplayVariant $panels_display) { - $display = $this->getEntityViewDisplay($entity_type_id, $bundle, $view_mode); - if (!$display) { - throw new PanelizerException("Unable to find display for given entity type, bundle and view mode"); - } - - // Set this individual Panels display. - $panels_displays = $display->getThirdPartySetting('panelizer', 'displays', []); - $panels_displays[$name] = $this->panelsManager->exportDisplay($panels_display); - $display->setThirdPartySetting('panelizer', 'displays', $panels_displays); - - $display->save(); - } - - /** - * {@inheritdoc} - */ - public function getDisplayStaticContexts($name, $entity_type_id, $bundle, $view_mode, EntityViewDisplayInterface $display = NULL) { - if (!$display) { - $display = $this->getEntityViewDisplay($entity_type_id, $bundle, $view_mode); - // If we still don't find a display, then we won't find a Panelizer - // default for sure. - if (!$display) { - return NULL; - } - } - - $config = $display->getThirdPartySetting('panelizer', 'displays', []); - if (!empty($config[$name]) && !empty($config[$name]['static_context'])) { - return $this->contextMapper->getContextValues($config[$name]['static_context']); - } - return []; - } - - /** - * {@inheritdoc} - */ - public function setDisplayStaticContexts($name, $entity_type_id, $bundle, $view_mode, $contexts) { - $display = $this->getEntityViewDisplay($entity_type_id, $bundle, $view_mode); - if (!$display) { - throw new PanelizerException("Unable to find display for given entity type, bundle and view mode"); - } - - // Set this Panels display's static contexts. - $panels_displays = $display->getThirdPartySetting('panelizer', 'displays', []); - $panels_displays[$name]['static_context'] = $contexts; - $display->setThirdPartySetting('panelizer', 'displays', $panels_displays); - - $display->save(); - } - - /** - * {@inheritdoc} - */ - public function isPanelized($entity_type_id, $bundle, $view_mode, EntityViewDisplayInterface $display = NULL) { - if (!$this->getEntityPlugin($entity_type_id)) { - return FALSE; - } - - if (!$display) { - $display = $this->getEntityViewDisplay($entity_type_id, $bundle, $view_mode); - } - - return $display->getThirdPartySetting('panelizer', 'enable', FALSE); - } - - /** - * {@inheritdoc} - */ - public function getPanelizerSettings($entity_type_id, $bundle, $view_mode, EntityViewDisplayInterface $display = NULL) { - if (!$display) { - $display = $this->getEntityViewDisplay($entity_type_id, $bundle, $view_mode); - } - - $settings = [ - 'enable' => $this->isPanelized($entity_type_id, $bundle, $view_mode, $display), - 'custom' => $display->getThirdPartySetting('panelizer', 'custom', FALSE), - 'allow' => $display->getThirdPartySetting('panelizer', 'allow', FALSE), - 'default' => $display->getThirdPartySetting('panelizer', 'default', 'default'), - ]; - - // Make sure that the Panelizer field actually exists. - if ($settings['custom']) { - $fields = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle); - $settings['custom'] = isset($fields['panelizer']) && $fields['panelizer']->getType() == 'panelizer'; - } - - return $settings; - } - - /** - * {@inheritdoc} - */ - public function setPanelizerSettings($entity_type_id, $bundle, $view_mode, array $settings, EntityViewDisplayInterface $display = NULL) { - if (!$display) { - $display = $this->getEntityViewDisplay($entity_type_id, $bundle, $view_mode); - } - - $display->setThirdPartySetting('panelizer', 'enable', !empty($settings['enable'])); - $display->setThirdPartySetting('panelizer', 'custom', !empty($settings['enable']) && !empty($settings['custom'])); - $display->setThirdPartySetting('panelizer', 'allow', !empty($settings['enable']) && !empty($settings['allow'])); - $display->setThirdPartySetting('panelizer', 'default', $settings['default']); - - if (!empty($settings['enable'])) { - // Set the default display. - $displays = $display->getThirdPartySetting('panelizer', 'displays', []); - if (empty($displays['default'])) { - /** @var \Drupal\panelizer\Plugin\PanelizerEntityInterface $panelizer_entity_plugin */ - $panelizer_entity_plugin = $this->panelizerEntityManager->createInstance($display->getTargetEntityTypeId(), []); - $displays['default'] = $this->panelsManager->exportDisplay($panelizer_entity_plugin->getDefaultDisplay($display, $display->getTargetBundle(), $display->getMode())); - $settings['default'] = "{$display->getTargetEntityTypeId()}__{$display->getTargetBundle()}__{$view_mode}__default"; - $display->setThirdPartySetting('panelizer', 'displays', $displays); - } - - // Make sure the field exists. - if (($settings['custom'] || $settings['allow'])) { - $field_storage = $this->entityTypeManager->getStorage('field_storage_config')->load($entity_type_id . '.panelizer'); - if (!$field_storage) { - $field_storage = $this->entityTypeManager->getStorage('field_storage_config')->create([ - 'entity_type' => $entity_type_id, - 'field_name' => 'panelizer', - 'type' => 'panelizer', - 'cardinality' => -1, - 'settings' => [], - 'status' => TRUE, - ]); - $field_storage->save(); - } - - $field = $this->entityTypeManager->getStorage('field_config')->load($entity_type_id . '.' . $bundle . '.panelizer'); - if (!$field) { - $field = $this->entityTypeManager->getStorage('field_config')->create([ - 'field_storage' => $field_storage, - 'bundle' => $bundle, - 'label' => $this->t('Panelizer'), - 'settings' => [], - ]); - $field->save(); - } - } - } - - $display->save(); - } - - /** - * Get a list of all the Panelizer operations. - * - * @return array - * Associative array of the human-readable operation names, keyed by the - * path. - */ - protected function getOperations() { - return [ - 'content' => $this->t('Content'), - 'layout' => $this->t('Layout'), - 'revert' => $this->t('Revert to default'), - ]; - } - - /** - * {@inheritdoc} - */ - public function getPermissions() { - $permissions = []; - - // Only look at entity types that have a corresponding Panelizer plugin. - $entity_types = array_intersect_key( - $this->entityTypeManager->getDefinitions(), - $this->panelizerEntityManager->getDefinitions() - ); - - foreach ($entity_types as $entity_type_id => $entity_type) { - $bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id); - foreach ($bundles as $bundle => $bundle_info) { - $permissions["administer panelizer $entity_type_id $bundle defaults"] = [ - 'title' => t('%entity_name %bundle_name: Administer Panelizer default panels, allowed content and settings.', [ - '%entity_name' => $entity_type->getLabel(), - '%bundle_name' => $bundle_info['label'], - ]), - 'description' => t('Users with this permission can fully administer panelizer for this entity bundle.'), - ]; - - foreach ($this->getOperations() as $path => $operation) { - $permissions["administer panelizer $entity_type_id $bundle $path"] = [ - 'title' => $this->t('%entity_name %bundle_name: Administer Panelizer @operation', [ - '%entity_name' => $entity_type->getLabel(), - '%bundle_name' => $bundle_info['label'], - '@operation' => $operation, - ]), - ]; - } - } - } - - ksort($permissions); - return $permissions; - } - - /** - * Check permission for an individual operation only. - * - * Doesn't check any of the baseline permissions that you need along with - * the operation permission. - * - * @param string $op - * The operation. - * @param string $entity_type_id - * The entity type id. - * @param string $bundle - * The bundle. - * @param \Drupal\Core\Session\AccountInterface $account - * The user account. - * - * @return bool - * TRUE if the user has permission; FALSE otherwise. - */ - protected function hasOperationPermission($op, $entity_type_id, $bundle, AccountInterface $account) { - switch ($op) { - case 'change content': - return $account->hasPermission("administer panelizer $entity_type_id $bundle content"); - - case 'change layout': - return $account->hasPermission("administer panelizer $entity_type_id $bundle layout"); - - case 'revert to default': - return $account->hasPermission("administer panelizer $entity_type_id $bundle revert"); - } - - return FALSE; - } - - /** - * {@inheritdoc} - */ - public function hasEntityPermission($op, EntityInterface $entity, $view_mode, AccountInterface $account = NULL) { - if (!$account) { - $account = $this->currentUser->getAccount(); - } - - // Must be able to edit the entity. - if (!$entity->access('update', $account)) { - return FALSE; - } - - // Must have overrides enabled. - $panelizer_settings = $this->getPanelizerSettings($entity->getEntityTypeId(), $entity->bundle(), $view_mode); - if (empty($panelizer_settings['custom'])) { - return FALSE; - } - - // Check admin permission. - if ($account->hasPermission('administer panelizer')) { - return TRUE; - } - - // @todo: check field access too! - - // if ($op == 'revert to default') { - // // We already have enough permissions at this point. - // return TRUE; - // } - - return $this->hasOperationPermission($op, $entity->getEntityTypeId(), $entity->bundle(), $account); - } - - /** - * {@inheritdoc} - */ - public function hasDefaultPermission($op, $entity_type_id, $bundle, $view_mode, $default, AccountInterface $account = NULL) { - if (!$this->isPanelized($entity_type_id, $bundle, $view_mode)) { - return FALSE; - } - - if (!$account) { - $account = $this->currentUser->getAccount(); - } - - // Check admin permissions. - if ($account->hasPermission('administer panelizer')) { - return TRUE; - } - if ($account->hasPermission("administer panelizer $entity_type_id $bundle defaults")) { - return TRUE; - } - - return $this->hasOperationPermission($op, $entity_type_id, $bundle, $account); - } - -} diff --git a/src/PanelizerEntityViewBuilder.php b/src/PanelizerEntityViewBuilder.php deleted file mode 100644 index 40c734f..0000000 --- a/src/PanelizerEntityViewBuilder.php +++ /dev/null @@ -1,412 +0,0 @@ -entityTypeId = $entity_type->id(); - $this->entityType = $entity_type; - $this->entityTypeManager = $entity_type_manager; - $this->moduleHandler = $module_handler; - $this->panelizer = $panelizer; - $this->panelizerManager = $panelizer_manager; - $this->panelsManager = $panels_manager; - } - - /** - * {@inheritdoc} - */ - public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { - return new static( - $entity_type, - $container->get('entity_type.manager'), - $container->get('module_handler'), - $container->get('panelizer'), - $container->get('plugin.manager.panelizer_entity'), - $container->get('panels.display_manager') - ); - } - - /** - * Get the Panelizer entity plugin. - * - * @return \Drupal\panelizer\Plugin\PanelizerEntityInterface|FALSE - */ - protected function getPanelizerPlugin() { - if (!isset($this->panelizerPlugin)) { - if (!$this->panelizerManager->hasDefinition($this->entityTypeId)) { - $this->panelizerPlugin = FALSE; - } - else { - $this->panelizerPlugin = $this->panelizerManager->createInstance($this->entityTypeId, []); - } - } - - return $this->panelizerPlugin; - } - - /** - * Check if Panelizer should be used for building this display. - * - * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display - * The display we're building. - * - * @return bool - */ - protected function isPanelizerEnabled(EntityViewDisplayInterface $display) { - return $display->getThirdPartySetting('panelizer', 'enable', FALSE); - } - - /** - * Gets the original view builder for this entity. - * - * @return \Drupal\Core\Entity\EntityViewBuilderInterface - * - * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException - */ - protected function getFallbackViewBuilder() { - return $this->entityTypeManager->getHandler($this->entityTypeId, 'fallback_view_builder'); - } - - /** - * Get the Panels display out of an the entity view display - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity. - * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display - * The display. - * @param $view_mode - * The view mode. - * - * @return \Drupal\panels\Plugin\DisplayVariant\PanelsDisplayVariant - * The Panels display. - */ - protected function getPanelsDisplay(EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) { - return $this->panelizer->getPanelsDisplay($entity, $view_mode, $display); - } - - /** - * Returns the display objects used to render a set of entities. - * - * Wraps EntityViewDisplay::collectRenderDisplays() so we can mock it in - * tests. - * - * @param \Drupal\Core\Entity\FieldableEntityInterface[] $entities - * The entities being rendered. They should all be of the same entity type. - * @param string $view_mode - * The view mode being rendered. - * - * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface[] - * The display objects to use to render the entities, keyed by entity - * bundle. - * - * @see EntityViewDisplay::collectRenderDisplays() - */ - protected function collectRenderDisplays($entities, $view_mode) { - return EntityViewDisplay::collectRenderDisplays($entities, $view_mode); - } - - /** - * Returns the entity context. - * - * Wraps creating new Context objects to avoid typed data in tests. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity. - * - * @return \Drupal\Core\Plugin\Context\Context - * The context. - */ - protected function getEntityContext(EntityInterface $entity) { - return new AutomaticContext(new ContextDefinition('entity:' . $this->entityTypeId, NULL, TRUE), $entity); - } - - /* - * Methods from EntityViewBuilderInterface. - */ - - /** - * {@inheritdoc} - */ - public function buildComponents(array &$build, array $entities, array $displays, $view_mode) { - $fallback_view_builder = $this->getFallbackViewBuilder(); - - $panelized_entities = []; - $fallback_entities = []; - /** - * @var string $id - * @var \Drupal\Core\Entity\EntityInterface $entity - */ - foreach ($entities as $id => $entity) { - $display = $displays[$entity->bundle()]; - if ($this->isPanelizerEnabled($display)) { - $panelized_entities[$id] = $entity; - } - else { - $fallback_entities[$id] = $entity; - } - } - - // Handle all the fallback entities first! - if (!empty($fallback_entities)) { - $fallback_view_builder->buildComponents($build, $fallback_entities, $displays, $view_mode); - } - - // Handle the panelized entities. - if (!empty($panelized_entities)) { - $this->moduleHandler - ->invokeAll('entity_prepare_view', [ - $this->entityTypeId, - $panelized_entities, - $displays, - $view_mode - ]); - } - } - - /** - * {@inheritdoc} - */ - public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) { - // Trigger hook_panelizer_pre_view_builder_alter(). - $this->moduleHandler->alter('panelizer_pre_view_builder', $view_mode, $entity, $langcode); - - $displays = $this->collectRenderDisplays([$entity], $view_mode); - $display = $displays[$entity->bundle()]; - - if (!$this->isPanelizerEnabled($display)) { - return $this->getFallbackViewBuilder()->view($entity, $view_mode, $langcode); - } - - $build = $this->buildMultiplePanelized([$entity->id() => $entity], $displays, $view_mode, $langcode); - return $build[$entity->id()]; - } - - /** - * {@inheritdoc} - */ - public function viewMultiple(array $entities = [], $view_mode = 'full', $langcode = NULL) { - $displays = $this->collectRenderDisplays($entities, $view_mode); - - $panelized_entities = []; - $fallback_entities = []; - foreach ($entities as $id => $entity) { - $display = $displays[$entity->bundle()]; - if ($this->isPanelizerEnabled($display)) { - $panelized_entities[$id] = $entity; - } - else { - $fallback_entities[$id] = $entity; - } - } - - $result = []; - if (!empty($fallback_entities)) { - $result += $this->getFallbackViewBuilder()->viewMultiple($fallback_entities, $view_mode, $langcode); - } - if (!empty($panelized_entities)) { - $result += $this->buildMultiplePanelized($panelized_entities, $displays, $view_mode, $langcode); - } - - return $result; - } - - /** - * {@inheritdoc} - */ - public function resetCache(array $entities = NULL) { - $this->getFallbackViewBuilder()->resetCache($entities); - } - - /** - * {@inheritdoc} - */ - public function viewField(FieldItemListInterface $items, $display_options = []) { - return $this->getFallbackViewBuilder()->viewfield($items, $display_options); - } - - /** - * {@inheritdoc} - */ - public function viewFieldItem(FieldItemInterface $item, $display = []) { - return $this->getFallbackViewBuilder()->viewFieldItem($item, $display); - } - - /** - * {@inheritdoc} - */ - public function getCacheTags() { - return $this->getFallbackViewBuilder()->getCacheTags(); - } - - /* - * Methods for actually rendering the Panelized entities. - */ - - /** - * Build the render array for a list of panelized entities. - * - * @param \Drupal\Core\Entity\EntityInterface[] $entities - * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface[] $displays - * @param string $view_mode - * @param string|NULL $langcode - * - * @return array - */ - protected function buildMultiplePanelized(array $entities, array $displays, $view_mode, $langcode) { - $build = []; - - foreach ($entities as $id => $entity) { - $panels_display = $this->panelizer->getPanelsDisplay($entity, $view_mode, $displays[$entity->bundle()]); - $settings = $this->panelizer->getPanelizerSettings($entity->getEntityTypeId(), $entity->bundle(), $view_mode, $displays[$entity->bundle()]); - $panels_display->setContexts($this->panelizer->getDisplayStaticContexts($settings['default'], $entity->getEntityTypeId(), $entity->bundle(), $view_mode, $displays[$entity->bundle()])); - $build[$id] = $this->buildPanelized($entity, $panels_display, $view_mode, $langcode); - - // Allow modules to modify the render array. - $alter_types = [ - "{$this->entityTypeId}_view", - 'entity_view', - ]; - $this->moduleHandler->alter($alter_types, $build[$id], $entity, $displays[$entity->bundle()]); - } - - return $build; - } - - /** - * Build the render array for a single panelized entity. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * @param \Drupal\panels\Plugin\DisplayVariant\PanelsDisplayVariant $panels_display - * @param string $view_mode - * @param string $langcode - * - * @return array - */ - protected function buildPanelized(EntityInterface $entity, PanelsDisplayVariant $panels_display, $view_mode, $langcode) { - $contexts = $panels_display->getContexts(); - $contexts['@panelizer.entity_context:entity'] = $this->getEntityContext($entity); - $panels_display->setContexts($contexts); - - $build = [ - '#theme' => [ - 'panelizer_view_mode__' . $this->entityTypeId . '__' . $entity->id(), - 'panelizer_view_mode__' . $this->entityTypeId . '__' . $entity->bundle(), - 'panelizer_view_mode__' . $this->entityTypeId, - 'panelizer_view_mode', - ], - '#panelizer_plugin' => $this->getPanelizerPlugin(), - '#panels_display' => $panels_display, - '#entity' => $entity, - '#view_mode' => $view_mode, - '#langcode' => $langcode, - 'content' => $panels_display->build(), - ]; - - if (isset($build['content']['#title'])) { - $build['#title'] = $build['content']['#title']; - } - - // @todo: I'm sure more is necessary to get the cache contexts right... - $entity_metadata = CacheableMetadata::createFromObject($entity); - CacheableMetadata::createFromObject($panels_display)->merge($entity_metadata)->applyTo($build); - - $this->getPanelizerPlugin()->alterBuild($build, $entity, $panels_display, $view_mode); - - return $build; - } - -} diff --git a/src/PanelizerInterface.php b/src/PanelizerInterface.php deleted file mode 100644 index 76a472c..0000000 --- a/src/PanelizerInterface.php +++ /dev/null @@ -1,243 +0,0 @@ -panelizerEntityManager = $panelizer_entity_manager; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, $base_plugin_id) { - return new static( - $container->get('plugin.manager.panelizer_entity') - ); - } - - /** - * {@inheritdoc} - */ - public function getDerivativeDefinitions($base_plugin_definition) { - foreach ($this->panelizerEntityManager->getDefinitions() as $plugin_id => $definition) { - $this->derivatives["$plugin_id"] = $base_plugin_definition; - $this->derivatives["$plugin_id"]['appears_on'] = [ - "entity.entity_view_display.$plugin_id.default", - "entity.entity_view_display.$plugin_id.view_mode" - ]; - } - return parent::getDerivativeDefinitions($base_plugin_definition); - } - -} diff --git a/src/Plugin/Field/FieldFormatter/PanelizerFormatter.php b/src/Plugin/Field/FieldFormatter/PanelizerFormatter.php deleted file mode 100644 index 7eb3666..0000000 --- a/src/Plugin/Field/FieldFormatter/PanelizerFormatter.php +++ /dev/null @@ -1,61 +0,0 @@ - $item) { - $elements[$delta] = [ - '#type' => 'item', - '#title' => $item->view_mode, - '#markup' => $this->viewValue($item), - ]; - } - - return $elements; - } - - /** - * Generate the output appropriate for one field item. - * - * @param \Drupal\Core\Field\FieldItemInterface $item - * One field item. - * - * @return string - * The textual output generated. - */ - protected function viewValue(FieldItemInterface $item) { - $description = ''; - if (!empty($item->default)) { - $description = $this->t('Using default called "@default"', ['@default' => $item->default]); - } - else { - $description = $this->t('Custom'); - } - return $description; - } - -} diff --git a/src/Plugin/Field/FieldType/LayoutSettingsItem.php b/src/Plugin/Field/FieldType/LayoutSettingsItem.php new file mode 100644 index 0000000..8987e72 --- /dev/null +++ b/src/Plugin/Field/FieldType/LayoutSettingsItem.php @@ -0,0 +1,73 @@ + [ + 'settings' => [ + 'type' => 'blob', + 'size' => 'normal', + 'serialize' => TRUE, + ], + ], + ]; + + return $schema; + } + + /** + * {@inheritdoc} + */ + public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { + $properties['settings'] = DataDefinition::create('map') + ->setLabel(new TranslatableMarkup('Layout Settings')) + ->setRequired(FALSE); + + return $properties; + } + + /** + * {@inheritdoc} + */ + public static function generateSampleValue(FieldDefinitionInterface $field_definition) { + $values['settings'] = []; + return $values; + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + return empty($this->settings); + } +} diff --git a/src/Plugin/Field/FieldType/PanelizerFieldType.php b/src/Plugin/Field/FieldType/PanelizerFieldType.php deleted file mode 100644 index 8f56c1b..0000000 --- a/src/Plugin/Field/FieldType/PanelizerFieldType.php +++ /dev/null @@ -1,143 +0,0 @@ -setLabel(new TranslatableMarkup('View mode')) - ->setSetting('case_sensitive', FALSE) - ->setRequired(TRUE); - $properties['default'] = DataDefinition::create('string') - ->setLabel(new TranslatableMarkup('Default name')) - ->setSetting('case_sensitive', FALSE) - ->setRequired(FALSE); - $properties['panels_display'] = MapDataDefinition::create('map') - ->setLabel(new TranslatableMarkup('Panels display')) - ->setRequired(FALSE); - - return $properties; - } - - /** - * @inheritDoc - */ - public static function mainPropertyName() { - return 'panels_display'; - } - - /** - * {@inheritdoc} - */ - public static function schema(FieldStorageDefinitionInterface $field_definition) { - $schema = [ - 'columns' => [ - 'view_mode' => [ - 'type' => 'varchar', - 'length' => '255', - 'binary' => FALSE, - ], - 'default' => [ - 'type' => 'varchar', - 'length' => '255', - 'binary' => FALSE, - ], - 'panels_display' => [ - 'type' => 'blob', - 'size' => 'normal', - 'serialize' => TRUE, - ], - ], - 'indexes' => [ - 'default' => ['default'], - ] - ]; - - return $schema; - } - - /** - * Returns the Panels display plugin manager. - * - * @return \Drupal\panels\PanelsDisplayManagerInterface - */ - protected static function getPanelsDisplayManager() { - return \Drupal::service('panels.display_manager'); - } - - /** - * {@inheritdoc} - */ - public static function generateSampleValue(FieldDefinitionInterface $field_definition) { - $panels_manager = static::getPanelsDisplayManager(); - $sample_display = $panels_manager->createDisplay(); - - $values['view_mode'] = 'default'; - $values['default'] = NULL; - $values['panels_display'] = $panels_manager->exportDisplay($sample_display); - return $values; - } - - /** - * {@inheritdoc} - */ - public function isEmpty() { - $panels_display = $this->get('panels_display')->getValue(); - $default = $this->get('default')->getValue(); - return empty($panels_display) && empty($default); - } - - /** - * {@inheritdoc} - */ - public function postSave($update) { - $panels_manager = $this->getPanelsDisplayManager(); - $panels_display_config = $this->get('panels_display')->getValue(); - - // If our field has custom panelizer display config data. - if (!empty($panels_display_config) && is_array($panels_display_config)) { - $panels_display = $panels_manager->importDisplay($panels_display_config, FALSE); - } - if (!empty($panels_display)) { - // Set the storage id to include the current revision id. - $entity = $this->getEntity(); - $storage_id_parts = [ - $entity->getEntityTypeId(), - $entity->id(), - $this->get('view_mode')->getValue() - ]; - if ($entity instanceof RevisionableInterface && $entity->getEntityType()->isRevisionable()) { - $storage_id_parts[] = $entity->getRevisionId(); - } - $panels_display->setStorage('panelizer_field', implode(':', $storage_id_parts)); - $this->set('panels_display', $panels_manager->exportDisplay($panels_display)); - - return TRUE; - } - } - -} diff --git a/src/Plugin/Field/FieldWidget/PanelizerWidget.php b/src/Plugin/Field/FieldWidget/PanelizerWidget.php deleted file mode 100644 index 1695b83..0000000 --- a/src/Plugin/Field/FieldWidget/PanelizerWidget.php +++ /dev/null @@ -1,146 +0,0 @@ -getEntity(); - $entity_type_id = $entity->getEntityTypeId(); - $entity_view_modes = $this->getEntityDisplayRepository()->getViewModeOptionsByBundle($entity_type_id, $entity->bundle()); - - // Get the current values from the entity. - $values = []; - /** @var \Drupal\Core\Field\FieldItemInterface $item */ - foreach ($items as $item) { - $values[$item->view_mode] = [ - 'default' => $item->default, - 'panels_display' => $item->panels_display, - ]; - } - - // If any view modes are missing, then set the default. - $displays = []; - foreach ($entity_view_modes as $view_mode => $view_mode_info) { - $display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode); - $displays[$view_mode] = $display->getThirdPartySetting('panelizer', 'displays', []); - // If we don't have a value, or the default is __bundle_default__ and our - // panels_display is empty, set the default to __bundle_default__. - if (!isset($values[$view_mode]) || ($values[$view_mode]['default'] == '__bundle_default__' && empty($values[$view_mode]['panels_display']))) { - if ($display->getThirdPartySetting('panelizer', 'enable', FALSE)) { - $values[$view_mode] = [ - 'default' => '__bundle_default__', - 'panels_display' => [], - ]; - } - } - } - - // Add elements to the form for each view mode. - $delta = 0; - foreach ($values as $view_mode => $value) { - $element[$delta]['view_mode'] = [ - '#type' => 'value', - '#value' => $view_mode, - ]; - - $settings = $this->getPanelizer()->getPanelizerSettings($entity_type_id, $entity->bundle(), $view_mode); - if (!empty($settings['allow'])) { - // We default to this option when the user hasn't previous interacted - // with the field. - $options = [ - '__bundle_default__' => $this->t('Current default display'), - ]; - foreach ($displays[$view_mode] as $machine_name => $panels_display) { - $options[$machine_name] = $panels_display['label']; - } - $element[$delta]['default'] = [ - '#title' => $entity_view_modes[$view_mode], - '#type' => 'select', - '#options' => $options, - '#default_value' => $value['default'], - ]; - // If we have a value in panels_display, prevent the user from - // interacting with the widget for the view modes that are overridden. - if (!empty($value['panels_display'])) { - $element[$delta]['default']['#disabled'] = TRUE; - $element[$delta]['default']['#options'][$value['default']] = $this->t('Custom Override'); - } - } - else { - $element[$delta]['default'] = [ - '#type' => 'value', - '#value' => $value['default'], - ]; - } - - $element[$delta]['panels_display'] = [ - '#type' => 'value', - '#value' => $value['panels_display'], - ]; - - $delta++; - } - - return $element; - } - -} diff --git a/src/Plugin/PanelizerEntity/PanelizerNode.php b/src/Plugin/PanelizerEntity/PanelizerNode.php deleted file mode 100644 index aeec6f9..0000000 --- a/src/Plugin/PanelizerEntity/PanelizerNode.php +++ /dev/null @@ -1,81 +0,0 @@ -setPageTitle('[node:title]'); - - // Remove the 'title' block because it's covered already. - foreach ($panels_display->getRegionAssignments() as $region => $blocks) { - /** @var \Drupal\Core\Block\BlockPluginInterface[] $blocks */ - foreach ($blocks as $block_id => $block) { - if ($block->getPluginId() == 'entity_field:node:title') { - $panels_display->removeBlock($block_id); - } - } - } - - if ($display->getComponent('links')) { - // @todo: add block for node links. - } - - if ($display->getComponent('langcode')) { - // @todo: add block for node language. - } - - return $panels_display; - } - - /** - * {@inheritdoc} - */ - public function alterBuild(array &$build, EntityInterface $entity, PanelsDisplayVariant $panels_display, $view_mode) { - /** @var $entity \Drupal\node\Entity\Node */ - parent::alterBuild($build, $entity, $panels_display, $view_mode); - - if ($entity->id()) { - $build['#contextual_links']['node'] = [ - 'route_parameters' => ['node' => $entity->id()], - 'metadata' => ['changed' => $entity->getChangedTime()], - ]; - } - } - - /** - * {@inheritdoc} - */ - public function preprocessViewMode(array &$variables, EntityInterface $entity, PanelsDisplayVariant $panels_display, $view_mode) { - parent::preprocessViewMode($variables, $entity, $panels_display, $view_mode); - - /** @var \Drupal\node\NodeInterface $node */ - $node = $entity; - - // Add node specific CSS classes. - if ($node->isPromoted()) { - $variables['attributes']['class'][] = 'node--promoted'; - } - if ($node->isSticky()) { - $variables['attributes']['class'][] = 'node--sticky'; - } - if (!$node->isPublished()) { - $variables['attributes']['class'][] = 'node--unpublished'; - } - } - -} \ No newline at end of file diff --git a/src/Plugin/PanelizerEntity/PanelizerTerm.php b/src/Plugin/PanelizerEntity/PanelizerTerm.php deleted file mode 100644 index a78a7d3..0000000 --- a/src/Plugin/PanelizerEntity/PanelizerTerm.php +++ /dev/null @@ -1,52 +0,0 @@ -setPageTitle('[term:name]'); - - // Remove the 'name' block because it's covered already. - foreach ($panels_display->getRegionAssignments() as $region => $blocks) { - /** @var \Drupal\Core\Block\BlockPluginInterface[] $blocks */ - foreach ($blocks as $block_id => $block) { - if ($block->getPluginId() == 'entity_field:taxonomy_term:name') { - $panels_display->removeBlock($block_id); - } - } - } - - return $panels_display; - } - - /** - * {@inheritdoc} - */ - public function alterBuild(array &$build, EntityInterface $entity, PanelsDisplayVariant $panels_display, $view_mode) { - /** @var $entity \Drupal\taxonomy\Entity\Term */ - parent::alterBuild($build, $entity, $panels_display, $view_mode); - - if ($entity->id()) { - $build['#contextual_links']['taxonomy_term'] = [ - 'route_parameters' => ['taxonomy_term' => $entity->id()], - 'metadata' => ['changed' => $entity->getChangedTime()], - ]; - } - } - -} diff --git a/src/Plugin/PanelizerEntity/PanelizerUser.php b/src/Plugin/PanelizerEntity/PanelizerUser.php deleted file mode 100644 index c2f84f2..0000000 --- a/src/Plugin/PanelizerEntity/PanelizerUser.php +++ /dev/null @@ -1,76 +0,0 @@ -setPageTitle('[user:name]'); - - // Remove the 'name' block because it's covered already. - foreach ($panels_display->getRegionAssignments() as $region => $blocks) { - /** @var \Drupal\Core\Block\BlockPluginInterface[] $blocks */ - foreach ($blocks as $block_id => $block) { - if ($block->getPluginId() == 'entity_field:user:name') { - $panels_display->removeBlock($block_id); - } - } - } - - if ($display->getComponent('member_for')) { - // @todo: add block for 'Member for'. - } - - return $panels_display; - } - - /** - * {@inheritdoc} - */ - public function alterBuild(array &$build, EntityInterface $entity, PanelsDisplayVariant $panels_display, $view_mode) { - /** @var $entity \Drupal\user\Entity\User */ - parent::alterBuild($build, $entity, $panels_display, $view_mode); - - if ($entity->id()) { - $build['#contextual_links']['user'] = [ - 'route_parameters' => ['user' => $entity->id()], - 'metadata' => ['changed' => $entity->getChangedTime()], - ]; - } - - // This function adds a default alt tag to the user_picture field to - // maintain accessibility. - if (user_picture_enabled() && !empty($build['content']['content'])) { - foreach (Element::children($build['content']['content']) as $key) { - if (isset($build['content']['content'][$key]['content']['field'])) { - foreach (Element::children($build['content']['content'][$key]['content']['field']) as $field_key) { - if ($build['content']['content'][$key]['content']['field']['#field_name'] == 'user_picture') { - if (empty($build['content']['content'][$key]['content']['field'][$field_key]['#item_attributes'])) { - $build['content']['content'][$key]['content']['field'][$field_key]['#item_attributes'] = [ - 'alt' => \Drupal::translation() - ->translate('Profile picture for user @username', ['@username' => $entity->getUsername()]) - ]; - } - } - } - } - } - } - } - -} diff --git a/src/Plugin/PanelizerEntityBase.php b/src/Plugin/PanelizerEntityBase.php deleted file mode 100644 index 30a8b53..0000000 --- a/src/Plugin/PanelizerEntityBase.php +++ /dev/null @@ -1,133 +0,0 @@ -panelsManager = $panels_manager; - $this->entityFieldManager = $entity_field_manager; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('panels.display_manager'), - $container->get('entity_field.manager') - ); - } - - /** - * {@inheritdoc} - */ - public function getDefaultDisplay(EntityViewDisplayInterface $display, $bundle, $view_mode) { - $panels_display = $this->panelsManager->createDisplay(); - - $panels_display->setConfiguration(['label' => $this->t('Default')] + $panels_display->getConfiguration()); - $panels_display->setLayout('layout_onecol'); - // @todo: For now we always use the IPE, but we should support not using the ipe. - $panels_display->setBuilder('ipe'); - $panels_display->setPattern('panelizer'); - - // Add all the visible fields to the Panel. - $entity_type_id = $this->getPluginId(); - /** - * @var string $field_name - * @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition - */ - foreach ($this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle) as $field_name => $field_definition) { - // Skip the Panelizer field. - if ($field_definition->getType() == 'panelizer') { - continue; - } - - if ($component = $display->getComponent($field_name)) { - $weight = $component['weight']; - unset($component['weight']); - - $panels_display->addBlock([ - 'id' => 'entity_field:' . $entity_type_id . ':' . $field_name, - 'label' => $field_definition->getLabel(), - 'provider' => 'ctools_block', - 'label_display' => '0', - 'formatter' => $component, - 'context_mapping' => [ - 'entity' => '@panelizer.entity_context:entity', - ], - 'region' => 'content', - 'weight' => $weight, - ]); - } - } - - return $panels_display; - } - - /** - * {@inheritdoc} - */ - public function alterBuild(array &$build, EntityInterface $entity, PanelsDisplayVariant $panels_display, $view_mode) { - // By default, do nothing! - } - - /** - * {@inheritdoc} - */ - public function preprocessViewMode(array &$variables, EntityInterface $entity, PanelsDisplayVariant $panels_display, $view_mode) { - $entity_type_id = $this->getPluginId(); - - // Add some default classes. - $variables['attributes']['class'][] = $entity_type_id; - $variables['attributes']['class'][] = $entity_type_id . '--type-' . $entity->bundle(); - $variables['attributes']['class'][] = $entity_type_id . '--view-mode-' . $view_mode; - $variables['attributes']['class'][] = 'clearfix'; - - // Don't render the title in the template - if ($view_mode == 'full') { - $variables['title'] = ''; - } - } - -} diff --git a/src/Plugin/PanelizerEntityInterface.php b/src/Plugin/PanelizerEntityInterface.php deleted file mode 100644 index 189e2db..0000000 --- a/src/Plugin/PanelizerEntityInterface.php +++ /dev/null @@ -1,70 +0,0 @@ -alterInfo('panelizer_entity_info'); - $this->setCacheBackend($cache_backend, 'panelizer_entity_plugins'); - } - -} diff --git a/src/Plugin/PanelizerEntityManagerInterface.php b/src/Plugin/PanelizerEntityManagerInterface.php deleted file mode 100644 index 4b6d471..0000000 --- a/src/Plugin/PanelizerEntityManagerInterface.php +++ /dev/null @@ -1,13 +0,0 @@ -t('Entity being panelized')); - $contexts['@panelizer.entity_context:entity'] = new AutomaticContext($entity_definition); - $user_definition = new ContextDefinition("entity:user", $this->t('Current user')); - $contexts['current_user'] = new Context($user_definition); - return $contexts + parent::getDefaultContexts($tempstore, $tempstore_id, $machine_name); - } - -} diff --git a/src/Plugin/PanelsStorage/PanelizerDefaultPanelsStorage.php b/src/Plugin/PanelsStorage/PanelizerDefaultPanelsStorage.php deleted file mode 100644 index 738b98d..0000000 --- a/src/Plugin/PanelsStorage/PanelizerDefaultPanelsStorage.php +++ /dev/null @@ -1,192 +0,0 @@ -entityTypeManager = $entity_type_manager; - $this->panelizer = $panelizer; - parent::__construct($configuration, $plugin_id, $plugin_definition); - } - - /** - * {@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('panelizer') - ); - } - - /** - * Converts the storage id into its component parts. - * - * @param string $id - * The storage id. There are two formats that can potentially be used: - * - The first is the normal format that we actually store: - * "entity_type_id:bundle:view_mode:name" - * - The second is a special internal format we use in the IPE so we can - * correctly set context: - * "*entity_type_id:entity_id:view_mode:name" - * - * @return array - * An array with 4 or 5 items: - * - Entity type id: string - * - Bundle name: string - * - View mode: string - * - Default name: string - * - Entity: \Drupal\Core\Entity\EntityInterface|NULL - * - * @throws \Drupal\panelizer\Exception\PanelizerException - */ - protected function parseId($id) { - list ($entity_type_id, $part_two, $view_mode, $name) = explode(':', $id); - - if (strpos($entity_type_id, '*') === 0) { - $entity_type_id = substr($entity_type_id, 1); - $storage = $this->entityTypeManager->getStorage($entity_type_id); - if ($entity = $storage->load($part_two)) { - $bundle = $entity->bundle(); - } - else { - throw new PanelizerException("Unable to load $entity_type_id with id $part_two"); - } - } - else { - $entity = NULL; - $bundle = $part_two; - } - - return [$entity_type_id, $bundle, $view_mode, $name, $entity]; - } - - /** - * Returns the entity context. - * - * Wraps creating new Context objects to avoid typed data in tests. - * - * @param string $entity_type_id - * The entity type id. - * @param \Drupal\Core\Entity\EntityInterface|NULL $entity - * The entity. - * - * @return \Drupal\Core\Plugin\Context\Context[] - * The available contexts. - */ - protected function getEntityContext($entity_type_id, EntityInterface $entity = NULL) { - $contexts = []; - // Set a placeholder context so that the calling code knows that we need - // an entity context. If we have the value available, then we actually set - // the context value. - $contexts['@panelizer.entity_context:entity'] = new AutomaticContext(new ContextDefinition('entity:' . $entity_type_id, NULL, TRUE), $entity); - return $contexts; - } - - - - /** - * {@inheritdoc} - */ - public function load($id) { - try { - list ($entity_type_id, $bundle, $view_mode, $name, $entity) = $this->parseId($id); - if ($panels_display = $this->panelizer->getDefaultPanelsDisplay($name, $entity_type_id, $bundle, $view_mode)) { - $contexts = $this->getEntityContext($entity_type_id, $entity); - $contexts = $contexts + $this->panelizer->getDisplayStaticContexts($name, $entity_type_id, $bundle, $view_mode); - $panels_display->setContexts($contexts); - return $panels_display; - } - } - catch (PanelizerException $e) { - // Do nothing to fallback on returning NULL. - } - } - - /** - * {@inheritdoc} - */ - public function save(PanelsDisplayVariant $panels_display) { - $id = $panels_display->getStorageId(); - try { - list ($entity_type_id, $bundle, $view_mode, $name) = $this->parseId($id); - $this->panelizer->setDefaultPanelsDisplay($name, $entity_type_id, $bundle, $view_mode, $panels_display); - } - catch (PanelizerException $e) { - throw new \Exception("Couldn't find Panelizer default to store Panels display"); - } - } - - /** - * {@inheritdoc} - */ - public function access($id, $op, AccountInterface $account) { - try { - list ($entity_type_id, $bundle, $view_mode, $name) = $this->parseId($id); - } - catch (PanelizerException $e) { - return AccessResult::forbidden(); - } - - if ($panels_display = $this->panelizer->getDefaultPanelsDisplay($name, $entity_type_id, $bundle, $view_mode)) { - if ($op == 'change layout') { - if ($this->panelizer->hasDefaultPermission('change layout', $entity_type_id, $bundle, $view_mode, $name, $account)) { - return AccessResult::allowed(); - } - } - else if ($op == 'read' || $this->panelizer->hasDefaultPermission('change content', $entity_type_id, $bundle, $view_mode, $name, $account)) { - return AccessResult::allowed(); - } - } - - return AccessResult::forbidden(); - } - -} \ No newline at end of file diff --git a/src/Plugin/PanelsStorage/PanelizerFieldPanelsStorage.php b/src/Plugin/PanelsStorage/PanelizerFieldPanelsStorage.php deleted file mode 100644 index ca946f8..0000000 --- a/src/Plugin/PanelsStorage/PanelizerFieldPanelsStorage.php +++ /dev/null @@ -1,196 +0,0 @@ -entityTypeManager = $entity_type_manager; - $this->panelizer = $panelizer; - parent::__construct($configuration, $plugin_id, $plugin_definition); - } - - /** - * {@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('panelizer') - ); - } - - /** - * Gets the underlying entity from storage. - * - * @param $id - * The storage service id. - * - * @return \Drupal\Core\Entity\EntityInterface|NULL - */ - protected function loadEntity($id) { - list ($entity_type, $id, , $revision_id) = array_pad(explode(':', $id), 4, NULL); - - $storage = $this->entityTypeManager->getStorage($entity_type); - if ($revision_id) { - $entity = $storage->loadRevision($revision_id); - } - else { - $entity = $storage->load($id); - } - - return $entity; - } - - /** - * Returns the entity context. - * - * Wraps creating new Context objects to avoid typed data in tests. - * - * @param string $entity_type_id - * The entity type id. - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity. - * - * @return \Drupal\Core\Plugin\Context\Context - * The context. - */ - protected function getEntityContext($entity_type_id, EntityInterface $entity) { - return new AutomaticContext(new ContextDefinition('entity:' . $entity_type_id, NULL, TRUE), $entity); - } - - /** - * {@inheritdoc} - */ - public function load($id) { - if ($entity = $this->loadEntity($id)) { - list ($entity_type_id, , $view_mode) = explode(':', $id); - if ($panels_display = $this->panelizer->getPanelsDisplay($entity, $view_mode)) { - // Set the entity as a context on the Panels display. - $contexts = [ - '@panelizer.entity_context:entity' => $this->getEntityContext($entity_type_id, $entity), - ]; - $panels_display->setContexts($contexts); - return $panels_display; - } - } - } - - /** - * {@inheritdoc} - */ - public function save(PanelsDisplayVariant $panels_display) { - $id = $panels_display->getStorageId(); - if ($entity = $this->loadEntity($id)) { - list (,, $view_mode) = explode(':', $id); - // If we're dealing with an entity that has a documented default, we - // don't want to lose that information when we save our customizations. - // This enables us to revert to the correct default at a later date. - if ($entity instanceof FieldableEntityInterface) { - $default = NULL; - if ($entity->hasField('panelizer') && $entity->panelizer->first()) { - foreach ($entity->panelizer as $item) { - if ($item->view_mode == $view_mode) { - $default = $item->default; - break; - } - } - } - try { - $this->panelizer->setPanelsDisplay($entity, $view_mode, $default, $panels_display); - } - catch (PanelizerException $e) { - // Translate to expected exception type. - throw new \Exception($e->getMessage()); - } - } - } - else { - throw new \Exception("Couldn't find entity to store Panels display on"); - } - } - - /** - * {@inheritdoc} - */ - public function access($id, $op, AccountInterface $account) { - if ($entity = $this->loadEntity($id)) { - $access = AccessResult::neutral() - ->addCacheableDependency($account); - - // We do not support "create", as this method's interface dictates, - // because we work with existing entities here. - $entity_operations = [ - 'read' => 'view', - 'update' => 'update', - 'delete'=> 'delete', - 'change layout' => 'update', - ]; - // Do not add entity cacheability metadata to the forbidden result, - // because it depends on the Panels operation, and not on the entity. - $access->orIf(isset($entity_operations[$op]) ? $entity->access($entity_operations[$op], $account, TRUE) : AccessResult::forbidden()); - - if (!$access->isForbidden() && $entity instanceof FieldableEntityInterface) { - list (,, $view_mode) = explode(':', $id); - if ($op == 'change layout') { - if ($this->panelizer->hasEntityPermission('change layout', $entity, $view_mode, $account)) { - return $access->orIf(AccessResult::allowed()); - } - } - else if ($op == 'read' || $this->panelizer->hasEntityPermission('change content', $entity, $view_mode, $account)) { - return $access->orIf(AccessResult::allowed()); - } - } - } - - return AccessResult::forbidden(); - } - -} diff --git a/src/Plugin/Relationship/ConfigurableRelationshipBase.php b/src/Plugin/Relationship/ConfigurableRelationshipBase.php new file mode 100644 index 0000000..c2fa38f --- /dev/null +++ b/src/Plugin/Relationship/ConfigurableRelationshipBase.php @@ -0,0 +1,102 @@ +configuration; + } + + /** + * {@inheritdoc} + */ + public function setConfiguration(array $configuration) { + $this->configuration = $configuration; + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return []; + } + + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + return []; + } + + /** + * Form constructor. + * + * Plugin forms are embedded in other forms. In order to know where the plugin + * form is located in the parent form, #parents and #array_parents must be + * known, but these are not available during the initial build phase. In order + * to have these properties available when building the plugin form's + * elements, let this method return a form element that has a #process + * callback and build the rest of the form in the callback. By the time the + * callback is executed, the element's #parents and #array_parents properties + * will have been set by the form API. For more documentation on #parents and + * #array_parents, see \Drupal\Core\Render\Element\FormElement. + * + * @param array $form + * An associative array containing the initial structure of the plugin form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. Calling code should pass on a subform + * state created through + * \Drupal\Core\Form\SubformState::createForSubform(). + * + * @return array + * The form structure. + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form['#tree'] = TRUE; + + $contexts = $form_state->getTemporaryValue('gathered_contexts') ?: []; + $form['context_mapping'] = $this->addContextAssignmentElement($this, $contexts); + + return $form; + } + + /** + * Form validation handler. + * + * @param array $form + * An associative array containing the structure of the plugin form as built + * by static::buildConfigurationForm(). + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. Calling code should pass on a subform + * state created through + * \Drupal\Core\Form\SubformState::createForSubform(). + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + } + + /** + * Form submission handler. + * + * @param array $form + * An associative array containing the structure of the plugin form as built + * by static::buildConfigurationForm(). + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. Calling code should pass on a subform + * state created through + * \Drupal\Core\Form\SubformState::createForSubform(). + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $this->configuration['context_mapping'] = $form_state->getValues('context_mapping'); + } +} diff --git a/src/Plugin/Relationship/ConfigurableRelationshipInterface.php b/src/Plugin/Relationship/ConfigurableRelationshipInterface.php new file mode 100644 index 0000000..3dcc097 --- /dev/null +++ b/src/Plugin/Relationship/ConfigurableRelationshipInterface.php @@ -0,0 +1,12 @@ +getContextValues( + $this->getStaticContextConfiguration() + ); + + /** @var \Drupal\ctools\Plugin\RelationshipManager $relationship_manager */ + $relationship_manager = \Drupal::service('plugin.manager.ctools.relationship'); + /** @var \Drupal\Core\Plugin\Context\ContextHandler $context_handler */ + $context_handler = \Drupal::service('context.handler'); + + foreach ($this->getRelationshipsConfiguration() as $machine_name => $relationship) { + /** @var \Drupal\ctools\Plugin\RelationshipInterface $plugin */ + $plugin = $relationship_manager->createInstance($relationship['plugin'], $relationship['settings'] ?: []); + $context_handler->applyContextMapping($plugin, $contexts); + + $contexts[$machine_name] = $plugin->getRelationship(); + } + + return $contexts; + } + + /** + * Get the relationships configuration. + * + * @return array + */ + protected function getRelationshipsConfiguration() { + return []; + } + + /** + * Get the relationships configuration. + * + * @return array + */ + protected function getStaticContextConfiguration() { + return []; + } +} diff --git a/src/Plugin/SectionStorage/DefaultsSectionStorage.php b/src/Plugin/SectionStorage/DefaultsSectionStorage.php new file mode 100644 index 0000000..11b4d73 --- /dev/null +++ b/src/Plugin/SectionStorage/DefaultsSectionStorage.php @@ -0,0 +1,24 @@ +getThirdPartySetting('panelizer', 'relationships', []); + } + + /** + * {@inheritdoc} + */ + protected function getStaticContextConfiguration() { + return $this->getThirdPartySetting('panelizer', 'static_context', []); + } +} diff --git a/src/Plugin/SectionStorage/OverridesSectionStorage.php b/src/Plugin/SectionStorage/OverridesSectionStorage.php new file mode 100644 index 0000000..4446265 --- /dev/null +++ b/src/Plugin/SectionStorage/OverridesSectionStorage.php @@ -0,0 +1,120 @@ +getEntity()->get(static::SETTINGS_FIELD_NAME)->settings; + $settings[$module][$key] = $value; + $this->getEntity()->get(static::SETTINGS_FIELD_NAME)->settings = $settings; + + return $settings; + } + + /** + * Gets the value of a third-party setting. + * + * @param string $module + * The module providing the third-party setting. + * @param string $key + * The setting name. + * @param mixed $default + * The default value + * + * @return mixed + * The value. + */ + public function getThirdPartySetting($module, $key, $default = NULL) { + $settings = $this->getEntity()->get(static::SETTINGS_FIELD_NAME)->settings; + if (isset($settings[$module][$key])) { + return $settings[$module][$key]; + } + + return $default; + } + + /** + * Gets all third-party settings of a given module. + * + * @param string $module + * The module providing the third-party settings. + * + * @return array + * An array of key-value pairs. + */ + public function getThirdPartySettings($module) { + $settings = $this->getEntity()->get(static::SETTINGS_FIELD_NAME)->settings; + if (isset($settings[$module])) { + return $settings[$module]; + } + + return []; + } + + /** + * Unsets a third-party setting. + * + * @param string $module + * The module providing the third-party setting. + * @param string $key + * The setting name. + * + * @return mixed + * The value. + */ + public function unsetThirdPartySetting($module, $key) { + $settings = $this->getEntity()->get(static::SETTINGS_FIELD_NAME)->settings; + $value = $settings[$module][$key]; + unset($settings[$module][$key]); + $this->getEntity()->get(static::SETTINGS_FIELD_NAME)->settings = $settings; + + return $value; + } + + /** + * Gets the list of third parties that store information. + * + * @return array + * The list of third parties. + */ + public function getThirdPartyProviders() { + $settings = $this->getEntity()->get(static::SETTINGS_FIELD_NAME)->settings; + return array_keys($settings); + } + + /** + * {@inheritdoc} + */ + protected function getRelationshipsConfiguration() { + return $this->getThirdPartySetting('panelizer', 'relationships', []); + } + + /** + * {@inheritdoc} + */ + protected function getStaticContextConfiguration() { + return $this->getThirdPartySetting('panelizer', 'static_context', []); + } +} diff --git a/src/Tests/Update/PanelizerLayoutIDUpdateTest.php b/src/Tests/Update/PanelizerLayoutIDUpdateTest.php deleted file mode 100644 index f08af8e..0000000 --- a/src/Tests/Update/PanelizerLayoutIDUpdateTest.php +++ /dev/null @@ -1,41 +0,0 @@ -databaseDumpFiles = [ - __DIR__ . '/../../../tests/fixtures/update/drupal-8.panelizer.minimal.php.gz', - ]; - } - - /** - * Test updates. - */ - public function testUpdate() { - $this->runUpdates(); - - $this->drupalLogin($this->rootUser); - $this->drupalGet('admin/structure/types/manage/article/display'); - $this->clickLink('Edit', 1); - $this->assertResponse(200); - - $this->drupalGet('node/1'); - $this->assertResponse(200); - - $this->drupalGet('node/2'); - $this->assertResponse(200); - } - -} diff --git a/src/Wizard/PanelizerAddWizard.php b/src/Wizard/PanelizerAddWizard.php deleted file mode 100644 index 85513e8..0000000 --- a/src/Wizard/PanelizerAddWizard.php +++ /dev/null @@ -1,72 +0,0 @@ -set('machine_name_prefix', "{$entity_type_id}__{$bundle}__{$view_mode_name}"); - } - $form = parent::buildForm($form, $form_state); - $cached_values = $form_state->getTemporaryValue('wizard'); - $cached_values['id'] = $this->getMachineName(); - // Some variants like PanelsDisplayVariant need this. Set it to empty. - $cached_values['access'] = []; - $form_state->setTemporaryValue('wizard', $cached_values); - return $form; - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - $operations = array_map('strval', [ - $this->getNextOp(), - $this->t('Update'), - $this->t('Update and save'), - $this->t('Save'), - ]); - - if (in_array($form_state->getValue('op'), $operations)) { - $cached_values = $form_state->getTemporaryValue('wizard'); - if ($form_state->hasValue('label')) { - $config = $cached_values['plugin']->getConfiguration(); - $config['label'] = $form_state->getValue('label'); - $cached_values['plugin']->setConfiguration($config); - } - if ($form_state->hasValue('id')) { - $cached_values['id'] = $form_state->getValue('id'); - /** @var \Drupal\panels\Plugin\DisplayVariant\PanelsDisplayVariant $plugin */ - $plugin = $cached_values['plugin']; - $plugin->setStorage($plugin->getStorageType(), $cached_values['id']); - } - } - parent::submitForm($form, $form_state); - } - - /** - * {@inheritdoc} - */ - public function finish(array &$form, FormStateInterface $form_state) { - parent::finish($form, $form_state); - $cached_values = $form_state->getTemporaryValue('wizard'); - $form_state->setRedirect('panelizer.wizard.edit', ['machine_name' => $cached_values['id'], 'step' => 'content']); - } - -} diff --git a/src/Wizard/PanelizerEditWizard.php b/src/Wizard/PanelizerEditWizard.php deleted file mode 100644 index fd68561..0000000 --- a/src/Wizard/PanelizerEditWizard.php +++ /dev/null @@ -1,225 +0,0 @@ -getMachineName(); - list($entity_type, $bundle, $view_mode, $display_id) = explode('__', $this->getMachineName()); - $panelizer = \Drupal::service('panelizer'); - // Load the panels display variant. - /** @var \Drupal\panelizer\Panelizer $panelizer */ - // @todo this $display_id looks all wrong to me since it's the name and view_mode. - $variant_plugin = $panelizer->getDefaultPanelsDisplay($display_id, $entity_type, $bundle, $view_mode); - $cached_values['plugin'] = $variant_plugin; - $cached_values['label'] = $cached_values['plugin']->getConfiguration()['label']; - - $display = $panelizer->getEntityViewDisplay($entity_type, $bundle, $view_mode); - $config = $display->getThirdPartySetting('panelizer', 'displays', []); - if (!empty($config[$display_id]['static_context'])) { - $cached_values['contexts'] = $config[$display_id]['static_context']; - } - return $cached_values; - } - - /** - * {@inheritdoc} - */ - protected function customizeForm(array $form, FormStateInterface $form_state) { - // The page actions. - $form['wizard_actions'] = [ - '#theme' => 'links', - '#links' => [], - '#attributes' => [ - 'class' => ['inline'], - ] - ]; - - // The tree of wizard steps. - $form['wizard_tree'] = [ - '#theme' => ['panelizer_wizard_tree'], - '#wizard' => $this, - '#cached_values' => $form_state->getTemporaryValue('wizard'), - ]; - - $form['#theme'] = 'panelizer_wizard_form'; - $form['#attached']['library'][] = 'panelizer/wizard_admin'; - $form = parent::customizeForm($form, $form_state); - return $form; - } - - /** - * {@inheritdoc} - */ - protected function actions(FormInterface $form_object, FormStateInterface $form_state) { - $cached_values = $form_state->getTemporaryValue('wizard'); - $operation = $this->getOperation($cached_values); - - $actions = []; - - $actions['submit'] = [ - '#type' => 'submit', - '#value' => $this->t('Update'), - '#validate' => [ - '::populateCachedValues', - [$form_object, 'validateForm'], - ], - '#submit' => [ - [$form_object, 'submitForm'], - ], - ]; - - $actions['update_and_save'] = [ - '#type' => 'submit', - '#value' => $this->t('Update and save'), - '#button_type' => 'primary', - '#validate' => [ - '::populateCachedValues', - [$form_object, 'validateForm'], - ], - '#submit' => [ - [$form_object, 'submitForm'], - ], - ]; - - $actions['finish'] = [ - '#type' => 'submit', - '#value' => $this->t('Save'), - '#validate' => [ - '::populateCachedValues', - [$form_object, 'validateForm'], - ], - '#submit' => [ - [$form_object, 'submitForm'], - ], - ]; - - $actions['cancel'] = [ - '#type' => 'submit', - '#value' => $this->t('Cancel'), - '#submit' => [ - '::clearTempstore' - ], - ]; - - // Add any submit or validate functions for the step and the global ones. - foreach (['submit', 'update_and_save', 'finish'] as $button) { - if (isset($operation['validate'])) { - $actions[$button]['#validate'] = array_merge($actions[$button]['#validate'], $operation['validate']); - } - $actions[$button]['#validate'][] = '::validateForm'; - if (isset($operation['submit'])) { - $actions[$button]['#submit'] = array_merge($actions[$button]['#submit'], $operation['submit']); - } - $actions[$button]['#submit'][] = '::submitForm'; - } - $actions['update_and_save']['#submit'][] = '::finish'; - $actions['finish']['#submit'][] = '::finish'; - - if ($form_state->get('ajax')) { - $cached_values = $form_state->getTemporaryValue('wizard'); - $ajax_parameters = $this->getNextParameters($cached_values); - $ajax_parameters['step'] = $this->getStep($cached_values); - $ajax_url = Url::fromRoute($this->getRouteName(), $ajax_parameters); - $ajax_options = [ - 'query' => $this->getRequest()->query->all() + [ - FormBuilderInterface::AJAX_FORM_REQUEST => TRUE, - ], - ]; - $actions['submit']['#ajax'] = [ - 'callback' => '::ajaxSubmit', - 'url' => $ajax_url, - 'options' => $ajax_options, - ]; - $actions['update_and_save']['#ajax'] = [ - 'callback' => '::ajaxFinish', - 'url' => $ajax_url, - 'options' => $ajax_options, - ]; - $actions['finish']['#ajax'] = [ - 'callback' => '::ajaxFinish', - 'url' => $ajax_url, - 'options' => $ajax_options, - ]; - } - - return $actions; - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - $operations = array_map('strval', [ - $this->getNextOp(), - $this->t('Update'), - $this->t('Update and save'), - $this->t('Save'), - ]); - - if (in_array($form_state->getValue('op'), $operations)) { - $cached_values = $form_state->getTemporaryValue('wizard'); - if ($form_state->hasValue('label')) { - $config = $cached_values['plugin']->getConfiguration(); - $config['label'] = $form_state->getValue('label'); - $cached_values['plugin']->setConfiguration($config); - } - if ($form_state->hasValue('id')) { - $cached_values['id'] = $form_state->getValue('id'); - } - if (is_null($this->machine_name) && !empty($cached_values['id'])) { - $this->machine_name = $cached_values['id']; - } - $this->getTempstore()->set($this->getMachineName(), $cached_values); - if (!$form_state->get('ajax')) { - $form_state->setRedirect($this->getRouteName(), $this->getNextParameters($cached_values)); - } - } - } - - /** - * Clears the temporary store. - * - * @param array $form - * @param \Drupal\Core\Form\FormStateInterface $form_state - */ - public function clearTempstore(array &$form, FormStateInterface $form_state) { - $this->getTempstore()->delete($this->getMachineName()); - list($entity_type_id, $bundle, $view_mode) = explode('__', $this->getMachineName()); - $bundle_entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id)->getBundleEntityType(); - if ($view_mode == 'default') { - $route = "entity.entity_view_display.{$entity_type_id}.default"; - $arguments = [ - $bundle_entity_type => $bundle, - ]; - } - else { - $route = "entity.entity_view_display.{$entity_type_id}.view_mode"; - $arguments = [ - $bundle_entity_type => $bundle, - 'view_mode_name' => $view_mode, - ]; - } - $form_state->setRedirect($route, $arguments); - } - -} diff --git a/src/Wizard/PanelizerWizardBase.php b/src/Wizard/PanelizerWizardBase.php deleted file mode 100644 index ac446b4..0000000 --- a/src/Wizard/PanelizerWizardBase.php +++ /dev/null @@ -1,168 +0,0 @@ -getTemporaryValue('wizard'); - // Get the current form operation. - $operation = $this->getOperation($cached_values); - $operations = $this->getOperations($cached_values); - $default_operation = reset($operations); - - // Get the machine name. There are two ways we can get this data. - $storage = $form_state->getStorage(); - $prefix = isset($storage['machine_name_prefix']) ? $storage['machine_name_prefix'] : $form_state->getTemporaryValue('wizard')['id']; - - if ($operation['form'] == $default_operation['form']) { - // Create id and label form elements. - $form['name'] = [ - '#type' => 'fieldset', - '#attributes' => ['class' => ['fieldset-no-legend']], - '#title' => $this->getWizardLabel(), - ]; - $form['name']['label'] = [ - '#type' => 'textfield', - '#title' => $this->getMachineLabel(), - '#required' => TRUE, - '#size' => 32, - '#default_value' => !empty($cached_values['label']) ? $cached_values['label'] : '', - '#maxlength' => 255, - '#disabled' => !empty($cached_values['label']), - ]; - $form['name']['id'] = [ - '#type' => 'machine_name', - '#maxlength' => 128, - '#machine_name' => [ - 'source' => ['name', 'label'], - 'exists' => $this->exists(), - 'prefix' => $prefix, - ], - '#description' => $this->t('A unique machine-readable name for this display. It must only contain lowercase letters, numbers, and underscores.'), - '#default_value' => !empty($cached_values['id']) ? $cached_values['id'] : '', - '#disabled' => !empty($cached_values['id']), - ]; - } - return $form; - } - - /** - * {@inheritdoc} - */ - public function getWizardLabel() { - return $this->t('Wizard Information'); - } - - /** - * {@inheritdoc} - */ - public function getMachineLabel() { - return $this->t('Wizard name'); - } - - /** - * {@inheritdoc} - */ - public function exists() { - return '\Drupal\panelizer\Form\PanelizerWizardGeneralForm::validateMachineName'; - } - - /** - * {@inheritdoc} - */ - public function getOperations($cached_values) { - $operations = [ - 'general' => [ - 'form' => PanelizerWizardGeneralForm::class, - 'title' => $this->t('General settings'), - ], - 'contexts' => [ - 'form' => PanelizerWizardContextForm::class, - 'title' => $this->t('Contexts'), - ], - ]; - - // Add any wizard operations from the plugin itself. - foreach ($cached_values['plugin']->getWizardOperations($cached_values) as $name => $operation) { - $operations[$name] = $operation; - } - - // Change the class that manages the Content step. - if (isset($operations['content'])) { - //$operations['content']['form'] = PanelizerWizardContentForm::class; - } - - return $operations; - } - - public function initValues() { - $cached_values = parent::initValues(); - $cached_values['access'] = new PanelizerUIAccess(); - if (empty($cached_values['plugin'])) { - /** @var \Drupal\panels\Plugin\DisplayVariant\PanelsDisplayVariant $plugin */ - $plugin = \Drupal::service('plugin.manager.display_variant')->createInstance('panels_variant'); - $plugin->setPattern('panelizer'); - $plugin->setBuilder('ipe'); - $plugin->setStorage('panelizer_default', 'TEMPORARY_STORAGE_ID'); - $cached_values['plugin'] = $plugin; - } - if (empty($cached_values['contexts'])) { - $cached_values['contexts'] = []; - } - return $cached_values; - } - - - /** - * {@inheritdoc} - */ - public function finish(array &$form, FormStateInterface $form_state) { - $cached_values = $form_state->getTemporaryValue('wizard'); - - // Save the panels display mode and its custom settings as third party - // data of the display mode for this entity+bundle+display. - /** @var \Drupal\panelizer\Panelizer $panelizer */ - $panelizer = \Drupal::service('panelizer'); - /** @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface $invalidator */ - $invalidator = \Drupal::service('cache_tags.invalidator'); - list($entity_type, $bundle, $view_mode, $display_id) = explode('__', $cached_values['id']); - $panelizer->setDefaultPanelsDisplay($display_id, $entity_type, $bundle, $view_mode, $cached_values['plugin']); - $panelizer->setDisplayStaticContexts($display_id, $entity_type, $bundle, $view_mode, $cached_values['contexts']); - - parent::finish($form, $form_state); - $form_state->setRedirect('panelizer.wizard.edit', ['machine_name' => $cached_values['id']]); - $invalidator->invalidateTags(["panelizer_default:$entity_type:$bundle:$view_mode:$display_id"]); - } - - /** - * Wraps the context mapper. - * - * @return \Drupal\ctools\ContextMapperInterface - */ - protected function getContextMapper() { - return \Drupal::service('ctools.context_mapper'); - } - - /** - * {@inheritdoc} - */ - protected function getContexts($cached_values) { - return $this->getContextMapper()->getContextValues($cached_values['contexts']); - } - -} diff --git a/templates/panelizer-view-mode.html.twig b/templates/panelizer-view-mode.html.twig deleted file mode 100644 index 415b691..0000000 --- a/templates/panelizer-view-mode.html.twig +++ /dev/null @@ -1,42 +0,0 @@ -{# -/** - * @file - * Template for a generic Panelizer view mode. - * - * Available variables: - * - entity: The entity with limited access to object properties and methods. - * - attributes: HTML attributes for the containing element. - * - content: All entity items. - * - entity_url: Direct URL of the current entity. - * - title: The title of the entity. - * - title_element: HTML element to use for the title (defaults to 'h2'). - * - title_attributes: Same as attributes, except applied to the main title - * tag that appears in the template. - * - 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. - * - view_mode: View mode; for example, "teaser" or "full". - * - * @see template_preprocess_panelizer_view_mode() - * - * @ingroup themeable - */ -#} - - {{ title_prefix }} - {% if title %} - <{{ title_element }}{{ title_attributes }}> - {% if entity_url %} - {{ title }} - {% else %} - {{ title }} - {% endif %} - - {% endif %} - {{ title_suffix }} - - - {{ content }} - - diff --git a/templates/panelizer-wizard-form.html.twig b/templates/panelizer-wizard-form.html.twig deleted file mode 100644 index a691162..0000000 --- a/templates/panelizer-wizard-form.html.twig +++ /dev/null @@ -1,31 +0,0 @@ -{# -/** - * @file - * Default theme implementation for a 'form' element. - * - * Available variables - * - attributes: A list of HTML attributes for the wrapper element. - * - children: The child elements of the form. - * - * @see template_preprocess_form() - * - * @ingroup themeable - */ -#} -
-
- {{ form.wizard_actions }} -
-
-
- {{ form.wizard_tree }} -
-
- {{ form|without('wizard_actions', 'wizard_tree', 'actions') }} -
-
- -
- {{ form.actions }} -
-
diff --git a/templates/panelizer-wizard-tree.html.twig b/templates/panelizer-wizard-tree.html.twig deleted file mode 100644 index 7d48fcf..0000000 --- a/templates/panelizer-wizard-tree.html.twig +++ /dev/null @@ -1,47 +0,0 @@ -{# -/** - * @file - * Default theme implementation to display wizard tree. - * - * Available variables: - * - step: The current step name. - * - tree: A nested list of menu items. Each menu item contains: - * - title: The menu link title. - * - url: The menu link url, instance of \Drupal\Core\Url - * - children: The menu item child items. - * - step: The name of the step. - * - * @ingroup themeable - */ -#} -{% import _self as panelizer %} - -{# - We call a macro which calls itself to render the full tree. - @see http://twig.sensiolabs.org/doc/tags/macro.html -#} -{{ panelizer.wizard_tree(tree, step, 0) }} - -{% macro wizard_tree(items, step, menu_level) %} - {% import _self as panelizer %} - {% if items %} - - {% endif %} -{% endmacro %} diff --git a/tests/fixtures/update/drupal-8.panelizer.minimal.php.gz b/tests/fixtures/update/drupal-8.panelizer.minimal.php.gz deleted file mode 100644 index 8798cb2f946ef6a0745531298bcedf31da4dfc90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63434 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%0A=+A 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 B`L+N6 diff --git a/tests/modules/panelizer_test/panelizer_test.info.yml b/tests/modules/panelizer_test/panelizer_test.info.yml deleted file mode 100644 index 8ed1e67..0000000 --- a/tests/modules/panelizer_test/panelizer_test.info.yml +++ /dev/null @@ -1,6 +0,0 @@ -type: module -name: Panelizer Test -description: 'Required for Panelizer simpletests only.' -core: 8.x -dependencies: - - panelizer:panelizer diff --git a/tests/modules/panelizer_test/src/Plugin/Block/PanelizerTestBlock.php b/tests/modules/panelizer_test/src/Plugin/Block/PanelizerTestBlock.php deleted file mode 100644 index 212243f..0000000 --- a/tests/modules/panelizer_test/src/Plugin/Block/PanelizerTestBlock.php +++ /dev/null @@ -1,26 +0,0 @@ - 'Abracadabra', - ]; - } - -} \ No newline at end of file diff --git a/tests/src/Functional/PanelizerAddDefaultLinkTest.php b/tests/src/Functional/PanelizerAddDefaultLinkTest.php deleted file mode 100644 index b8fb425..0000000 --- a/tests/src/Functional/PanelizerAddDefaultLinkTest.php +++ /dev/null @@ -1,67 +0,0 @@ -drupalPlaceBlock('local_actions_block', [ - 'region' => 'content', - 'theme' => \Drupal::theme()->getActiveTheme()->getName(), - ]); - - $content_type = 'page'; - - // Log in the user. - $this->loginUser1(); - - // Create the content type. - $this->drupalCreateContentType(['type' => $content_type, 'name' => 'Page']); - - // Panelize the content type. - $this->panelize($content_type); - - // Confirm that the content type is now panelized. - $this->assertLink('Add a new Panelizer default display'); - - // Un-panelize the content type. - $this->unpanelize($content_type); - - // Confirm that the content type is no longer panelized. - $this->assertNoLink('Add a new Panelizer default display'); - } - -} diff --git a/tests/src/Functional/PanelizerDefaultsTest.php b/tests/src/Functional/PanelizerDefaultsTest.php deleted file mode 100644 index a147f37..0000000 --- a/tests/src/Functional/PanelizerDefaultsTest.php +++ /dev/null @@ -1,94 +0,0 @@ -install([$theme]); - \Drupal::service('theme_handler')->setDefault($theme); - - // Place the local actions block in the theme so that we can assert the - // presence of local actions and such. - $this->drupalPlaceBlock('local_actions_block', [ - 'region' => 'content', - 'theme' => $theme, - ]); - } - - public function test() { - $this->setupContentType(); - $this->loginUser1(); - - // Get all enabled view modes machine names for page. - $view_modes = array_keys(\Drupal::service('entity_display.repository') - ->getViewModeOptionsByBundle('node', 'page')); - foreach ($view_modes as $i => $view_mode_name) { - // Be sure view mode can be panelized. - $this->panelize('page', $view_mode_name); - // Create an additional default layout so we can assert that it's available - // as an option when choosing the layout on the node form. - $panelizer_id = $this->addPanelizerDefault('page', $view_mode_name); - $this->assertDefaultExists('page', $view_mode_name, $panelizer_id); - // The user should only be able to choose the layout if specifically allowed - // to (the panelizer[allow] checkbox in the view display configuration). By - // default, they aren't. - $this->drupalGet('node/add/page'); - $this->assertResponse(200); - $this->assertNoFieldByName("panelizer['{$i}][default]"); - // Allow user to select panelized modes in UI. - $this->panelize('page', $view_mode_name, [ - 'panelizer[custom]' => TRUE, - 'panelizer[allow]' => TRUE, - ]); - $this->drupalGet('node/add/page'); - $this->assertResponse(200); - $this->assertFieldByName("panelizer[{$i}][default]"); - $this->assertOption("edit-panelizer-{$i}-default", 'default'); - $this->assertOption("edit-panelizer-{$i}-default", $panelizer_id); - // Clean up. - $this->deletePanelizerDefault('page', $view_mode_name, $panelizer_id); - $this->assertDefaultNotExists('page', $view_mode_name, $panelizer_id); - } - } - -} diff --git a/tests/src/Functional/PanelizerIpeTest.php b/tests/src/Functional/PanelizerIpeTest.php deleted file mode 100644 index 9b04138..0000000 --- a/tests/src/Functional/PanelizerIpeTest.php +++ /dev/null @@ -1,378 +0,0 @@ -rebuildAll(); - } - - /** - * The content type that will be tested against. - * - * @string - */ - protected $content_type = 'page'; - - /** - * Create a user with the required permissions. - * - * @param array $perms - * Any additiona permissions that need to be added. - * - * @return Drupal\user\Entity\User - * The user account that was created. - */ - protected function createAdminUser(array $perms = array()) { - $perms += [ - // From system. - 'access administration pages', - - // Content permissions. - 'access content', - 'administer content types', - 'administer nodes', - 'create page content', - 'edit any page content', - 'edit own page content', - - // From Field UI. - 'administer node display', - - // From Panels. - 'access panels in-place editing', - ]; - $this->verbose('
' . print_r($perms, TRUE) . '
'); - return $this->drupalCreateUser($perms); - } - - /** - * Test that the IPE functionality as user 1, which should cover all options. - */ - public function testAdminUser() { - $this->setupContentType($this->content_type); - - // Create a test node. - $node = $this->createTestNode(); - - // Log in as user 1. - $this->loginUser1(); - - // Load the test node. - $this->drupalGet('node/' . $node->id()); - $this->assertResponse(200); - - // Confirm the JSON Drupal settings are appropriate. - $drupalSettings = NULL; - $matches = []; - if (preg_match('@@', $this->getRawContent(), $matches)) { - $drupalSettings = Json::decode($matches[1]); - $this->verbose('
' . print_r($drupalSettings, TRUE) . '
'); - } - $this->assertNotNull($drupalSettings); - if (!empty($drupalSettings)) { - $this->assertTrue(isset($drupalSettings['panels_ipe'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['regions'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['layout'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['user_permission'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['panels_display'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['unsaved'])); - $this->assertTrue(isset($drupalSettings['panelizer'])); - $this->assertTrue(isset($drupalSettings['panelizer']['entity'])); - $this->assertTrue(isset($drupalSettings['panelizer']['entity']['entity_type_id'])); - $this->assertEqual($drupalSettings['panelizer']['entity']['entity_type_id'], 'node'); - $this->assertTrue(isset($drupalSettings['panelizer']['entity']['entity_id'])); - $this->assertEqual($drupalSettings['panelizer']['entity']['entity_id'], $node->id()); - $this->assertTrue(isset($drupalSettings['panelizer']['user_permission'])); - $this->assertTrue(isset($drupalSettings['panelizer']['user_permission']['revert'])); - $this->assertTrue(isset($drupalSettings['panelizer']['user_permission']['save_default'])); - } - } - - /** - * Confirm the 'administer panelizer' permission works. - */ - public function testAdministerPanelizerPermission() { - $this->setupContentType($this->content_type); - - // Create a test node. - $node = $this->createTestNode(); - - // Create a new user with the permissions being tested. - $perms = [ - 'administer panelizer', - ]; - $account = $this->createAdminUser($perms); - $this->drupalLogin($account); - - // Load the test node. - $this->drupalGet('node/' . $node->id()); - $this->assertResponse(200); - - // Confirm the appropriate DOM structures are present for the IPE. - $drupalSettings = NULL; - $matches = []; - if (preg_match('@@', $this->getRawContent(), $matches)) { - $drupalSettings = Json::decode($matches[1]); - $this->verbose('
' . print_r($drupalSettings, TRUE) . '
'); - } - $this->assertNotNull($drupalSettings); - if (!empty($drupalSettings)) { - $this->assertTrue(isset($drupalSettings['panels_ipe'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['regions'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['layout'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['user_permission'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['panels_display'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['unsaved'])); - $this->assertTrue(isset($drupalSettings['panelizer'])); - $this->assertTrue(isset($drupalSettings['panelizer']['entity'])); - $this->assertTrue(isset($drupalSettings['panelizer']['entity']['entity_type_id'])); - $this->assertEqual($drupalSettings['panelizer']['entity']['entity_type_id'], 'node'); - $this->assertTrue(isset($drupalSettings['panelizer']['entity']['entity_id'])); - $this->assertEqual($drupalSettings['panelizer']['entity']['entity_id'], $node->id()); - $this->assertTrue(isset($drupalSettings['panelizer']['user_permission'])); - $this->assertTrue(isset($drupalSettings['panelizer']['user_permission']['revert'])); - $this->assertTrue(isset($drupalSettings['panelizer']['user_permission']['save_default'])); - $this->assertTrue($drupalSettings['panelizer']['user_permission']['revert']); - $this->assertTrue($drupalSettings['panelizer']['user_permission']['save_default']); - } - } - - /** - * @todo Confirm the 'set panelizer default' permission works. - */ - // public function testSetDefault() { - // } - - /** - * @todo Confirm the 'administer panelizer $entity_type_id $bundle defaults' - * permission works. - */ - // public function testAdministerEntityDefaults() { - // } - - /** - * @todo Confirm the 'administer panelizer $entity_type_id $bundle content' - * permission works. - */ - public function testAdministerEntityContentPermission() { - $this->setupContentType($this->content_type); - - // Need the node for the tests below, so create it now. - $node = $this->createTestNode(); - - $perms = [ - 'administer panelizer node page content', - ]; - $drupalSettings = $this->setupPermissionTests($perms, $node); - $this->assertNotNull($drupalSettings); - - // @todo How to tell if the user can change the display or add new items vs - // other tasks? - if (!empty($drupalSettings)) { - $this->assertTrue(isset($drupalSettings['panels_ipe'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['regions'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['layout'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['user_permission'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['panels_display'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['unsaved'])); - $this->assertTrue(isset($drupalSettings['panelizer'])); - $this->assertTrue(isset($drupalSettings['panelizer']['entity'])); - $this->assertTrue(isset($drupalSettings['panelizer']['entity']['entity_type_id'])); - $this->assertEqual($drupalSettings['panelizer']['entity']['entity_type_id'], 'node'); - $this->assertTrue(isset($drupalSettings['panelizer']['entity']['entity_id'])); - $this->assertEqual($drupalSettings['panelizer']['entity']['entity_id'], $node->id()); - $this->assertTrue(isset($drupalSettings['panelizer']['user_permission'])); - $this->assertTrue(isset($drupalSettings['panelizer']['user_permission']['revert'])); - $this->assertTrue(isset($drupalSettings['panelizer']['user_permission']['save_default'])); - } - } - - /** - * @todo Confirm the 'administer panelizer $entity_type_id $bundle layout' - * permission works. - */ - public function testAdministerEntityLayoutPermission() { - $this->setupContentType($this->content_type); - - // Need the node for the tests below, so create it now. - $node = $this->createTestNode(); - - // Test with just the 'layout' permission - $perms = [ - 'administer panelizer node page layout', - ]; - $drupalSettings = $this->setupPermissionTests($perms, $node); - $this->assertNotNull($drupalSettings); - - if (!empty($drupalSettings)) { - $this->assertFalse(isset($drupalSettings['panels_ipe'])); - $this->assertFalse(isset($drupalSettings['panelizer'])); - } - - // Make sure the user is logged out before doing another pass. - $this->drupalLogout(); - - // Test with the 'revert' and the 'content' permission. - $perms = [ - // The permission to be tested. - 'administer panelizer node page layout', - // This permission has to be enabled for the 'revert' permission to work. - 'administer panelizer node page content', - ]; - $drupalSettings = $this->setupPermissionTests($perms, $node); - $this->assertNotNull($drupalSettings); - - // @todo How to tell if the user can change the layout vs other tasks? - if (!empty($drupalSettings)) { - $this->assertTrue(isset($drupalSettings['panels_ipe'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['regions'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['layout'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['user_permission'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['panels_display'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['unsaved'])); - $this->assertTrue(isset($drupalSettings['panelizer'])); - $this->assertTrue(isset($drupalSettings['panelizer']['entity'])); - $this->assertTrue(isset($drupalSettings['panelizer']['entity']['entity_type_id'])); - $this->assertEqual($drupalSettings['panelizer']['entity']['entity_type_id'], 'node'); - $this->assertTrue(isset($drupalSettings['panelizer']['entity']['entity_id'])); - $this->assertEqual($drupalSettings['panelizer']['entity']['entity_id'], $node->id()); - $this->assertTrue(isset($drupalSettings['panelizer']['user_permission'])); - $this->assertTrue(isset($drupalSettings['panelizer']['user_permission']['revert'])); - $this->assertTrue(isset($drupalSettings['panelizer']['user_permission']['save_default'])); - $this->assertFalse($drupalSettings['panelizer']['user_permission']['revert']); - $this->assertFalse($drupalSettings['panelizer']['user_permission']['save_default']); - } - } - - /** - * @todo Confirm the 'administer panelizer $entity_type_id $bundle revert' - * permission works. - */ - public function testAdministerEntityRevertPermission() { - $this->setupContentType($this->content_type); - - // Need the node for the tests below, so create it now. - $node = $this->createTestNode(); - - // Test with just the 'revert' permission - $perms = [ - 'administer panelizer node page revert', - ]; - $drupalSettings = $this->setupPermissionTests($perms, $node); - $this->assertNotNull($drupalSettings); - - if (!empty($drupalSettings)) { - $this->assertFalse(isset($drupalSettings['panels_ipe'])); - $this->assertFalse(isset($drupalSettings['panelizer'])); - } - - // Make sure the user is logged out before doing another pass. - $this->drupalLogout(); - - // Test with the 'revert' and the 'content' permission. - $perms = [ - // The permission to be tested. - 'administer panelizer node page revert', - // This permission has to be enabled for the 'revert' permission to work. - 'administer panelizer node page content', - ]; - $drupalSettings = $this->setupPermissionTests($perms, $node); - $this->assertNotNull($drupalSettings); - - if (!empty($drupalSettings)) { - $this->assertTrue(isset($drupalSettings['panels_ipe'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['regions'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['layout'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['user_permission'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['panels_display'])); - $this->assertTrue(isset($drupalSettings['panels_ipe']['unsaved'])); - $this->assertTrue(isset($drupalSettings['panelizer'])); - $this->assertTrue(isset($drupalSettings['panelizer']['entity'])); - $this->assertTrue(isset($drupalSettings['panelizer']['entity']['entity_type_id'])); - $this->assertEqual($drupalSettings['panelizer']['entity']['entity_type_id'], 'node'); - $this->assertTrue(isset($drupalSettings['panelizer']['entity']['entity_id'])); - $this->assertEqual($drupalSettings['panelizer']['entity']['entity_id'], $node->id()); - $this->assertTrue(isset($drupalSettings['panelizer']['user_permission'])); - $this->assertTrue(isset($drupalSettings['panelizer']['user_permission']['revert'])); - $this->assertTrue(isset($drupalSettings['panelizer']['user_permission']['save_default'])); - $this->assertTrue($drupalSettings['panelizer']['user_permission']['revert']); - $this->assertFalse($drupalSettings['panelizer']['user_permission']['save_default']); - } - } - - /** - * Do the necessary setup work for the individual permissions tests. - * - * @param array $perms - * Any additiona permissions that need to be added. - * @param obj $node - * The node to test against, if none provided one will be generated. - * - * @return array - * The full drupalSettings JSON structure in array format. - */ - protected function setupPermissionTests(array $perms, $node = NULL) { - // Create a new user with the permissions being tested. - $account = $this->createAdminUser($perms); - $this->drupalLogin($account); - - // Make sure there's a test node to work with. - if (empty($node)) { - $node = $this->createTestNode(); - } - - // Load the test node. - $this->drupalGet('node/' . $node->id()); - $this->assertResponse(200); - - // Extract the drupalSettings structure and return it. - $drupalSettings = NULL; - $matches = []; - if (preg_match('@@', $this->getRawContent(), $matches)) { - $drupalSettings = Json::decode($matches[1]); - $this->verbose('
' . print_r($drupalSettings, TRUE) . '
'); - } - return $drupalSettings; - } - -} diff --git a/tests/src/Functional/PanelizerNodeFunctionalTest.php b/tests/src/Functional/PanelizerNodeFunctionalTest.php deleted file mode 100644 index fe57812..0000000 --- a/tests/src/Functional/PanelizerNodeFunctionalTest.php +++ /dev/null @@ -1,163 +0,0 @@ -setupContentType(); - $this->loginUser1(); - $this->panelize('page', NULL, ['panelizer[custom]' => TRUE]); - } - - /** - * Tests the admin interface to set a default layout for a bundle. - */ - public function testWizardUI() { - // Enter the wizard. - $this->drupalGet('admin/structure/panelizer/edit/node__page__default__default'); - $this->assertResponse(200); - $this->assertText('Wizard Information'); - $this->assertField('edit-label'); - - // Contexts step. - $this->clickLink('Contexts'); - $this->assertText('@panelizer.entity_context:entity', 'The current entity context is present.'); - - // Layout selection step. - $this->clickLink('Layout'); - $this->assertSession()->buttonExists('edit-update-layout'); - - // Content step. Add the Node block to the top region. - $this->clickLink('Content'); - $this->clickLink('Add new block'); - $this->clickLink('Title'); - $edit = [ - 'region' => 'content', - ]; - $this->drupalPostForm(NULL, $edit, t('Add block')); - $this->assertResponse(200); - - // Finish the wizard. - $this->drupalPostForm(NULL, [], t('Update and save')); - $this->assertResponse(200); - // Confirm this returned to the main wizard page. - $this->assertText('Wizard Information'); - $this->assertField('edit-label'); - - // Return to the Manage Display page, which is where the Cancel button - // currently sends you. That's a UX WTF and should be fixed... - $this->drupalPostForm(NULL, [], t('Cancel')); - $this->assertResponse(200); - - // Confirm the page is back to the content type settings page. - $this->assertFieldChecked('edit-panelizer-custom'); - - // Now change and save the general setting. - $edit = [ - 'panelizer[custom]' => FALSE, - ]; - $this->drupalPostForm(NULL, $edit, t('Save')); - $this->assertResponse(200); - $this->assertNoFieldChecked('edit-panelizer-custom'); - - // Add another block at the Content step and then save changes. - $this->drupalGet('admin/structure/panelizer/edit/node__page__default__default/content'); - $this->assertResponse(200); - $this->clickLink('Add new block'); - $this->clickLink('Body'); - $edit = [ - 'region' => 'content', - ]; - $this->drupalPostForm(NULL, $edit, t('Add block')); - $this->assertResponse(200); - $this->assertText('entity_field:node:body', 'The body block was added successfully.'); - $this->drupalPostForm(NULL, [], t('Save')); - $this->assertResponse(200); - $this->clickLink('Content'); - $this->assertText('entity_field:node:body', 'The body block was saved successfully.'); - - // Check that the Manage Display tab changed now that Panelizer is set up. - // Also, the field display table should be hidden. - $this->assertNoRaw('
'); - - // Disable Panelizer for the default display mode. This should bring back - // the field overview table at Manage Display and not display the link to - // edit the default Panelizer layout. - $this->unpanelize('page'); - $this->assertNoLinkByHref('admin/structure/panelizer/edit/node__page__default'); - $this->assertRaw('
'); - } - - /** - * Tests rendering a node with Panelizer default. - */ - public function testPanelizerDefault() { - /** @var \Drupal\panelizer\PanelizerInterface $panelizer */ - $panelizer = $this->container->get('panelizer'); - $displays = $panelizer->getDefaultPanelsDisplays('node', 'page', 'default'); - $display = $displays['default']; - $display->addBlock([ - 'id' => 'panelizer_test', - 'label' => 'Panelizer test', - 'provider' => 'block_content', - 'region' => 'content', - ]); - $panelizer->setDefaultPanelsDisplay('default', 'node', 'page', 'default', $display); - - // Create a node, and check that the IPE is visible on it. - $node = $this->drupalCreateNode(['type' => 'page']); - $out = $this->drupalGet('node/' . $node->id()); - $this->assertResponse(200); - $this->verbose($out); - $elements = $this->xpath('//*[@id="panels-ipe-content"]'); - if (is_array($elements)) { - $this->assertIdentical(count($elements), 1); - } - else { - $this->fail('Could not parse page content.'); - } - - // Check that the block we added is visible. - $this->assertText('Panelizer test'); - $this->assertText('Abracadabra'); - } - -} diff --git a/tests/src/Functional/PanelizerNodeTranslationsTest.php b/tests/src/Functional/PanelizerNodeTranslationsTest.php deleted file mode 100644 index 714f92e..0000000 --- a/tests/src/Functional/PanelizerNodeTranslationsTest.php +++ /dev/null @@ -1,200 +0,0 @@ -loginUser1(); - } - - /** - * The entity type being tested. - * - * @var string - */ - protected $entityTypeId = 'node'; - - /** - * The bundle being tested. - * - * @var string - */ - protected $bundle = 'page'; - - /** - * Tests the admin interface to set a default layout for a bundle. - */ - public function _testWizardUI() { - $this->panelize($this->bundle, NULL, ['panelizer[custom]' => TRUE]); - - // Enter the wizard. - $this->drupalGet("admin/structure/panelizer/edit/{$this->entityTypeId}__{$this->bundle}__default__default"); - $this->assertResponse(200); - $this->assertText('Wizard Information'); - $this->assertField('edit-label'); - - // Contexts step. - $this->clickLink('Contexts'); - $this->assertText('@panelizer.entity_context:entity', 'The current entity context is present.'); - - // Layout selection step. - $this->clickLink('Layout'); - $this->assertSession()->buttonExists('edit-update-layout'); - - // Content step. Add the Node block to the top region. - // @todo The index will have to change if the install profile is changed. - $this->clickLink('Content', 1); - $this->clickLink('Add new block'); - $this->clickLink('Title'); - $edit = [ - 'region' => 'content', - ]; - $this->drupalPostForm(NULL, $edit, t('Add block')); - $this->assertResponse(200); - - // Finish the wizard. - $this->drupalPostForm(NULL, [], t('Update and save')); - $this->assertResponse(200); - // Confirm this returned to the main wizard page. - $this->assertText('Wizard Information'); - $this->assertField('edit-label'); - - // Return to the Manage Display page, which is where the Cancel button - // currently sends you. That's a UX WTF and should be fixed... - $this->drupalPostForm(NULL, [], t('Cancel')); - $this->assertResponse(200); - - // Confirm the page is back to the content type settings page. - $this->assertFieldChecked('edit-panelizer-custom'); - - // Now change and save the general setting. - $edit = [ - 'panelizer[custom]' => FALSE, - ]; - $this->drupalPostForm(NULL, $edit, t('Save')); - $this->assertResponse(200); - $this->assertNoFieldChecked('edit-panelizer-custom'); - - // Add another block at the Content step and then save changes. - $this->drupalGet("admin/structure/panelizer/edit/{$this->entityTypeId}__{$this->bundle}__default__default/content"); - $this->assertResponse(200); - $this->clickLink('Add new block'); - $this->clickLink('Body'); - $edit = [ - 'region' => 'content', - ]; - $this->drupalPostForm(NULL, $edit, t('Add block')); - $this->assertResponse(200); - $this->assertText("entity_field:{$this->entityTypeId}:body", 'The body block was added successfully.'); - $this->drupalPostForm(NULL, [], t('Save')); - $this->assertResponse(200); - $this->clickLink('Content', 1); - $this->assertText("entity_field:{$this->entityTypeId}:body", 'The body block was saved successfully.'); - - // Check that the Manage Display tab changed now that Panelizer is set up. - // Also, the field display table should be hidden. - $this->assertNoRaw('
'); - - // Disable Panelizer for the default display mode. This should bring back - // the field overview table at Manage Display and not display the link to - // edit the default Panelizer layout. - $this->unpanelize($this->bundle); - $this->assertNoLinkByHref("admin/structure/panelizer/edit/{$this->entityTypeId}__{$this->bundle}__default"); - $this->assertRaw('
'); - } - - /** - * Tests rendering a node with Panelizer default. - */ - public function testPanelizerDefault() { - $this->panelize($this->bundle, NULL, ['panelizer[custom]' => TRUE]); - /** @var \Drupal\panelizer\PanelizerInterface $panelizer */ - $panelizer = $this->container->get('panelizer'); - $displays = $panelizer->getDefaultPanelsDisplays($this->entityTypeId, $this->bundle, 'default'); - $display = $displays['default']; - $display->addBlock([ - 'id' => 'panelizer_test', - 'label' => 'Panelizer test', - 'provider' => 'block_content', - 'region' => 'content', - ]); - $panelizer->setDefaultPanelsDisplay('default', $this->entityTypeId, $this->bundle, 'default', $display); - - // Create a node, and check that the IPE is visible on it. - $node = $this->drupalCreateNode([ - 'type' => $this->bundle, - 'langcode' => [ - [ - 'value' => 'en', - ], - ], - ]); - $out = $this->drupalGet('node/' . $node->id()); - $this->assertResponse(200); - $this->verbose($out); - $elements = $this->xpath('//*[@id="panels-ipe-content"]'); - if (is_array($elements)) { - $this->assertIdentical(count($elements), 1); - } - else { - $this->fail('Could not parse page content.'); - } - - // Check that the block we added is visible. - $this->assertText('Panelizer test'); - $this->assertText('Abracadabra'); - - // Load the translation page. - $this->clickLink('Translate'); - $this->assertText('English (Original language)'); - $this->assertText('Published'); - $this->assertText('Not translated'); - } - - // @todo Confirm that the different languages of a translated node are loaded properly when using a default display. - // @todo Decide what should happen if a node is translated and has a customized display. - // @todo Confirm loading a referenced block uses the block's correct language. -} diff --git a/tests/src/Functional/PanelizerTermFunctionalTest.php b/tests/src/Functional/PanelizerTermFunctionalTest.php deleted file mode 100644 index 3467f7a..0000000 --- a/tests/src/Functional/PanelizerTermFunctionalTest.php +++ /dev/null @@ -1,122 +0,0 @@ - 'tags', - 'name' => 'Tags', - ])->save(); - - $user = $this->drupalCreateUser([ - 'administer taxonomy', - 'administer taxonomy_term display', - 'edit terms in tags', - 'administer panelizer', - 'access panels in-place editing', - 'administer taxonomy_term fields', - ]); - $this->drupalLogin($user); - - $this->drupalGet('admin/structure/taxonomy/manage/tags/overview/display'); - $edit = [ - 'panelizer[enable]' => TRUE, - 'panelizer[custom]' => TRUE, - ]; - $this->drupalPostForm(NULL, $edit, t('Save')); - $this->assertResponse(200); - $this->rebuildAll(); - } - - /** - * Tests rendering a taxonomy term with Panelizer default. - */ - public function testPanelizerDefault() { - /** @var \Drupal\panelizer\PanelizerInterface $panelizer */ - $panelizer = \Drupal::service('panelizer'); - $displays = $panelizer->getDefaultPanelsDisplays('taxonomy_term', 'tags', 'default'); - $display = $displays['default']; - $display->addBlock([ - 'id' => 'panelizer_test', - 'label' => 'Panelizer test', - 'provider' => 'block_content', - 'region' => 'content', - ]); - $panelizer->setDefaultPanelsDisplay('default', 'taxonomy_term', 'tags', 'default', $display); - - // Create a term, and check that the IPE is visible on it. - $term = $this->createTerm(); - - $out = $this->drupalGet('taxonomy/term/' . $term->id()); - $this->assertResponse(200); - $this->verbose($out); - $elements = $this->xpath('//*[@id="panels-ipe-content"]'); - if (is_array($elements)) { - $this->assertIdentical(count($elements), 1); - } - else { - $this->fail('Could not parse page content.'); - } - - // Check that the block we added is visible. - $this->assertText('Panelizer test'); - $this->assertText('Abracadabra'); - } - - /** - * Create a term. - * - * @return Term; - */ - protected function createTerm() { - $settings = [ - 'description' => [['value' => $this->randomMachineName(32)]], - 'name' => $this->randomMachineName(8), - 'vid' => 'tags', - 'uid' => \Drupal::currentUser()->id(), - ]; - $term = Term::create($settings); - $term->save(); - return $term; - } - -} diff --git a/tests/src/Functional/PanelizerTestTrait.php b/tests/src/Functional/PanelizerTestTrait.php deleted file mode 100644 index 951c0a9..0000000 --- a/tests/src/Functional/PanelizerTestTrait.php +++ /dev/null @@ -1,237 +0,0 @@ -setPassword($password)->save(); - // Support old and new tests. - $account->passRaw = $password; - $account->pass_raw = $password; - $this->drupalLogin($account); - } - - /** - * Prep a content type for use with these tests. - * - * @param string $content_type - * The content type, i.e. the node bundle ID, to configure; defaults to - * 'page'. - */ - protected function setupContentType($content_type = 'page') { - // Log in as user 1. - $this->loginUser1(); - - // Create the content type. - $this->drupalCreateContentType(['type' => $content_type, 'name' => 'Page']); - - // Allow each node to have a customized display. - $this->panelize($content_type, NULL, ['panelizer[custom]' => TRUE]); - - // Logout so that a new user can log in. - $this->drupalLogout(); - } - - /** - * Create a test node. - * - * @param string $type - * The entity type to create, defaults to 'page'. - * - * @return object - * An example node. - */ - protected function createTestNode($type = 'page') { - // Create a test node. - return $this->drupalCreateNode([ - 'title' => t('Hello, world!'), - 'type' => $type, - ]); - } - - /** - * Panelizes a node type's default view display. - * - * @param string $content_type - * The content type, i.e. the node bundle ID, to configure; defaults to - * 'page'. - * @param string $display - * (optional) The view mode to work on. - * @param array $values - * (optional) Additional form values. - */ - protected function panelize($content_type = 'page', $display = NULL, array $values = []) { - $this->drupalGet("admin/structure/types"); - $this->assertResponse(200); - - $this->drupalGet("admin/structure/types/manage/{$content_type}"); - $this->assertResponse(200); - - $path = "admin/structure/types/manage/{$content_type}/display"; - if (!empty($display)) { - $path .= '/' . $display; - } - $this->drupalGet($path); - $this->assertResponse(200); - - $edit = [ - 'panelizer[enable]' => TRUE, - ] + $values; - $this->drupalPostForm(NULL, $edit, t('Save')); - $this->assertResponse(200); - - entity_get_form_display('node', $content_type, 'default') - ->setComponent('panelizer', [ - 'type' => 'panelizer', - ]) - ->save(); - } - - /** - * Unpanelizes a node type's default view display. - * - * Panelizer is disabled for the display, but its configuration is retained. - * - * @param string $content_type - * The content type, i.e. the node bundle ID, to configure; defaults to - * 'page'. - * @param string $display - * (optional) The view mode to work on. - * @param array $values - * (optional) Additional form values. - */ - protected function unpanelize($content_type = 'page', $display = NULL, array $values = []) { - $this->drupalGet("admin/structure/types/manage/{$content_type}/display/{$display}"); - $this->assertResponse(200); - - $edit = [ - 'panelizer[enable]' => FALSE, - ] + $values; - $this->drupalPostForm(NULL, $edit, t('Save')); - $this->assertResponse(200); - - entity_get_form_display('node', $content_type, 'default') - ->removeComponent('panelizer') - ->save(); - } - - /** - * - * - * @param string $content_type - * The content type, i.e. the node bundle ID, to configure; defaults to - * 'page'. - */ - protected function addPanelizerDefault($content_type = 'page', $display = 'default') { - $label = $this->getRandomGenerator()->word(16); - $id = strtolower($label); - $default_id = "node__{$content_type}__{$display}__{$id}"; - $options = [ - 'query' => [ - 'js' => 'nojs', - ], - ]; - $path = "admin/structure/types/manage/{$content_type}/display"; - if (!empty($display)) { - $path .= '/' . $display; - } - $this->drupalGet($path); - $this->assertResponse(200); - $this->clickLink('Add a new Panelizer default display'); - - // Step 1: Enter the default's label and ID. - $edit = [ - 'id' => $id, - 'label' => $label, - ]; - $this->drupalPostForm(NULL, $edit, t('Next')); - $this->assertResponse(200); - - // Step 2: Define contexts. - $this->assertUrl("admin/structure/panelizer/add/{$default_id}/contexts", $options); - $this->drupalPostForm(NULL, [], t('Next')); - $this->assertResponse(200); - - // Step 3: Select layout. - $this->assertUrl("admin/structure/panelizer/add/{$default_id}/layout", $options); - $this->drupalPostForm(NULL, [], t('Next')); - $this->assertResponse(200); - - // Step 4: Select content. - $this->assertUrl("admin/structure/panelizer/add/{$default_id}/content", $options); - $this->drupalPostForm(NULL, [], t('Finish')); - $this->assertResponse(200); - - return $id; - } - - /** - * Deletes a Panelizer default. - * - * @param string $content_type - * The content type, i.e. the node bundle ID, to configure; defaults to - * 'page'. - * @param string $display - * (optional) The view mode to work on. - * @param string $id - * (optional) The default ID. - */ - protected function deletePanelizerDefault($content_type = 'page', $display = 'default', $id = 'default') { - $this->drupalGet("admin/structure/panelizer/delete/node__{$content_type}__{$display}__{$id}"); - $this->assertResponse(200); - $this->drupalPostForm(NULL, [], t('Confirm')); - $this->assertResponse(200); - } - - /** - * Asserts that a Panelizer default exists. - * - * @param string $content_type - * The content type, i.e. the node bundle ID, to configure; defaults to - * 'page'. - * @param string $display - * (optional) The view mode to work on. - * @param string $id - * (optional) The default ID. - */ - protected function assertDefaultExists($content_type = 'page', $display = 'default', $id = 'default') { - $settings = entity_get_display('node', $content_type, $display) - ->getThirdPartySettings('panelizer'); - - $display_exists = isset($settings['displays'][$id]); - $this->assertTrue($display_exists); - } - - /** - * Asserts that a Panelizer default does not exist. - * - * @param string $content_type - * The content type, i.e. the node bundle ID, to configure; defaults to - * 'page'. - * @param string $display - * (optional) The view mode to work on. - * @param string $id - * The default ID. - */ - protected function assertDefaultNotExists($content_type = 'page', $display = 'default', $id = 'default') { - $settings = entity_get_display('node', $content_type, $display) - ->getThirdPartySettings('panelizer'); - - $display_exists = isset($settings['displays'][$id]); - $this->assertFalse($display_exists); - } - -} diff --git a/tests/src/Functional/PanelizerUserFunctionalTest.php b/tests/src/Functional/PanelizerUserFunctionalTest.php deleted file mode 100644 index 73aa092..0000000 --- a/tests/src/Functional/PanelizerUserFunctionalTest.php +++ /dev/null @@ -1,111 +0,0 @@ -drupalCreateUser([ - // Required for Panelizer. - 'administer panelizer', - 'access panels in-place editing', - // Allow managing user entities. - 'administer users', - // Allow managing user entity settings. - 'administer account settings', - // View access to user profiles. - 'access user profiles', - // Allow managing the user entity fields and display settings. - 'administer user display', - 'administer user fields', - ]); - $this->drupalLogin($user); - - // Enable Panelizer for this entity. - $this->drupalGet('admin/config/people/accounts/display'); - $this->assertResponse(200); - $edit = [ - 'panelizer[enable]' => TRUE, - 'panelizer[custom]' => TRUE, - ]; - $this->drupalPostForm(NULL, $edit, t('Save')); - $this->assertResponse(200); - - // Reload all caches. - $this->rebuildAll(); - } - - /** - * Tests rendering a user with Panelizer default. - */ - public function testPanelizerDefault() { - /** @var \Drupal\panelizer\PanelizerInterface $panelizer */ - $panelizer = \Drupal::service('panelizer'); - $displays = $panelizer->getDefaultPanelsDisplays('user', 'user', 'default'); - $display = $displays['default']; - $display->addBlock([ - 'id' => 'panelizer_test', - 'label' => 'Panelizer test', - 'provider' => 'block_content', - 'region' => 'content', - ]); - $panelizer->setDefaultPanelsDisplay('default', 'user', 'user', 'default', $display); - - // Create a user, and check that the IPE is visible on it. - $account = $this->drupalCreateUser(); - - // Check the user entity page. - $out = $this->drupalGet('user/' . $account->id()); - $this->assertResponse(200); - $this->verbose($out); - - // Verify that - $elements = $this->xpath('//*[@id="panels-ipe-content"]'); - if (is_array($elements)) { - $this->assertIdentical(count($elements), 1); - } - else { - $this->fail('Could not parse page content.'); - } - - // Check that the block we added is visible. - $this->assertText('Panelizer test'); - $this->assertText('Abracadabra'); - } - -} diff --git a/tests/src/FunctionalJavascript/PanelizerIntegrationTest.php b/tests/src/FunctionalJavascript/PanelizerIntegrationTest.php deleted file mode 100644 index d9af169..0000000 --- a/tests/src/FunctionalJavascript/PanelizerIntegrationTest.php +++ /dev/null @@ -1,87 +0,0 @@ -drupalCreateUser([ - 'access content', - 'access panels in-place editing', - 'administer blocks', - 'administer content types', - 'administer nodes', - 'administer node display', - 'administer panelizer', - ]); - $this->drupalLogin($admin_user); - - // Create the "Basic Page" content type. - $this->createContentType([ - 'type' => 'page', - 'name' => 'Basic Page', - ]); - - // Enable Panelizer for the "Basic Page" content type. - $this->drupalGet('admin/structure/types/manage/page/display'); - $this->submitForm(['panelizer[enable]' => 1], t('Save')); - - // Create a new Basic Page. - $this->drupalGet('node/add/page'); - $this->submitForm(['title[0][value]' => 'Test Node'], t('Save')); - - $this->test_route = 'node/1'; - } - - /** - * Tests that the IPE editing session is specific to a user. - */ - public function testUserEditSession() { - $this->visitIPERoute(); - $this->assertSession()->elementExists('css', '.layout--onecol'); - - // Change the layout to lock the IPE. - $this->changeLayout('Columns: 2', 'layout_twocol'); - $this->assertSession()->elementExists('css', '.layout--twocol'); - $this->assertSession()->elementNotExists('css', '.layout--onecol'); - - // Create a second node. - $this->drupalGet('node/add/page'); - $this->submitForm(['title[0][value]' => 'Test Node 2'], t('Save')); - $this->test_route = 'node/2'; - - // Ensure the second node does not use the session of the other node. - $this->visitIPERoute(); - $this->assertSession()->elementExists('css', '.layout--onecol'); - $this->assertSession()->elementNotExists('css', '.layout--twocol'); - } - -} diff --git a/tests/src/Unit/PanelizerDefaultPanelsStorageTest.php b/tests/src/Unit/PanelizerDefaultPanelsStorageTest.php deleted file mode 100644 index 6f14453..0000000 --- a/tests/src/Unit/PanelizerDefaultPanelsStorageTest.php +++ /dev/null @@ -1,271 +0,0 @@ -storage = $this->prophesize(EntityStorageInterface::class); - - $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class); - $this->entityTypeManager->getStorage('entity_type_id')->willReturn($this->storage->reveal()); - - $this->panelizer = $this->prophesize(Panelizer::class); - - $this->panelsStorage = $this->getMockBuilder(PanelizerDefaultPanelsStorage::class) - ->setConstructorArgs([ - [], - '', - [], - $this->entityTypeManager->reveal(), - $this->panelizer->reveal(), - ]) - ->setMethods(['getEntityContext']) - ->getMock(); - } - - /** - * @covers ::load - */ - public function testLoadEmptyContext() { - $entity_context = $this->prophesize(AutomaticContext::class); - - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $panels_display->setContexts([ - '@panelizer.entity_context:entity' => $entity_context->reveal(), - ])->shouldBeCalled(); - - $this->panelizer->getDefaultPanelsDisplay('default', 'entity_type_id', 'bundle', 'view_mode', NULL) - ->willReturn($panels_display->reveal()); - - $this->panelizer - ->getDisplayStaticContexts('default', 'entity_type_id', 'bundle', 'view_mode') - ->willReturn([]); - - $this->panelsStorage->method('getEntityContext') - ->with($this->equalTo('entity_type_id'), $this->isNull()) - ->willReturn([ - '@panelizer.entity_context:entity' => $entity_context->reveal(), - ]); - - $this->assertSame($panels_display->reveal(), $this->panelsStorage->load('entity_type_id:bundle:view_mode:default')); - } - - /** - * @covers ::load - */ - public function testLoadWithContextValue() { - $entity_context = $this->prophesize(AutomaticContext::class); - - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $panels_display->setContexts([ - '@panelizer.entity_context:entity' => $entity_context->reveal(), - ])->shouldBeCalled(); - - $this->panelizer->getDefaultPanelsDisplay('default', 'entity_type_id', 'bundle', 'view_mode', NULL) - ->willReturn($panels_display->reveal()); - - $this->panelizer - ->getDisplayStaticContexts('default', 'entity_type_id', 'bundle', 'view_mode') - ->willReturn([]); - - $entity = $this->prophesize(EntityInterface::class); - $entity->bundle()->willReturn("bundle"); - $this->storage->load('123')->willReturn($entity->reveal())->shouldBeCalled(); - - $this->panelsStorage->method('getEntityContext') - ->with($this->equalTo('entity_type_id'), $entity->reveal()) - ->willReturn([ - '@panelizer.entity_context:entity' => $entity_context->reveal(), - ]); - - $this->assertSame($panels_display->reveal(), $this->panelsStorage->load('*entity_type_id:123:view_mode:default')); - } - - /** - * @covers ::load - */ - public function testLoadDoesntExist() { - $this->panelizer->getDefaultPanelsDisplay('default', 'entity_type_id', 'bundle', 'view_mode', NULL) - ->willReturn(NULL); - - $this->assertSame(NULL, $this->panelsStorage->load('entity_type_id:bundle:view_mode:default')); - } - - /** - * @covers ::load - */ - public function testLoadNoEntity() { - $this->storage->load('123')->willReturn(NULL)->shouldBeCalled(); - - $this->panelizer->getDefaultPanelsDisplay('default', 'entity_type_id', 'bundle', 'view_mode', NULL) - ->shouldNotBeCalled(); - - $this->assertSame(NULL, $this->panelsStorage->load('*entity_type_id:123:view_mode:default')); - } - - /** - * @covers ::save - */ - public function testSaveSuccessful() { - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $panels_display->getStorageId()->willReturn('entity_type_id:bundle:view_mode:default'); - - $this->panelizer->setDefaultPanelsDisplay('default', 'entity_type_id', 'bundle', 'view_mode', $panels_display->reveal()) - ->shouldBeCalled(); - - $this->panelsStorage->save($panels_display->reveal()); - } - - /** - * @covers ::save - * - * @expectedException \Exception - * @expectedExceptionMessage Couldn't find Panelizer default to store Panels display - */ - public function testSaveDoesntExist() { - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $panels_display->getStorageId()->willReturn('entity_type_id:bundle:view_mode:default'); - - $this->panelizer->setDefaultPanelsDisplay('default', 'entity_type_id', 'bundle', 'view_mode', $panels_display->reveal()) - ->willThrow(new PanelizerException()); - - $this->panelsStorage->save($panels_display->reveal()); - } - - /** - * @covers ::save - * - * @expectedException \Exception - * @expectedExceptionMessage Couldn't find Panelizer default to store Panels display - */ - public function testSaveNoEntity() { - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $panels_display->getStorageId()->willReturn('*entity_type_id:123:view_mode:default'); - - $this->storage->load('123')->willReturn(NULL)->shouldBeCalled(); - - $this->panelizer->setDefaultPanelsDisplay('default', 'entity_type_id', 'bundle', 'view_mode', $panels_display->reveal()) - ->shouldNotBeCalled(); - - $this->panelsStorage->save($panels_display->reveal()); - } - - /** - * @covers ::access - */ - public function testAccessRead() { - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $account = $this->prophesize(AccountInterface::class); - - $this->panelizer->getDefaultPanelsDisplay('default', 'entity_type_id', 'bundle', 'view_mode') - ->willReturn($panels_display->reveal()); - $this->panelizer->hasDefaultPermission()->shouldNotBeCalled(); - - $this->assertEquals(AccessResult::allowed(), $this->panelsStorage->access('entity_type_id:bundle:view_mode:default', 'read', $account->reveal())); - } - - /** - * @covers ::access - */ - public function testAccessNotFound() { - $account = $this->prophesize(AccountInterface::class); - - $this->panelizer->getDefaultPanelsDisplay('default', 'entity_type_id', 'bundle', 'view_mode') - ->willReturn(NULL); - $this->panelizer->hasDefaultPermission()->shouldNotBeCalled(); - - $this->assertEquals(AccessResult::forbidden(), $this->panelsStorage->access('entity_type_id:bundle:view_mode:default', 'read', $account->reveal())); - } - - /** - * @covers ::access - */ - public function testAccessNoEntity() { - $account = $this->prophesize(AccountInterface::class); - - $this->storage->load('123')->willReturn(NULL)->shouldBeCalled(); - - $this->panelizer->getDefaultPanelsDisplay('default', 'entity_type_id', 'bundle', 'view_mode') - ->shouldNotBeCalled(); - - $this->assertEquals(AccessResult::forbidden(), $this->panelsStorage->access('*entity_type_id:123:view_mode:default', 'read', $account->reveal())); - } - - /** - * @covers ::access - */ - public function testAccessChangeContent() { - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $account = $this->prophesize(AccountInterface::class); - - $this->panelizer->getDefaultPanelsDisplay('default', 'entity_type_id', 'bundle', 'view_mode') - ->willReturn($panels_display->reveal()); - $this->panelizer->hasDefaultPermission('change content', 'entity_type_id', 'bundle', 'view_mode', 'default', $account->reveal()) - ->willReturn(TRUE); - - $this->assertEquals(AccessResult::allowed(), $this->panelsStorage->access('entity_type_id:bundle:view_mode:default', 'update', $account->reveal())); - $this->assertEquals(AccessResult::allowed(), $this->panelsStorage->access('entity_type_id:bundle:view_mode:default', 'delete', $account->reveal())); - $this->assertEquals(AccessResult::allowed(), $this->panelsStorage->access('entity_type_id:bundle:view_mode:default', 'create', $account->reveal())); - } - - /** - * @covers ::access - */ - public function testAccessChangeLayout() { - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $account = $this->prophesize(AccountInterface::class); - - $this->panelizer->getDefaultPanelsDisplay('default', 'entity_type_id', 'bundle', 'view_mode') - ->willReturn($panels_display->reveal()); - $this->panelizer->hasDefaultPermission('change layout', 'entity_type_id', 'bundle', 'view_mode', 'default', $account->reveal()) - ->willReturn(TRUE); - - $this->assertEquals(AccessResult::allowed(), $this->panelsStorage->access('entity_type_id:bundle:view_mode:default', 'change layout', $account->reveal())); - } - -} diff --git a/tests/src/Unit/PanelizerEntityViewBuilderTest.php b/tests/src/Unit/PanelizerEntityViewBuilderTest.php deleted file mode 100644 index b934da9..0000000 --- a/tests/src/Unit/PanelizerEntityViewBuilderTest.php +++ /dev/null @@ -1,370 +0,0 @@ -entityType = $this->prophesize(EntityTypeInterface::class); - $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class); - $this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class); - $this->panelizer = $this->prophesize(PanelizerInterface::class); - $this->panelizerManager = $this->prophesize(PanelizerEntityManagerInterface::class); - $this->panelsManager = $this->prophesize(PanelsDisplayManagerInterface::class); - - $this->entityType->id() - ->willReturn('entity_type_id'); - - $this->entityViewBuilder = $this->getMockBuilder(PanelizerEntityViewBuilder::class) - ->setConstructorArgs([ - $this->entityType->reveal(), - $this->entityTypeManager->reveal(), - $this->moduleHandler->reveal(), - $this->panelizer->reveal(), - $this->panelizerManager->reveal(), - $this->panelsManager->reveal() - ]) - ->setMethods(['getFallbackViewBuilder', 'getPanelizerPlugin', 'collectRenderDisplays', 'getEntityContext']) - ->getMock(); - - $this->fallbackViewBuilder = $this->prophesize(EntityViewBuilderInterface::class); - $this->panelizerPlugin = $this->prophesize(PanelizerEntityInterface::class); - - $this->entityViewBuilder->method('getFallbackViewBuilder') - ->willReturn($this->fallbackViewBuilder->reveal()); - $this->entityViewBuilder->method('getPanelizerPlugin') - ->willReturn($this->panelizerPlugin->reveal()); - } - - /** - * Tests buildComponents(). - * - * @covers ::buildComponents - */ - public function testBuildComponents() { - $build = ['random_value' => 123]; - - $entity1 = $this->prophesize(FieldableEntityInterface::class); - $entity1->bundle()->willReturn('abc'); - - $display1 = $this->prophesize(EntityViewDisplayInterface::class); - $display1->getThirdPartySetting('panelizer', 'enable', FALSE) - ->willReturn(TRUE); - - $entity2 = $this->prophesize(FieldableEntityInterface::class); - $entity2->bundle()->willReturn('xyz'); - - $display2 = $this->prophesize(EntityViewDisplayInterface::class); - $display2->getThirdPartySetting('panelizer', 'enable', FALSE) - ->willReturn(FALSE); - - $displays = [ - 'abc' => $display1->reveal(), - 'xyz' => $display2->reveal(), - ]; - - $this->fallbackViewBuilder->buildComponents($build, [234 => $entity2->reveal()], $displays, 'full') - ->shouldBeCalled(); - - $this->moduleHandler->invokeAll('entity_prepare_view', [ - 'entity_type_id', - [123 => $entity1->reveal()], - $displays, - 'full' - ])->shouldBeCalled(); - - $this->entityViewBuilder->buildComponents( - $build, - [ - 123 => $entity1->reveal(), - 234 => $entity2->reveal() - ], - $displays, - 'full' - ); - } - - /** - * Setups up the mock objects for testing view() and viewMultiple(). - * - * @return array - * An associative array with the following keys: - * - entities: Associative array of the mocked entity objects, keyed by the - * id. - * - expected: Associative array of the built render arrays keyed by the - * entity id. - */ - protected function setupView() { - $entity1 = $this->prophesize(FieldableEntityInterface::class); - $entity1->bundle()->willReturn('abc'); - $entity1->getEntityTypeId()->willReturn('entity_type_id'); - $entity1->id()->willReturn(123); - $entity1->getCacheContexts()->willReturn(['context']); - $entity1->getCacheTags()->willReturn(['tag']); - $entity1->getCacheMaxAge()->willReturn(123); - - $display1 = $this->prophesize(EntityViewDisplayInterface::class); - $display1->getThirdPartySetting('panelizer', 'enable', FALSE) - ->willReturn(TRUE); - - $entity2 = $this->prophesize(FieldableEntityInterface::class); - $entity2->bundle()->willReturn('xyz'); - $entity2->getEntityTypeId()->willReturn('entity_type_id'); - - $display2 = $this->prophesize(EntityViewDisplayInterface::class); - $display2->getThirdPartySetting('panelizer', 'enable', FALSE) - ->willReturn(FALSE); - - $this->entityViewBuilder - ->method('collectRenderDisplays') - ->willReturn([ - 'abc' => $display1->reveal(), - 'xyz' => $display2->reveal() - ]); - - $entity_context = $this->prophesize(AutomaticContext::class); - $this->entityViewBuilder->method('getEntityContext') - ->willReturn($entity_context->reveal()); - - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $other_context = $this->prophesize(AutomaticContext::class); - $panels_display->getContexts() - ->willReturn(['other' => $other_context->reveal()]); - $panels_display->setContexts([ - 'other' => $other_context->reveal(), - '@panelizer.entity_context:entity' => $entity_context->reveal(), - ])->shouldBeCalled(); - $panels_display->build()->willReturn(['#markup' => 'Panelized']); - - $this->panelizer - ->getPanelizerSettings('entity_type_id', 'abc', 'full', $display1->reveal()) - ->willReturn([ - 'default' => 'default', - ]); - - $this->panelizer - ->getDisplayStaticContexts('default', 'entity_type_id', 'abc', 'full', $display1->reveal()) - ->willReturn([ - 'other' => $other_context->reveal(), - '@panelizer.entity_context:entity' => $entity_context->reveal(), - ]); - - $this->panelizer->getPanelsDisplay($entity1->reveal(), 'full', $display1->reveal()) - ->willReturn($panels_display->reveal()); - - $panels_display->getCacheContexts()->willReturn([]); - $panels_display->getCacheTags()->willReturn([]); - $panels_display->getCacheMaxAge()->willReturn(-1); - - return [ - 'entities' => [ - 123 => $entity1->reveal(), - 234 => $entity2->reveal(), - ], - 'expected' => [ - 123 => [ - '#theme' => [ - 'panelizer_view_mode__entity_type_id__123', - 'panelizer_view_mode__entity_type_id__abc', - 'panelizer_view_mode__entity_type_id', - 'panelizer_view_mode', - ], - '#panelizer_plugin' => $this->panelizerPlugin->reveal(), - '#panels_display' => $panels_display->reveal(), - '#entity' => $entity1->reveal(), - '#view_mode' => 'full', - '#langcode' => 'pl', - 'content' => [ - '#markup' => 'Panelized', - ], - '#cache' => [ - 'tags' => ['tag'], - 'contexts' => ['context'], - 'max-age' => 123, - ], - ], - 234 => [ - '#markup' => 'Fallback', - ], - ] - ]; - } - - /** - * Tests view(). - * - * @covers ::view - */ - public function testView() { - $data = $this->setupView(); - $entities = $data['entities']; - $expected = $data['expected']; - - $this->fallbackViewBuilder->view($entities[234], 'full', 'pl') - ->willReturn(['#markup' => 'Fallback']); - - $this->assertEquals($expected[123], $this->entityViewBuilder->view($entities[123], 'full', 'pl')); - $this->assertEquals($expected[234], $this->entityViewBuilder->view($entities[234], 'full', 'pl')); - } - - /** - * Tests viewMultiple(). - * - * @covers ::viewMultiple - */ - public function testViewMultiple() { - $data = $this->setupView(); - $entities = $data['entities']; - $expected = $data['expected']; - - $this->fallbackViewBuilder->viewMultiple([234 => $entities[234]], 'full', 'pl') - ->willReturn([234 => ['#markup' => 'Fallback']]); - - $this->assertEquals($expected, $this->entityViewBuilder->viewMultiple($entities, 'full', 'pl')); - } - - /** - * Tests resetCache(). - * - * @covers ::resetCache - */ - public function testResetCache() { - $entities = [ - $this->prophesize(EntityInterface::class)->reveal(), - $this->prophesize(EntityInterface::class)->reveal(), - ]; - $this->fallbackViewBuilder->resetCache($entities)->shouldBeCalled(); - $this->entityViewBuilder->resetCache($entities); - } - - /** - * Tests viewField(). - * - * @covers ::viewField - */ - public function testViewField() { - $items = $this->prophesize(FieldItemListInterface::class)->reveal(); - $display_options = ['abc' => 123]; - $this->fallbackViewBuilder->viewField($items, $display_options) - ->willReturn(['#markup' => 'field']); - $this->assertEquals(['#markup' => 'field'], $this->entityViewBuilder->viewField($items, $display_options)); - } - - /** - * Tests viewFieldItem(). - * - * @covers ::viewFieldItem - */ - public function testViewFieldItem() { - $item = $this->prophesize(FieldItemInterface::class)->reveal(); - $display = ['abc' => 123]; - $this->fallbackViewBuilder->viewFieldItem($item, $display) - ->willReturn(['#markup' => 'item']); - $this->assertEquals(['#markup' => 'item'], $this->entityViewBuilder->viewFieldItem($item, $display)); - } - - /** - * Tests getCacheTags(). - * - * @covers ::getCacheTags - */ - public function testGetCacheTags() { - $this->fallbackViewBuilder->getCacheTags() - ->willReturn(['tag']); - $this->assertEquals(['tag'], $this->entityViewBuilder->getCacheTags()); - } - -} diff --git a/tests/src/Unit/PanelizerFieldPanelsStorageTest.php b/tests/src/Unit/PanelizerFieldPanelsStorageTest.php deleted file mode 100644 index 089ddc1..0000000 --- a/tests/src/Unit/PanelizerFieldPanelsStorageTest.php +++ /dev/null @@ -1,277 +0,0 @@ -storage = $this->prophesize(EntityStorageInterface::class); - - $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class); - $this->entityTypeManager->getStorage('entity_type_id')->willReturn($this->storage->reveal()); - - $this->panelizer = $this->prophesize(Panelizer::class); - - $this->panelsStorage = $this->getMockBuilder(PanelizerFieldPanelsStorage::class) - ->setConstructorArgs([ - [], - '', - [], - $this->entityTypeManager->reveal(), - $this->panelizer->reveal(), - ]) - ->setMethods(['getEntityContext']) - ->getMock(); - } - - /** - * @covers ::load - */ - public function testLoad() { - $entity_context = $this->prophesize(AutomaticContext::class); - - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $panels_display->setContexts([ - '@panelizer.entity_context:entity' => $entity_context->reveal(), - ])->shouldBeCalled(); - - $entity = $this->prophesize(FieldableEntityInterface::class); - - $this->panelizer->getPanelsDisplay($entity->reveal(), 'view_mode') - ->willReturn($panels_display->reveal()); - - $this->storage->load('123')->willReturn($entity->reveal())->shouldBeCalled(); - - $this->panelsStorage->method('getEntityContext') - ->with($this->equalTo('entity_type_id'), $entity->reveal()) - ->willReturn($entity_context->reveal()); - - $this->assertSame($panels_display->reveal(), $this->panelsStorage->load('entity_type_id:123:view_mode')); - } - - /** - * @covers ::load - */ - public function testLoadRevision() { - $entity_context = $this->prophesize(AutomaticContext::class); - - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $panels_display->setContexts([ - '@panelizer.entity_context:entity' => $entity_context->reveal(), - ])->shouldBeCalled(); - - $entity = $this->prophesize(FieldableEntityInterface::class); - - $this->panelizer->getPanelsDisplay($entity->reveal(), 'view_mode') - ->willReturn($panels_display->reveal()); - - $this->storage->loadRevision('456')->willReturn($entity->reveal())->shouldBeCalled(); - - $this->panelsStorage->method('getEntityContext') - ->with($this->equalTo('entity_type_id'), $entity->reveal()) - ->willReturn($entity_context->reveal()); - - $this->assertSame($panels_display->reveal(), $this->panelsStorage->load('entity_type_id:123:view_mode:456')); - } - - /** - * @covers ::load - */ - public function testLoadNoEntity() { - $this->storage->load('123')->willReturn(NULL)->shouldBeCalled(); - - $this->panelizer->getPanelsDisplay()->shouldNotBeCalled(); - - $this->assertSame(NULL, $this->panelsStorage->load('entity_type_id:123:view_mode')); - } - - /** - * @covers ::load - */ - public function testLoadNotFound() { - $entity = $this->prophesize(FieldableEntityInterface::class); - - $this->storage->load('123')->willReturn($entity->reveal()); - - $this->panelizer->getPanelsDisplay($entity->reveal(), 'view_mode') - ->willReturn(NULL); - - $this->assertSame(NULL, $this->panelsStorage->load('entity_type_id:123:view_mode')); - } - - /** - * @covers ::save - */ - public function testSaveSuccessful() { - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $panels_display->getStorageId()->willReturn('entity_type_id:123:view_mode'); - - $entity = $this->prophesize(FieldableEntityInterface::class); - - $this->panelizer->setPanelsDisplay($entity->reveal(), 'view_mode', NULL, $panels_display) - ->shouldBeCalled(); - - $this->storage->load('123')->willReturn($entity->reveal())->shouldBeCalled(); - - $this->panelsStorage->save($panels_display->reveal()); - } - - /** - * @covers ::save - * - * @expectedException \Exception - * @expectedExceptionMessage Couldn't find entity to store Panels display on - */ - public function testSaveNoEntity() { - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $panels_display->getStorageId()->willReturn('entity_type_id:123:view_mode'); - - $this->panelizer->setPanelsDisplay()->shouldNotBeCalled(); - - $this->storage->load('123')->willReturn(NULL)->shouldBeCalled(); - - $this->panelsStorage->save($panels_display->reveal()); - } - - /** - * @covers ::save - * - * @expectedException \Exception - * @expectedExceptionMessage Save failed - */ - public function testSaveFailed() { - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $panels_display->getStorageId()->willReturn('entity_type_id:123:view_mode'); - - $entity = $this->prophesize(FieldableEntityInterface::class); - - $this->panelizer->setPanelsDisplay($entity->reveal(), 'view_mode', NULL, $panels_display) - ->willThrow(new PanelizerException("Save failed")); - - $this->storage->load('123')->willReturn($entity->reveal())->shouldBeCalled(); - - $this->panelsStorage->save($panels_display->reveal()); - } - - /** - * @covers ::access - */ - public function testAccessRead() { - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $account = $this->prophesize(AccountInterface::class); - - $entity = $this->prophesize(FieldableEntityInterface::class); - $entity->access('view', $account->reveal(), TRUE) - ->willReturn(AccessResult::allowed()); - - $this->storage->load('123')->willReturn($entity->reveal()); - - $this->panelizer->getPanelsDisplay($entity->reveal(), 'view_mode') - ->willReturn($panels_display->reveal()); - $this->panelizer->hasEntityPermission()->shouldNotBeCalled(); - - $this->assertEquals(AccessResult::allowed(), $this->panelsStorage->access('entity_type_id:123:view_mode', 'read', $account->reveal())); - } - - /** - * @covers ::access - */ - public function testAccessNoEntity() { - $account = $this->prophesize(AccountInterface::class); - - $this->storage->load('123')->willReturn(NULL)->shouldBeCalled(); - - $this->panelizer->getPanelsDisplay()->shouldNotBeCalled(); - - $this->assertEquals(AccessResult::forbidden(), $this->panelsStorage->access('entity_type_id:123:view_mode', 'read', $account->reveal())); - } - - - /** - * @covers ::access - */ - public function testAccessChangeContent() { - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $account = $this->prophesize(AccountInterface::class); - - $entity = $this->prophesize(FieldableEntityInterface::class); - $entity->access('update', $account->reveal(), TRUE) - ->willReturn(AccessResult::allowed()); - - $this->storage->load('123')->willReturn($entity->reveal()); - - $this->panelizer->getPanelsDisplay($entity->reveal(), 'view_mode') - ->willReturn($panels_display->reveal()); - $this->panelizer->hasEntityPermission('change content', $entity->reveal(), 'view_mode', $account->reveal()) - ->willReturn(TRUE); - - $access = $this->panelsStorage->access('entity_type_id:123:view_mode', 'update', $account->reveal()); - $this->assertEquals(AccessResult::allowed(), $access); - } - - /** - * @covers ::access - */ - public function testAccessChangeLayout() { - $panels_display = $this->prophesize(PanelsDisplayVariant::class); - $account = $this->prophesize(AccountInterface::class); - - $entity = $this->prophesize(FieldableEntityInterface::class); - $entity->access('update', $account->reveal(), TRUE) - ->willReturn(AccessResult::allowed()); - - $this->storage->load('123')->willReturn($entity->reveal()); - - $this->panelizer->getPanelsDisplay($entity->reveal(), 'view_mode') - ->willReturn($panels_display->reveal()); - $this->panelizer->hasEntityPermission('change layout', $entity->reveal(), 'view_mode', $account->reveal()) - ->willReturn(TRUE); - - $this->assertEquals(AccessResult::allowed(), $this->panelsStorage->access('entity_type_id:123:view_mode', 'change layout', $account->reveal())); - } - -}