database->startTransaction(); try { // Load the stored entity, if any. if (!$entity->isNew() && !isset($entity->original)) { $entity->original = entity_load_unchanged($this->entityTypeId, $entity->id()); } if ($entity->isNew()) { $entity->mlid = $this->database->insert($this->entityType->getBaseTable())->fields(array('menu_name' => $entity->menu_name))->execute(); $entity->enforceIsNew(); } // Unlike the save() method from DatabaseStorageController, we invoke the // 'presave' hook first because we want to allow modules to alter the // entity before all the logic from our preSave() method. $this->invokeHook('presave', $entity); $entity->preSave($this); // If every value in $entity->original is the same in the $entity, there // is no reason to run the update queries or clear the caches. We use // array_intersect_key() with the $entity as the first parameter because // $entity may have additional keys left over from building a router entry. // The intersect removes the extra keys, allowing a meaningful comparison. if ($entity->isNew() || (array_intersect_key(get_object_vars($entity), get_object_vars($entity->original)) != get_object_vars($entity->original))) { $return = drupal_write_record($this->entityType->getBaseTable(), $entity, $this->idKey); if ($return) { if (!$entity->isNew()) { $this->resetCache(array($entity->{$this->idKey})); $entity->postSave($this, TRUE); $this->invokeHook('update', $entity); } else { $return = SAVED_NEW; $this->resetCache(); $entity->enforceIsNew(FALSE); $entity->postSave($this, FALSE); $this->invokeHook('insert', $entity); } } } // Ignore slave server temporarily. db_ignore_slave(); unset($entity->original); return $return; } catch (\Exception $e) { $transaction->rollback(); watchdog_exception($this->entityTypeId, $e); throw new EntityStorageException($e->getMessage(), $e->getCode(), $e); } } /** * {@inheritdoc} */ public function setPreventReparenting($value = FALSE) { $this->preventReparenting = $value; } /** * {@inheritdoc} */ public function getPreventReparenting() { return $this->preventReparenting; } /** * {@inheritdoc} */ public function loadUpdatedCustomized(array $router_paths) { $query = parent::buildQuery(NULL); $query ->condition(db_or() ->condition('updated', 1) ->condition(db_and() ->condition('router_path', $router_paths, 'NOT IN') ->condition('external', 0) ->condition('customized', 1) ) ); $query_result = $query->execute(); if ($class = $this->entityType->getClass()) { // We provide the necessary arguments for PDO to create objects of the // specified entity class. // @see \Drupal\Core\Entity\EntityInterface::__construct() $query_result->setFetchMode(\PDO::FETCH_CLASS, $class, array(array(), $this->entityTypeId)); } return $query_result->fetchAllAssoc($this->idKey); } /** * {@inheritdoc} */ public function loadModuleAdminTasks() { $query = $this->buildQuery(NULL); $query ->condition('base.link_path', 'admin/%', 'LIKE') ->condition('base.hidden', 0, '>=') ->condition('base.module', 'system') ->condition('base.route_name', 'system.admin', '<>'); $ids = $query->execute()->fetchCol(1); return $this->loadMultiple($ids); } /** * {@inheritdoc} */ public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE) { // If plid == 0, there is nothing to update. if ($entity->plid) { // Check if at least one visible child exists in the table. $query = $this->getQuery(); $query ->condition('menu_name', $entity->menu_name) ->condition('hidden', 0) ->condition('plid', $entity->plid) ->count(); if ($exclude) { $query->condition('mlid', $entity->id(), '<>'); } $parent_has_children = ((bool) $query->execute()) ? 1 : 0; $this->database->update('menu_links') ->fields(array('has_children' => $parent_has_children)) ->condition('mlid', $entity->plid) ->execute(); } } /** * {@inheritdoc} */ public function findChildrenRelativeDepth(EntityInterface $entity) { // @todo Since all we need is a specific field from the base table, does it // make sense to convert to EFQ? $query = $this->database->select('menu_links'); $query->addField('menu_links', 'depth'); $query->condition('menu_name', $entity->menu_name); $query->orderBy('depth', 'DESC'); $query->range(0, 1); $i = 1; $p = 'p1'; while ($i <= MENU_MAX_DEPTH && $entity->{$p}) { $query->condition($p, $entity->{$p}); $p = 'p' . ++$i; } $max_depth = $query->execute()->fetchField(); return ($max_depth > $entity->depth) ? $max_depth - $entity->depth : 0; } /** * {@inheritdoc} */ public function moveChildren(EntityInterface $entity) { $query = $this->database->update($this->entityType->getBaseTable()); $query->fields(array('menu_name' => $entity->menu_name)); $p = 'p1'; $expressions = array(); for ($i = 1; $i <= $entity->depth; $p = 'p' . ++$i) { $expressions[] = array($p, ":p_$i", array(":p_$i" => $entity->{$p})); } $j = $entity->original->depth + 1; while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) { $expressions[] = array('p' . $i++, 'p' . $j++, array()); } while ($i <= MENU_MAX_DEPTH) { $expressions[] = array('p' . $i++, 0, array()); } $shift = $entity->depth - $entity->original->depth; if ($shift > 0) { // The order of expressions must be reversed so the new values don't // overwrite the old ones before they can be used because "Single-table // UPDATE assignments are generally evaluated from left to right" // @see http://dev.mysql.com/doc/refman/5.0/en/update.html $expressions = array_reverse($expressions); } foreach ($expressions as $expression) { $query->expression($expression[0], $expression[1], $expression[2]); } $query->expression('depth', 'depth + :depth', array(':depth' => $shift)); $query->condition('menu_name', $entity->original->menu_name); $p = 'p1'; for ($i = 1; $i <= MENU_MAX_DEPTH && $entity->original->{$p}; $p = 'p' . ++$i) { $query->condition($p, $entity->original->{$p}); } $query->execute(); // Check the has_children status of the parent, while excluding this item. $this->updateParentalStatus($entity->original, TRUE); } /** * {@inheritdoc} */ public function countMenuLinks($menu_name) { $query = $this->getQuery(); $query ->condition('menu_name', $menu_name) ->count(); return $query->execute(); } /** * {@inheritdoc} */ public function getParentFromHierarchy(EntityInterface $entity) { $parent_path = $entity->link_path; do { $parent = FALSE; $parent_path = substr($parent_path, 0, strrpos($parent_path, '/')); $query = $this->getQuery(); $query ->condition('mlid', $entity->id(), '<>') ->condition('module', 'system') // We always respect the link's 'menu_name'; inheritance for router // items is ensured in _menu_router_build(). ->condition('menu_name', $entity->menu_name) ->condition('link_path', $parent_path); $result = $query->execute(); // Only valid if we get a unique result. if (count($result) == 1) { $parent = $this->load(reset($result)); } } while ($parent === FALSE && $parent_path); return $parent; } /** * {@inheritdoc} */ public function createFromDefaultLink(array $item) { // Suggested items are disabled by default. $item += array( 'type' => MENU_NORMAL_ITEM, 'hidden' => 0, 'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])), ); if ($item['type'] == MENU_SUGGESTED_ITEM) { $item['hidden'] = 1; } // Note, we set this as 'system', so that we can be sure to distinguish all // the menu links generated automatically from hook_menu_link_defaults(). $item['module'] = 'system'; return $this->create($item); } }