You are here

entity_hierarchy.module in Entity Reference Hierarchy 8

Same filename and directory in other branches
  1. 8.2 entity_hierarchy.module
  2. 3.x entity_hierarchy.module

A module to make nodes hierarchical.

File

entity_hierarchy.module
View source
<?php

/**
 * @file
 *
 * A module to make nodes hierarchical.
 */
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity;
use Drupal\Core\Render\Element;
use Drupal\node\Entity\Node;
use Drupal\node\NodeTypeInterface;
use Drupal\node\NodeInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;

/**
 * Implements @see hook_help().
 */
function entity_hierarchy_help($route_name, RouteMatchInterface $route_match) {

  // TODO: improve help text
  switch ($route_name) {
    case 'help.page.entity_hierarchy':
      return t('A module to make nodes hierarchical.');
  }
}

/**
 * Implements @see hook_form_BASE_FORM_ID_alter() for node_form().
 *
 * Adds a vertical tab to the node form allowing a hierarchy parent to be
 * selected or deleted. Here we're doing some permission checking, then
 * presenting the form using the HierarchyManager class. We then use an
 * #entity_builders callback function to save the form data to the node object.
 *
 * @see HierarchyManagerInterface::addHierarchyFormElement
 * @see entity_hierarchy_node_builder
 */
function entity_hierarchy_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  // TODO: entity_hierarchy_set_breadcrumbs($node, TRUE);
  // Load the node object associated with this form
  $node = $form_state
    ->getFormObject()
    ->getEntity();
  $account = \Drupal::currentUser();

  /** @var \Drupal\entity_hierarchy\HierarchyManager $hierarchy_manager */
  $hierarchy_manager = \Drupal::service('entity_hierarchy.manager');

  // If this node type can be a child.
  // TODO: Probably should be doing access checking using a class like
  // EntityAccessControlHandler::access or more at the route/level

  /** @see \Drupal\Core\Entity\EntityAccessControlHandler::access */
  if ($hierarchy_manager
    ->hierarchyCanBeChild($node) || $hierarchy_manager
    ->hierarchyCanBeParent($node)) {

    // if the current user can edit the current node's hierarchy settings (or create new children)
    $uid = $node
      ->getOwnerId();
    $can_set_parent = $account
      ->hasPermission('edit all node parents') || $node
      ->isNew() && $account
      ->hasPermission('create child nodes') || $uid == $account
      ->getAccount()
      ->id() && $account
      ->hasPermission('edit own node parents');

    // Only show the form is the user has permission
    if ($can_set_parent) {
      $collapsed = TRUE;

      // Todo: fix (check if a parent is already set)
      $form = $hierarchy_manager
        ->addHierarchyFormElement($form, $form_state, $node, $account, $collapsed);

      // Form API entity_builders callback; see https://www.drupal.org/node/2420295
      $form['#entity_builders'][] = 'entity_hierarchy_node_builder';
    }
  }
}

/**
 * Entity form builder adds the hierarchy information to the node object. More
 * officially, this builds an updated entity object based upon the submitted
 * form values.
 *
 * This function is called from the #entity_builders callback.
 *
 * @see entity_hierarchy_form_node_form_alter
 * @see EntityForm::buildEntity
 *
 * As per https://www.drupal.org/node/2420295, an entity field may be better
 * suited to this task.
 * @see \Drupal\Core\Entity\ContentEntityBase
 * @see core/lib/Drupal/Core/Entity/ContentEntityBase.php
 */
function entity_hierarchy_node_builder($entity_type, NodeInterface $node, &$form, FormStateInterface $form_state) {
  $node->entity_hierarchy_parents = $form_state
    ->getValue('entity_hierarchy_parents');
}

/**
 * Implements @see hook_entity_type_build().
 *
 * Here we're adding a form controller class for a custom node form without
 * overriding the default node form.
 *
 * More specifically, we're adding a children form to a tab named children.
 * This routing information for this tab is set in the .routing.yml file, and
 * the .links.task.yml file.
 *
 * @see \Drupal\entity_hierarchy\Form\NodehierarchyChildrenForm
 */
