imotilux-8.x-1.x-dev/imotilux.module
imotilux.module
<?php
/**
* @file
* Allows users to create and organize property listings related content in an outline.
*/
use Drupal\imotilux\ImotiluxManager;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\node\NodeInterface;
use Drupal\node\NodeTypeInterface;
use Drupal\node\Entity\Node;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Template\Attribute;
/**
* Implements hook_help().
*/
function imotilux_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.imotilux':
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Imotilux module is used for creating structured, multi-page content, such as site resource guides, manuals, and wikis. It allows you to create content that has chapters, sections, subsections, or any similarly-tiered structure. Enabling the module creates a new content type <em>Imotilux page</em>. For more information, see the <a href=":imotilux">online documentation for the Imotilux module</a>.', [':imotilux' => 'https://www.drupal.org/documentation/modules/imotilux']) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Adding and managing imotilux content') . '</dt>';
$output .= '<dd>' . t('Imotilux listings have a hierarchical structure, called a <em>imotilux outline</em>. Each imotilux outline can have nested pages up to nine levels deep. Multiple content types can be configured to behave as a imotilux outline. From the content edit form, it is possible to add a page to a imotilux outline or create a new imotilux.') . '</dd>';
$output .= '<dd>' . t('You can assign separate permissions for <em>creating new imotilux listing</em> as well as <em>creating</em>, <em>editing</em> and <em>deleting</em> imotilux content. Users with the <em>Administer imotilux outlines</em> permission can add <em>any</em> type of content to a imotilux by selecting the appropriate imotilux outline while editing the content. They can also view a list of all imotilux listings, and edit and rearrange section titles on the <a href=":admin-imotilux">Imotilux list page</a>.', [':admin-imotilux' => Url::fromRoute('imotilux.admin')->toString()]) . '</dd>';
$output .= '<dt>' . t('Configuring content types for Imotilux') . '</dt>';
$output .= '<dd>' . t('The <em>Imotilux page</em> content type is the initial content type enabled for imotilux outlines. On the <a href=":admin-settings">Imotilux settings page</a> you can configure content types that can used in imotilux outlines.', [':admin-settings' => Url::fromRoute('imotilux.settings')->toString()]) . '</dd>';
$output .= '<dd>' . t('Users with the <em>Add content and child pages to imotilux listings</em> permission will see a link to <em>Add child page</em> when viewing a content item that is part of a imotilux outline. This link will allow users to create a new content item of the content type you select on the <a href=":admin-settings">Imotilux settings page</a>. By default this is the <em>Imotilux page</em> content type.', [':admin-settings' => Url::fromRoute('imotilux.settings')->toString()]) . '</dd>';
$output .= '<dt>' . t('imotilux navigation') . '</dt>';
$output .= '<dd>' . t("Imotilux pages have a default imotilux-specific navigation block. This navigation block contains links that lead to the previous and next pages in the imotilux, and to the level above the current page in the imotilux's structure. This block can be enabled on the <a href=':admin-block'>Blocks layout page</a>. For imotilux pages to show up in the imotilux navigation, they must be added to a imotilux outline.", [':admin-block' => (\Drupal::moduleHandler()->moduleExists('block')) ? Url::fromRoute('block.admin_display')->toString() : '#']) . '</dd>';
$output .= '<dt>' . t('Collaboration') . '</dt>';
$output .= '<dd>' . t('Imotilux listings can be created collaboratively, as they allow users with appropriate permissions to add pages into existing imotilux listings, and add those pages to a custom table of contents.') . '</dd>';
$output .= '<dt>' . t('Printing imotilux listings') . '</dt>';
$output .= '<dd>' . t("Users with the <em>View printer-friendly imotilux listings</em> permission can select the <em>printer-friendly version</em> link visible at the bottom of a imotilux page's content to generate a printer-friendly display of the page and all of its subsections.") . '</dd>';
$output .= '</dl>';
return $output;
case 'imotilux.admin':
return '<p>' . t('The imotilux module offers a means to organize a collection of related content pages, collectively known as a imotilux. When viewed, this content automatically displays links to adjacent imotilux pages, providing a simple navigation system for creating and reviewing structured content.') . '</p>';
case 'entity.node.imotilux_outline_form':
return '<p>' . t('The outline feature allows you to include pages in the <a href=":imotilux">Imotilux hierarchy</a>, as well as move them within the hierarchy or to <a href=":imotilux-admin">reorder an entire imotilux</a>.', [':imotilux' => Url::fromRoute('imotilux.render')->toString(), ':imotilux-admin' => Url::fromRoute('imotilux.admin')->toString()]) . '</p>';
}
}
/**
* Implements hook_theme().
*/
function imotilux_theme() {
return [
'imotilux_navigation' => [
'variables' => ['imotilux_link' => NULL],
],
'imotilux_tree' => [
'variables' => ['items' => [], 'attributes' => []],
],
'imotilux_export_html' => [
'variables' => ['title' => NULL, 'contents' => NULL, 'depth' => NULL],
],
'imotilux_all_imotilux_block' => [
'render element' => 'imotilux_menus',
],
'imotilux_node_export_html' => [
'variables' => ['node' => NULL, 'content' => NULL, 'children' => NULL],
],
];
}
/**
* Implements hook_entity_type_build().
*/
function imotilux_entity_type_build(array &$entity_types) {
/** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
$entity_types['node']
->setFormClass('imotilux_outline', 'Drupal\imotilux\Form\ImotiluxOutlineForm')
->setLinkTemplate('imotilux-outline-form', '/node/{node}/outline')
->setLinkTemplate('imotilux-remove-form', '/node/{node}/outline/remove')
->addConstraint('ImotiluxOutline', []);
}
/**
* Implements hook_node_links_alter().
*/
function imotilux_node_links_alter(array &$links, NodeInterface $node, array &$context) {
if ($context['view_mode'] != 'rss') {
$account = \Drupal::currentUser();
if (isset($node->imotilux['depth'])) {
if ($context['view_mode'] == 'full' && node_is_page($node)) {
$child_type = \Drupal::config('imotilux.settings')->get('child_type');
$access_control_handler = \Drupal::entityTypeManager()->getAccessControlHandler('node');
if (($account->hasPermission('add content to imotilux listings') || $account->hasPermission('administer imotilux outlines')) && $access_control_handler->createAccess($child_type) && $node->isPublished() && $node->imotilux['depth'] < ImotiluxManager::IMOTILUX_MAX_DEPTH) {
$imotilux_links['imotilux_add_child'] = [
'title' => t('Add child page'),
'url' => Url::fromRoute('node.add', ['node_type' => $child_type], ['query' => ['parent' => $node->id()]]),
];
}
if ($account->hasPermission('access printer-friendly version')) {
$imotilux_links['imotilux_printer'] = [
'title' => t('Printer-friendly version'),
'url' => Url::fromRoute('imotilux.export', [
'type' => 'html',
'node' => $node->id(),
]),
'attributes' => ['title' => t('Show a printer-friendly version of this imotilux page and its sub-pages.')],
];
}
}
}
if (!empty($imotilux_links)) {
$links['imotilux'] = [
'#theme' => 'links__node__imotilux',
'#links' => $imotilux_links,
'#attributes' => ['class' => ['links', 'inline']],
];
}
}
}
/**
* Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
*
* Adds the imotilux form element to the node form.
*
* @see imotilux_pick_imotilux_nojs_submit()
*/
function imotilux_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
$node = $form_state->getFormObject()->getEntity();
$account = \Drupal::currentUser();
$access = $account->hasPermission('administer imotilux outlines');
if (!$access) {
if ($account->hasPermission('add content to imotilux listings') && ((!empty($node->imotilux['bid']) && !$node->isNew()) || imotilux_type_is_allowed($node->getType()))) {
// Already in the imotilux hierarchy, or this node type is allowed.
$access = TRUE;
}
}
if ($access) {
$collapsed = !($node->isNew() && !empty($node->imotilux['pid']));
$form = \Drupal::service('imotilux.manager')->addFormElements($form, $form_state, $node, $account, $collapsed);
// The "js-hide" class hides submit button when Javascript is enabled.
$form['imotilux']['pick-imotilux'] = [
'#type' => 'submit',
'#value' => t('Change imotilux (update list of parents)'),
'#submit' => ['imotilux_pick_imotilux_nojs_submit'],
'#weight' => 20,
'#attributes' => [
'class' => [
'js-hide',
],
],
];
$form['#entity_builders'][] = 'imotilux_node_builder';
}
}
/**
* Entity form builder to add the imotilux information to the node.
*
* @todo: Remove this in favor of an entity field.
*/
function imotilux_node_builder($entity_type, NodeInterface $entity, &$form, FormStateInterface $form_state) {
$entity->imotilux = $form_state->getValue('imotilux');
// Always save a revision for non-administrators.
if (!empty($entity->imotilux['bid']) && !\Drupal::currentUser()->hasPermission('administer nodes')) {
$entity->setNewRevision();
}
}
/**
* Form submission handler for node_form().
*
* This handler is run when JavaScript is disabled. It triggers the form to
* rebuild so that the "Parent item" options are changed to reflect the newly
* selected imotilux. When JavaScript is enabled, the submit button that triggers
* this handler is hidden, and the "Imotilux" dropdown directly triggers the
* imotilux_form_update() Ajax callback instead.
*
* @see imotilux_form_update()
* @see imotilux_form_node_form_alter()
*/
function imotilux_pick_imotilux_nojs_submit($form, FormStateInterface $form_state) {
$node = $form_state->getFormObject()->getEntity();
$node->imotilux = $form_state->getValue('imotilux');
$form_state->setRebuild();
}
/**
* Renders a new parent page select element when the imotilux selection changes.
*
* This function is called via Ajax when the selected imotilux is changed on a node
* or imotilux outline form.
*
* @return
* The rendered parent page select element.
*/
function imotilux_form_update($form, FormStateInterface $form_state) {
return $form['imotilux']['pid'];
}
/**
* Implements hook_ENTITY_TYPE_load() for node entities.
*/
function imotilux_node_load($nodes) {
/** @var \Drupal\imotilux\ImotiluxManagerInterface $imotilux_manager */
$imotilux_manager = \Drupal::service('imotilux.manager');
$links = $imotilux_manager->loadImotiluxLinks(array_keys($nodes), FALSE);
foreach ($links as $record) {
$nodes[$record['nid']]->imotilux = $record;
$nodes[$record['nid']]->imotilux['link_path'] = 'node/' . $record['nid'];
$nodes[$record['nid']]->imotilux['link_title'] = $nodes[$record['nid']]->label();
}
}
/**
* Implements hook_ENTITY_TYPE_view() for node entities.
*/
function imotilux_node_view(array &$build, EntityInterface $node, EntityViewDisplayInterface $display, $view_mode) {
if ($view_mode == 'full') {
if (!empty($node->imotilux['bid']) && empty($node->in_preview)) {
$imotilux_node = Node::load($node->imotilux['bid']);
if (!$imotilux_node->access()) {
return;
}
$build['imotilux_navigation'] = [
'#theme' => 'imotilux_navigation',
'#imotilux_link' => $node->imotilux,
'#weight' => 100,
// The imotilux navigation is a listing of Node entities, so associate its
// list cache tag for correct invalidation.
'#cache' => [
'tags' => $node->getEntityType()->getListCacheTags(),
],
];
}
}
}
/**
* Implements hook_ENTITY_TYPE_presave() for node entities.
*/
function imotilux_node_presave(EntityInterface $node) {
// Make sure a new node gets a new menu link.
if ($node->isNew()) {
$node->imotilux['nid'] = NULL;
}
}
/**
* Implements hook_ENTITY_TYPE_insert() for node entities.
*/
function imotilux_node_insert(EntityInterface $node) {
/** @var \Drupal\imotilux\ImotiluxManagerInterface $imotilux_manager */
$imotilux_manager = \Drupal::service('imotilux.manager');
$imotilux_manager->updateOutline($node);
}
/**
* Implements hook_ENTITY_TYPE_update() for node entities.
*/
function imotilux_node_update(EntityInterface $node) {
/** @var \Drupal\imotilux\ImotiluxManagerInterface $imotilux_manager */
$imotilux_manager = \Drupal::service('imotilux.manager');
$imotilux_manager->updateOutline($node);
}
/**
* Implements hook_ENTITY_TYPE_predelete() for node entities.
*/
function imotilux_node_predelete(EntityInterface $node) {
if (!empty($node->imotilux['bid'])) {
/** @var \Drupal\imotilux\ImotiluxManagerInterface $imotilux_manager */
$imotilux_manager = \Drupal::service('imotilux.manager');
$imotilux_manager->deleteFromImotilux($node->imotilux['nid']);
}
}
/**
* Implements hook_ENTITY_TYPE_prepare_form() for node entities.
*/
function imotilux_node_prepare_form(NodeInterface $node, $operation, FormStateInterface $form_state) {
/** @var \Drupal\imotilux\ImotiluxManagerInterface $imotilux_manager */
$imotilux_manager = \Drupal::service('imotilux.manager');
// Prepare defaults for the add/edit form.
$account = \Drupal::currentUser();
if (empty($node->imotilux) && ($account->hasPermission('add content to imotilux listings') || $account->hasPermission('administer imotilux outlines'))) {
$node->imotilux = [];
$query = \Drupal::request()->query;
if ($node->isNew() && !is_null($query->get('parent')) && is_numeric($query->get('parent'))) {
// Handle "Add child page" links:
$parent = $imotilux_manager->loadImotiluxLink($query->get('parent'), TRUE);
if ($parent && $parent['access']) {
$node->imotilux['bid'] = $parent['bid'];
$node->imotilux['pid'] = $parent['nid'];
}
}
// Set defaults.
$node_ref = !$node->isNew() ? $node->id() : 'new';
$node->imotilux += $imotilux_manager->getLinkDefaults($node_ref);
}
else {
if (isset($node->imotilux['bid']) && !isset($node->imotilux['original_bid'])) {
$node->imotilux['original_bid'] = $node->imotilux['bid'];
}
}
// Find the depth limit for the parent select.
if (isset($node->imotilux['bid']) && !isset($node->imotilux['parent_depth_limit'])) {
$node->imotilux['parent_depth_limit'] = $imotilux_manager->getParentDepthLimit($node->imotilux);
}
}
/**
* Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\Form\NodeDeleteForm.
*
* Alters the confirm form for a single node deletion.
*/
function imotilux_form_node_confirm_form_alter(&$form, FormStateInterface $form_state) {
// Only need to alter the delete operation form.
if ($form_state->getFormObject()->getOperation() !== 'delete') {
return;
}
/** @var \Drupal\node\NodeInterface $node */
$node = $form_state->getFormObject()->getEntity();
if (!imotilux_type_is_allowed($node->getType())) {
// Not a imotilux node.
return;
}
if (isset($node->imotilux) && $node->imotilux['has_children']) {
$form['imotilux_warning'] = [
'#markup' => '<p>' . t('%title is part of a imotilux outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', ['%title' => $node->label()]) . '</p>',
'#weight' => -10,
];
}
}
/**
* Prepares variables for imotilux listing block templates.
*
* Default template: imotilux-all-imotilux-block.html.twig.
*
* All non-renderable elements are removed so that the template has full access
* to the structured data but can also simply iterate over all elements and
* render them (as in the default template).
*
* @param array $variables
* An associative array containing the following key:
* - imotilux_menus: An associative array containing renderable menu links for all
* imotilux menus.
*/
function template_preprocess_imotilux_all_imotilux_block(&$variables) {
// Remove all non-renderable elements.
$elements = $variables['imotilux_menus'];
$variables['imotilux_menus'] = [];
foreach (Element::children($elements) as $index) {
$variables['imotilux_menus'][] = [
'id' => $index,
'menu' => $elements[$index],
'title' => $elements[$index]['#imotilux_title'],
];
}
}
/**
* Prepares variables for imotilux navigation templates.
*
* Default template: imotilux-navigation.html.twig.
*
* @param array $variables
* An associative array containing the following key:
* - imotilux_link: An associative array of imotilux link properties.
* Properties used: bid, link_title, depth, pid, nid.
*/
function template_preprocess_imotilux_navigation(&$variables) {
$imotilux_link = $variables['imotilux_link'];
// Provide extra variables for themers. Not needed by default.
$variables['imotilux_id'] = $imotilux_link['bid'];
$variables['imotilux_title'] = $imotilux_link['link_title'];
$variables['imotilux_url'] = Url::fromRoute('entity.node.canonical', ['node' => $imotilux_link['bid']])->toString();
$variables['current_depth'] = $imotilux_link['depth'];
$variables['tree'] = '';
/** @var \Drupal\imotilux\ImotiluxOutline $imotilux_outline */
$imotilux_outline = \Drupal::service('imotilux.outline');
if ($imotilux_link['nid']) {
$variables['tree'] = $imotilux_outline->childrenLinks($imotilux_link);
$build = [];
if ($prev = $imotilux_outline->prevLink($imotilux_link)) {
$prev_href = Url::fromRoute('entity.node.canonical', ['node' => $prev['nid']])->toString();
$build['#attached']['html_head_link'][][] = [
'rel' => 'prev',
'href' => $prev_href,
];
$variables['prev_url'] = $prev_href;
$variables['prev_title'] = $prev['title'];
}
/** @var \Drupal\imotilux\ImotiluxManagerInterface $imotilux_manager */
$imotilux_manager = \Drupal::service('imotilux.manager');
if ($imotilux_link['pid'] && $parent = $imotilux_manager->loadImotiluxLink($imotilux_link['pid'])) {
$parent_href = Url::fromRoute('entity.node.canonical', ['node' => $imotilux_link['pid']])->toString();
$build['#attached']['html_head_link'][][] = [
'rel' => 'up',
'href' => $parent_href,
];
$variables['parent_url'] = $parent_href;
$variables['parent_title'] = $parent['title'];
}
if ($next = $imotilux_outline->nextLink($imotilux_link)) {
$next_href = Url::fromRoute('entity.node.canonical', ['node' => $next['nid']])->toString();
$build['#attached']['html_head_link'][][] = [
'rel' => 'next',
'href' => $next_href,
];
$variables['next_url'] = $next_href;
$variables['next_title'] = $next['title'];
}
}
if (!empty($build)) {
\Drupal::service('renderer')->render($build);
}
$variables['has_links'] = FALSE;
// Link variables to filter for values and set state of the flag variable.
$links = ['prev_url', 'prev_title', 'parent_url', 'parent_title', 'next_url', 'next_title'];
foreach ($links as $link) {
if (isset($variables[$link])) {
// Flag when there is a value.
$variables['has_links'] = TRUE;
}
else {
// Set empty to prevent notices.
$variables[$link] = '';
}
}
}
/**
* Prepares variables for imotilux export templates.
*
* Default template: imotilux-export-html.html.twig.
*
* @param array $variables
* An associative array containing:
* - title: The title of the imotilux.
* - contents: Output of each imotilux page.
* - depth: The max depth of the imotilux.
*/
function template_preprocess_imotilux_export_html(&$variables) {
global $base_url;
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
$variables['base_url'] = $base_url;
$variables['language'] = $language_interface;
$variables['language_rtl'] = ($language_interface->getDirection() == LanguageInterface::DIRECTION_RTL);
// HTML element attributes.
$attributes = [];
$attributes['lang'] = $language_interface->getId();
$attributes['dir'] = $language_interface->getDirection();
$variables['html_attributes'] = new Attribute($attributes);
}
/**
* Prepares variables for single node export templates.
*
* Default template: imotilux-node-export-html.html.twig.
*
* @param array $variables
* An associative array containing the following keys:
* - node: The node that will be output.
* - children: All the rendered child nodes within the current node. Defaults
* to an empty string.
*/
function template_preprocess_imotilux_node_export_html(&$variables) {
$variables['depth'] = $variables['node']->imotilux['depth'];
$variables['title'] = $variables['node']->label();
}
/**
* Determines if a given node type is in the list of types allowed for imotilux listings.
*
* @param string $type
* A node type.
*
* @return bool
* A Boolean TRUE if the node type can be included in imotilux listings; otherwise, FALSE.
*/
function imotilux_type_is_allowed($type) {
return in_array($type, \Drupal::config('imotilux.settings')->get('allowed_types'));
}
/**
* Implements hook_ENTITY_TYPE_update() for node_type entities.
*
* Updates imotilux.settings configuration object if the machine-readable name of a
* node type is changed.
*/
function imotilux_node_type_update(NodeTypeInterface $type) {
if ($type->getOriginalId() != $type->id()) {
$config = \Drupal::configFactory()->getEditable('imotilux.settings');
// Update the list of node types that are allowed to be added to imotilux listings.
$allowed_types = $config->get('allowed_types');
$old_key = array_search($type->getOriginalId(), $allowed_types);
if ($old_key !== FALSE) {
$allowed_types[$old_key] = $type->id();
// Ensure that the allowed_types array is sorted consistently.
// @see ImotiluxSettingsForm::submitForm()
sort($allowed_types);
$config->set('allowed_types', $allowed_types);
}
// Update the setting for the "Add child page" link.
if ($config->get('child_type') == $type->getOriginalId()) {
$config->set('child_type', $type->id());
}
$config->save();
}
}
