diff --git a/core/modules/page/config/page.front_page.yml b/core/modules/page/config/page.front_page.yml new file mode 100644 index 0000000..7562328 --- /dev/null +++ b/core/modules/page/config/page.front_page.yml @@ -0,0 +1,8 @@ +id: front_page +uuid: 5da81acf-3264-4d91-9b00-b2461c86e974 +label: 'Front page' +visibility: '1' +paths: '' +layout: 'static_layout:page__complex' +langcode: und +weight: 1 diff --git a/core/modules/page/config/page.not_admin_page.yml b/core/modules/page/config/page.not_admin_page.yml new file mode 100644 index 0000000..c7d1032 --- /dev/null +++ b/core/modules/page/config/page.not_admin_page.yml @@ -0,0 +1,8 @@ +id: not_admin_page +uuid: 45e82840-d11b-4726-8d09-9be84a69457d +label: 'Not admin page' +visibility: '0' +paths: "admin\r\nadmin/*" +layout: 'static_layout:page__simple' +langcode: und +weight: 10 diff --git a/core/modules/page/config/page.user_page.yml b/core/modules/page/config/page.user_page.yml new file mode 100644 index 0000000..ce86333 --- /dev/null +++ b/core/modules/page/config/page.user_page.yml @@ -0,0 +1,8 @@ +id: user_page +uuid: ccd213ff-54d4-4f3a-9a00-f8406842dff0 +label: 'User page' +visibility: '1' +paths: "user\r\nuser/*" +layout: 'static_layout:page__complex' +langcode: und +weight: 5 diff --git a/core/modules/page/layouts/static/complex/complex.yml b/core/modules/page/layouts/static/complex/complex.yml new file mode 100644 index 0000000..ec6491a --- /dev/null +++ b/core/modules/page/layouts/static/complex/complex.yml @@ -0,0 +1,2 @@ +title: Complex layout +template: complex diff --git a/core/modules/page/layouts/static/simple/simple.yml b/core/modules/page/layouts/static/simple/simple.yml new file mode 100644 index 0000000..3c395fa --- /dev/null +++ b/core/modules/page/layouts/static/simple/simple.yml @@ -0,0 +1,2 @@ +title: Simple layout +template: simple diff --git a/core/modules/page/lib/Drupal/page/Page.php b/core/modules/page/lib/Drupal/page/Page.php new file mode 100644 index 0000000..bfd835c --- /dev/null +++ b/core/modules/page/lib/Drupal/page/Page.php @@ -0,0 +1,66 @@ + 'textfield', + '#title' => t('Label'), + '#maxlength' => 255, + '#default_value' => $page->label(), + '#description' => t("Example: 'Front page' or 'Section page'."), + '#required' => TRUE, + ); + $form['id'] = array( + '#type' => 'machine_name', + '#default_value' => $page->id(), + '#machine_name' => array( + 'exists' => 'page_load', + 'source' => array('label'), + ), + '#disabled' => !$page->isNew(), + ); + + // Get list of layouts and expose that for page layout selection. + $layouts = drupal_container()->get('plugin.manager.layout')->getDefinitions(); + $layout_options = array(); + foreach ($layouts as $key => $layout) { + $layout_options[$key] = $layout['title']; + } + $form['layout'] = array( + '#type' => 'select', + '#title' => t('Layout for this page'), + '#default_value' => isset($page->layout) ? $page->layout : '', + '#options' => $layout_options, + ); + + // @todo this would ideally be pluggable and depend on general conditions + // and all, however, these are not yet abstracted. + $options = array( + 0 => t('All paths except those listed'), + 1 => t('Only the listed paths'), + ); + $description = t("Enter one path per line. The '*' character is a wildcard. Example paths are %user for the current user's page and %user-wildcard for every user page. %front is the front page.", array('%user' => 'user', '%user-wildcard' => 'user/*', '%front' => '')); + $form['visibility'] = array( + '#type' => 'radios', + '#title' => t('Apply to specific paths'), + '#options' => $options, + '#default_value' => isset($page->visibility) ? $page->visibility : 0, + ); + $form['paths'] = array( + '#type' => 'textarea', + '#title' => '' . t('Paths') . '', + '#default_value' => isset($page->paths) ? $page->paths : '', + '#description' => $description, + ); + + return parent::form($form, $form_state, $page); + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::actions(). + */ + protected function actions(array $form, array &$form_state) { + // Only includes a Save action for the entity, no direct Delete button. + return array( + 'submit' => array( + '#value' => t('Save'), + '#validate' => array( + array($this, 'validate'), + ), + '#submit' => array( + array($this, 'submit'), + array($this, 'save'), + ), + ), + ); + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::save(). + */ + public function save(array $form, array &$form_state) { + $page = $this->getEntity($form_state); + $page->save(); + + watchdog('page', 'Page @label saved.', array('@label' => $page->label()), WATCHDOG_NOTICE); + drupal_set_message(t('Page %label saved.', array('%label' => $page->label()))); + + $form_state['redirect'] = 'admin/structure/pages'; + } + +} + diff --git a/core/modules/page/lib/Drupal/page/PageListController.php b/core/modules/page/lib/Drupal/page/PageListController.php new file mode 100644 index 0000000..16d2600 --- /dev/null +++ b/core/modules/page/lib/Drupal/page/PageListController.php @@ -0,0 +1,87 @@ +storage->load(); + uasort($items, 'page_sort_weight'); + return $items; + } + + /** + * Get form for listing page to display for weights. + */ + public function getForm($form, &$form_state) { + $form['items'] = array( + '#tree' => TRUE, + ); + foreach ($this->load() as $entity) { + $form['items'][$entity->id()]['label'] = array( + '#markup' => check_plain($entity->label()), + ); + + $paths = nl2br(check_plain($entity->paths)); + if (empty($entity->visibility)) { + $paths = t('Except: !paths', array('!paths' => '
' . $paths)); + } + $form['items'][$entity->id()]['paths'] = array( + '#markup' => $paths, + ); + + $form['items'][$entity->id()]['weight'] = array( + '#type' => 'weight', + '#title' => t('Weight'), + '#default_value' => $entity->weight, + '#delta' => 100, + '#attributes' => array('class' => array('entity-weight')), + ); + + $form['items'][$entity->id()]['operations'] = array( + $this->buildOperations($entity), + ); + } + + $form['actions'] = array(); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save changes') + ); + return $form; + } + + /** + * Save weight changes to configuration entities. + */ + public function submitForm($form, &$form_state) { + foreach ($form_state['values']['items'] as $id => $item) { + $page = entity_load('page', $id); + $page->weight = $item['weight']; + $page->save(); + } + } + +} diff --git a/core/modules/page/page.admin.inc b/core/modules/page/page.admin.inc new file mode 100644 index 0000000..5ac414d --- /dev/null +++ b/core/modules/page/page.admin.inc @@ -0,0 +1,120 @@ +getForm($form, $form_state); +} + + +/** + * Form callback to save a tabledrag list. + */ +function page_page_list_form_submit($form, &$form_state) { + $controller = entity_list_controller('page'); + return $controller->submitForm($form, $form_state); +} + +/** + * Page callback: Presents the page editing form. + * + * @see page_menu() + */ +function page_page_edit(Page $page) { + drupal_set_title(t('Edit page @label', array('@label' => $page->label())), PASS_THROUGH); + return entity_get_form($page); +} + +/** + * Page callback: Provides the new page addition form. + * + * @see page_menu() + */ +function page_page_add() { + $page = entity_create('page', array()); + return entity_get_form($page); +} + +/** + * Page callback: Form constructor for page deletion confirmation form. + * + * @see page_menu() + */ +function page_delete_confirm($form, &$form_state, Page $page) { + // Always provide entity id in the same form key as in the entity edit form. + $form['id'] = array('#type' => 'value', '#value' => $page->id()); + $form_state['page'] = $page; + return confirm_form($form, + t('Are you sure you want to remove the page %title?', array('%title' => $page->label())), + 'admin/structure/pages', + t('This action cannot be undone.'), + t('Delete'), + t('Cancel') + ); +} + +/** + * Form submission handler for page_delete_confirm(). + */ +function page_delete_confirm_submit($form, &$form_state) { + $page = $form_state['page']; + $page->delete(); + drupal_set_message(t('Page %label has been deleted.', array('%label' => $page->label()))); + watchdog('page', 'Page %label has been deleted.', array('%label' => $page->label()), WATCHDOG_NOTICE); + $form_state['redirect'] = 'admin/structure/pages'; +} + +/** + * Theme callback for page listing form. + */ +function theme_page_page_list_form($variables) { + $form = $variables['form']; + $rows = array(); + + foreach (element_children($form['items'], TRUE) as $id) { + $rows[] = array( + 'data' => array( + drupal_render($form['items'][$id]['label']), + drupal_render($form['items'][$id]['paths']), + drupal_render($form['items'][$id]['weight']), + drupal_render($form['items'][$id]['operations']), + ), + 'class' => array('draggable'), + ); + } + + $header = array( + t('Label'), + t('Paths'), + t('Weight'), + t('Operations'), + ); + + $variables = array( + 'header' => $header, + 'rows' => $rows, + 'attributes' => array('id' => 'page-list'), + ); + $table = theme('table', $variables); + $table .= drupal_render_children($form); + drupal_add_tabledrag('page-list', 'order', 'sibling', 'entity-weight'); + return $table; +} diff --git a/core/modules/page/page.info b/core/modules/page/page.info new file mode 100644 index 0000000..5abbdce --- /dev/null +++ b/core/modules/page/page.info @@ -0,0 +1,8 @@ +name = Page +description = Makes it possible to swap different page layouts. +package = Core +version = VERSION +core = 8.x +dependencies[] = config +dependencies[] = layout +configure = admin/structure/pages diff --git a/core/modules/page/page.module b/core/modules/page/page.module new file mode 100644 index 0000000..9186058 --- /dev/null +++ b/core/modules/page/page.module @@ -0,0 +1,188 @@ + 'Page library', + 'description' => 'Manage pages using layouts that allow content to be placed.', + 'page callback' => 'page_page_list', + 'access callback' => 'user_access', + 'access arguments' => array('administer pages'), + 'file' => 'page.admin.inc', + ); + $items['admin/structure/pages/add'] = array( + 'title' => 'Add page', + 'page callback' => 'page_page_add', + 'access callback' => 'user_access', + 'access arguments' => array('administer pages'), + 'type' => MENU_LOCAL_ACTION, + 'file' => 'page.admin.inc', + ); + $items['admin/structure/pages/manage/%page'] = array( + 'title' => 'Edit page', + 'page callback' => 'page_page_edit', + 'page arguments' => array(4), + 'access callback' => 'user_access', + 'access arguments' => array('administer pages'), + 'type' => MENU_CALLBACK, + 'file' => 'page.admin.inc', + ); + $items['admin/structure/pages/manage/%page/edit'] = array( + 'title' => 'Edit', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + $items['admin/structure/pages/manage/%page/delete'] = array( + 'title' => 'Delete', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('page_delete_confirm', 4), + 'access callback' => 'user_access', + 'access arguments' => array('administer pages'), + 'type' => MENU_LOCAL_TASK, + 'file' => 'page.admin.inc', + ); + return $items; +} + +/** + * Implements hook_permission(). + */ +function page_permission() { + return array( + 'administer pages' => array( + 'title' => t('Administer pages'), + 'description' => t('Manage the set of pages with distinct layouts on the site.'), + ), + ); +} + +/** + * @todo remove this in favor of actually integrating with layout mappers. + */ +function page_init() { + if ($page = page_get_matched_page()) { + drupal_set_message(t('%page matched, %layout to be used.', array('%page' => $page->label(), '%layout' => $page->layout))); + } +} + +/** + * API function to look up if the current page is matched by a config. + * + * @return Drupal\page\Page|FALSE + */ +function page_get_matched_page() { + $pages = page_load_all(); + $page_match = FALSE; + foreach ($pages as $page) { + if (!empty($page->paths)) { + // Convert paths to lowercase. This allows comparison of the same path + // with different case. Ex: /Page, /page, /PAGE. + $paths = drupal_strtolower($page->paths); + // Compare the lowercase path alias (if any) and internal path. + $path = current_path(); + $path_alias = drupal_strtolower(drupal_get_path_alias($path)); + $page_match = drupal_match_path($path_alias, $paths) || (($path != $path_alias) && drupal_match_path($path, $paths)); + // When $page->visibility has a value of 0, the page is used on all + // paths except those listed in $page->paths. When set to 1, it is + // used only on those pages listed in $page->paths. + $page_match = !($page->visibility xor $page_match); + if ($page_match) { + break; + } + } + } + return $page_match ? $page : FALSE; +} + +/** + * Implements hook_entity_info(). + */ +function page_entity_info() { + $types['page'] = array( + 'label' => 'Page', + 'entity class' => 'Drupal\page\Page', + 'controller class' => 'Drupal\Core\Config\Entity\ConfigStorageController', + 'form controller class' => array( + 'default' => 'Drupal\page\PageFormController', + ), + 'list controller class' => 'Drupal\page\PageListController', + 'list path' => 'admin/structure/pages', + 'uri callback' => 'page_uri', + 'config prefix' => 'page', + 'entity keys' => array( + 'id' => 'id', + 'label' => 'label', + 'uuid' => 'uuid', + ), + ); + return $types; +} + +/** + * Entity URI callback. + * + * @param Drupal\page\Page $page + * Page configuration entity instance. + * + * @return array + * Entity URI information. + */ +function page_uri(Page $page) { + return array( + 'path' => 'admin/structure/pages/manage/' . $page->id(), + ); +} + +/** + * Load one page object by its identifier. + * + * @return Drupal\page\Page + * Page configuration entity instance. + */ +function page_load($id) { + return entity_load('page', $id); +} + +/** + * Load all page objects. + * + * @return array + * List of Drupal\page\Page instances keyed by id. + */ +function page_load_all() { + return entity_load_multiple('page'); +} + +/** + * Implements hook_theme(). + */ +function page_theme($existing, $type, $theme, $path) { + return array( + 'page_page_list_form' => array( + 'render element' => 'form', + 'file' => 'page.admin.inc', + ), + ); +} + +/** + * Helper to sort objects by weight. + */ +function page_sort_weight($a, $b) { + $a_weight = (is_object($a) && isset($a->weight)) ? $a->weight : 0; + $b_weight = (is_object($b) && isset($b->weight)) ? $b->weight : 0; + if ($a_weight == $b_weight) { + return 0; + } + return ($a_weight < $b_weight) ? -1 : 1; +}