function entity_hierarchy_entity_type_build(array &$entity_types) {

  /** @type $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
  $entity_types['node']
    ->setFormClass('entity_hierarchy_children', 'Drupal\\entity_hierarchy\\Form\\NodehierarchyChildrenForm')
    ->setLinkTemplate('entity_hierarchy-children-form', '/node/{node}/children');
}

/**
 * Todo: implement functionality to delete children of a parent by altering the delete form.
 */

/**
 * Implements @see hook_form_FORM_ID_alter().
 *
 * Here we are altering the node type edit form to include hierarchy settings,
 * such as setting which content types should support one or more child content
 * types.
 *
 * @see HierarchyManagerInterface::hierarchyGetNodeTypeSettingsForm
 */
function entity_hierarchy_form_node_type_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  /** @var \Drupal\entity_hierarchy\HierarchyManager $hierarchy_manager */
  $hierarchy_manager = \Drupal::service('entity_hierarchy.manager');

  // TODO: check if there's any reason we're not including the base $form
  // declaration in the class method.
  $type = $form['type']['#default_value'];

  // The content type
  $form['hierarchy'] = array(
    '#type' => 'details',
    '#group' => 'additional_settings',
    '#title' => t('Entity Hierarchy'),
    '#weight' => 10,
  );

  // Right now, we add this js to all admin pages using hook_page_attachments; see below.
  // $form['#attached']['library'][] = entity_hierarchy/entity_hierarchy.nodetypeform;
  $form['hierarchy'] += $hierarchy_manager
    ->hierarchyGetNodeTypeSettingsForm($type);
  $form['#entity_builders'][] = 'entity_hierarchy_form_node_type_form_builder';
}

/**
 * Entity form builder for the node type form to save the hierarchy settings
 * using the configuration factory.
 */
function entity_hierarchy_form_node_type_form_builder($entity_type, NodeTypeInterface $type, &$form, FormStateInterface $form_state) {
  $node_type = $type
    ->get('type');
  $config = \Drupal::getContainer()
    ->get('config.factory')
    ->getEditable('entity_hierarchy.settings');
  $config
    ->set('nh_allowchild_' . $node_type, $form_state
    ->getValue('nh_allowchild'));

  //  $config->set('nh_createmenu_'.$node_type, $form_state->getValue('nh_createmenu'));
  //  $config->set('nh_multiple_'.$node_type, $form_state->getValue('nh_multiple'));
  $config
    ->set('nh_defaultparent_' . $node_type, $form_state
    ->getValue('nh_defaultparent'));
  $config
    ->save();
}

/**
 * Implements @see hook_ENTITY_TYPE_insert() for node entities.
 *
 * This function will be called whenever a new node is created. We will write
 * the hierarchy information to the database if a parent is set on the node
 * add form.
 *
 * @see HierarchyManagerInterface::hierarchySaveNode
 */
function entity_hierarchy_node_insert(NodeInterface $node) {

  /** @var \Drupal\entity_hierarchy\HierarchyManager $hierarchy_manager */
  $hierarchy_manager = \Drupal::service('entity_hierarchy.manager');

  // TODO: check if we really need a permission check here. (Don't even show the form if no permission.)
  $user = \Drupal::currentUser();
  $uid = $user
    ->id();
  $node_uid = $node
    ->getOwnerId();
  if ($user
    ->hasPermission('edit all node parents') || $node_uid == $uid && $user
    ->hasPermission('edit own node parents')) {
    $hierarchy_manager
      ->hierarchySaveNode($node);
  }
}

/**
 * Implements hook_ENTITY_TYPE_delete().
 *
 * Todo: move this to the services class.
 */
function entity_hierarchy_node_delete(EntityInterface $node) {
  db_delete('entity_hierarchy')
    ->condition('hid', $node
    ->id())
    ->execute();
}

/**
 * Implements @see hook_ENTITY_TYPE_update().
 *
 * This function will be called whenever a node is updated. We will write
 * the hierarchy information to the database if a parent is set on the node
 * edit form or delete the parent if a checkbox is selected.
 *
 * @see HierarchyManagerInterface::hierarchySaveNode
 */
function entity_hierarchy_node_update(EntityInterface $node) {
  $hierarchy_manager = \Drupal::service('entity_hierarchy.manager');
  $hierarchy_manager
    ->hierarchySaveNode($node);
}

/**
 * Implements @see hook_node_prepare_form().
 *
 * We are loading the hierarchy parents for a given node id, and adding it to
 * the node object for later processing in
 * HierarchyManagerInterface::addHierarchyFormElement, which is called in
 * entity_hierarchy_form_node_form_alter().
 *
 * @see entity_hierarchy_form_node_form_alter
 * @see HierarchyManagerInterface::addHierarchyFormElement
 * @see HierarchyManagerInterface::hierarchyDefaultRecord
 */
