entity_hierarchy.module in Entity Reference Hierarchy 8
Same filename and directory in other branches
A module to make nodes hierarchical.
File
entity_hierarchy.moduleView 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
Name | 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. |