function entity_hierarchy_node_prepare_form(NodeInterface $node) {

  /** @var \Drupal\entity_hierarchy\HierarchyManager $hierarchy_manager */
  $hierarchy_manager = \Drupal::service('entity_hierarchy.manager');

  /** @var \Drupal\entity_hierarchy\HierarchyOutlineStorage $hierarchy_storage */
  $hierarchy_storage = \Drupal::service('entity_hierarchy.outline_storage');

  // Load the parents if that hasn't been done before.
  $nid = $node
    ->id();
  if (!isset($node->entity_hierarchy_parents) && !empty($nid)) {
    $node->entity_hierarchy_parents = $hierarchy_storage
      ->hierarchyGetParents($node
      ->id());
  }

  // Cannot use module_invoke_all because it doesn't support references.
  foreach (\Drupal::moduleHandler()
    ->getImplementations('entity_hierarchy_default_parents') as $module) {
    $function = $module . '_entity_hierarchy_default_parents';
    $function($node);
  }
  if ($hierarchy_manager
    ->hierarchyCanBeChild($node) || $hierarchy_manager
    ->hierarchyCanBeParent($node)) {
    if (!isset($node->entity_hierarchy_parents) || empty($node->entity_hierarchy_parents)) {

      // Create a default entity_hierarchy object.
      $nid = empty($nid) ? null : $node
        ->id();
      $parent = $hierarchy_manager
        ->hierarchyDefaultRecord($nid, 0);

      // Set the type default if there is one.
      if (empty($nid)) {
        $config = \Drupal::config('entity_hierarchy.settings');
        $default = $config
          ->get('nh_defaultparent_' . $node
          ->getType());

        // Get the parent node id from passed in from the get params.
        $pnid = !empty($_GET['parent']) ? (int) $_GET['parent'] : $default;

        // Get the parent from the get string. User must have update perms for parent unless it is the default.
        $account = \Drupal::currentUser();
        if ($pnid && ($parent_node = Node::load($pnid))) {
          if ($hierarchy_manager
            ->hierarchyCanBeParent($node) && ($account
            ->hasPermission('create child of any parent') || $node
            ->access("update") || $parent_node
            ->id() == $default)) {
            $parent->pnid = $pnid;
          }
        }
      }
      $node->entity_hierarchy_parents[] = $parent;
    }
  }
}

/**
 * Implements @see hook_page_attachments().
 *
 * Right now, we're attaching an un-used jS file to all admin pages. Need to
 * re-visit this and only load on the appropriate pages and/or forms.
 */
function entity_hierarchy_page_attachments(&$page) {

  // This returns TRUE for admin paths.
  if (\Drupal::service('router.admin_context')
    ->isAdminRoute()) {

    // Load entity_hierarchy.js on admin pages
    $page['#attached']['library'][] = 'entity_hierarchy/entity_hierarchy.nodetypeform';
  }
}

/**
 * Implements hook_ENTITY_TYPE_view().
 */
function entity_hierarchy_node_view(array &$build, EntityInterface $node, EntityViewDisplayInterface $display, $view_mode) {
  if ($view_mode == 'full') {
    $create_links = array();
    $current_user = \Drupal::currentUser();

    /** @var \Drupal\entity_hierarchy\HierarchyManager $hierarchy_manager */
    $hierarchy_manager = \Drupal::service('entity_hierarchy.manager');
    if ($current_user
      ->hasPermission('create child nodes') && $current_user
      ->hasPermission('create child of any parent' || $current_user
      ->hasPermission('update'))) {
      foreach ($hierarchy_manager
        ->hierarchyGetAllowedChildTypes($node
        ->getType()) as $key) {
        if ($node
          ->access()) {
          $destination = (array) drupal_get_destination() + array(
            'parent' => $node
              ->id(),
          );

          //d7: $key = str_replace('_', '-', $key);
          $url = \Drupal\Core\Url::fromRoute('node.add', array(
            'node_type' => $key,
          ), array(
            'query' => $destination,
          ));
          $link = \Drupal\Core\Link::fromTextAndUrl(t($key), $url);
          $create_links[] = render(@$link
            ->toRenderable());
        }
        if ($create_links) {
          $build['entity_hierarchy_new_child_links'] = array(
            // @todo: handle this with hook_theme and template_preprocess (see below) and use #theme over #markup
            // @see function template_preprocess_book_navigation
            // '#theme' => 'entity_hierarchy_new_child_links',
            // The Create new child link should probably come after the content, so make the weight high.
            '#weight' => 1000,
            '#markup' => '<div class="newchild">' . t('Create new child ') . implode(" | ", $create_links) . '</div>',
          );
        }
      }
    }
  }
}

/**
 * Implements hook_theme().
 *
 * Todo: implement this
 */
function entity_hierarchy_theme($existing, $type, $theme, $path) {
  return array(
    'entity_hierarchy_new_child_links' => array(
      'variables' => array(),
    ),
  );
}

/**
 * Prepares variables for hierarchy links to be added to pages.
 *
 * Default template: entity_hierarchy-new-child-links.html.twig.
 *
 * @param array $variables
 *
 * Todo: define variables
 */
function template_preprocess_entity_hierarchy_new_child_links(&$variables) {
  $node = \Drupal::request()->attributes
    ->get('node');

  //  dpm($node);
  //  dpm($variables);
}

/**
 * Implements @see hook_ENTITY_TYPE_load() for node entities.
 *
 * Here we're adding the parent's nid to the node object.
 */
function entity_hierarchy_node_load($nodes) {

  //  $node = \Drupal::routeMatch()->getParameter('node');
  //  $current_nid = null;
  //  if ($node) {
  //    $current_nid = $node->id();
  //    dpm($current_nid);
  //  }

  ////   For now, we'll only deal with the current node's parent with the above code

  ////  foreach($nodes as $node) {

  ////    dpm($node->id());

  ////  }

  //  /** @var \Drupal\entity_hierarchy\HierarchyOutlineStorage $hierarchy_storage */
  //  $hierarchy_storage = \Drupal::service('entity_hierarchy.outline_storage');
  //  $parent = $hierarchy_storage->hierarchyGetParent($current_nid);
  //  $nodes[$current_nid['nid']]->hierarchy_parent = $parent;
  //  dpm($nodes);
}

/**
 * Helper functions for strings to determine if a string starts or ends with a
 * substring.
 */
function startsWith($haystack, $needle) {

  // search backwards starting from haystack length characters from the end
  return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== FALSE;
}
function endsWith($haystack, $needle) {

  // search forward starting from end minus needle length characters
  return $needle === "" || strpos($haystack, $needle, strlen($haystack) - strlen($needle)) !== FALSE;
}

Functions

Namesort descending Description
endsWith
entity_hierarchy_entity_type_build Implements Here we're adding a form controller class for a custom node form without overriding the default node form.
entity_hierarchy_form_node_form_alter Implements Adds a vertical tab to the node form allowing a hierarchy parent to be selected or deleted. Here we're doing some permission checking, then presenting the form using the HierarchyManager class. We then use an #entity_builders callback…
entity_hierarchy_form_node_type_form_alter Implements Here we are altering the node type edit form to include hierarchy settings, such as setting which content types should support one or more child content types.
entity_hierarchy_form_node_type_form_builder Entity form builder for the node type form to save the hierarchy settings using the configuration factory.
entity_hierarchy_help Implements
entity_hierarchy_node_builder Entity form builder adds the hierarchy information to the node object. More officially, this builds an updated entity object based upon the submitted form values.
entity_hierarchy_node_delete Implements hook_ENTITY_TYPE_delete().
entity_hierarchy_node_insert Implements This function will be called whenever a new node is created. We will write the hierarchy information to the database if a parent is set on the node add form.
entity_hierarchy_node_load Implements Here we're adding the parent's nid to the node object.
entity_hierarchy_node_prepare_form Implements We are loading the hierarchy parents for a given node id, and adding it to the node object for later processing in HierarchyManagerInterface::addHierarchyFormElement, which is called in entity_hierarchy_form_node_form_alter().
entity_hierarchy_node_update Implements This function will be called whenever a node is updated. We will write the hierarchy information to the database if a parent is set on the node edit form or delete the parent if a checkbox is selected.
entity_hierarchy_node_view Implements hook_ENTITY_TYPE_view().
entity_hierarchy_page_attachments Implements Right now, we're attaching an un-used jS file to all admin pages. Need to re-visit this and only load on the appropriate pages and/or forms.
entity_hierarchy_theme Implements hook_theme().
startsWith Helper functions for strings to determine if a string starts or ends with a substring.
template_preprocess_entity_hierarchy_new_child_links Prepares variables for hierarchy links to be added to pages.