/** * Tests Quick Edit autocomplete term behavior. */ public function testAutocompleteQuickEdit() { $this->drupalLogin($this->editor_user); $quickedit_uri = 'quickedit/form/node/' . $this->node->id() . '/' . $this->field_name . '/' . $this->node->language()->getId() . '/full'; $post = array('nocssjs' => 'true') + $this->getAjaxPageStatePostData(); $response = $this->drupalPost($quickedit_uri, 'application/vnd.drupal-ajax', $post); $ajax_commands = Json::decode($response); // Prepare form values for submission. drupalPostAJAX() is not suitable for // handling pages with JSON responses, so we need our own solution here. $form_tokens_found = preg_match('/\\sname="form_token" value="([^"]+)"/', $ajax_commands[0]['data'], $token_match) && preg_match('/\\sname="form_build_id" value="([^"]+)"/', $ajax_commands[0]['data'], $build_id_match); $this->assertTrue($form_tokens_found, 'Form tokens found in output.'); if ($form_tokens_found) { $post = array('form_id' => 'quickedit_field_form', 'form_token' => $token_match[1], 'form_build_id' => $build_id_match[1], $this->field_name => implode(', ', array($this->term1->getName(), 'new term', $this->term2->getName())), 'op' => t('Save')); // Submit field form and check response. Should render back all the terms. $response = $this->drupalPost($quickedit_uri, 'application/vnd.drupal-ajax', $post); $this->assertResponse(200); $ajax_commands = Json::decode($response); $this->drupalSetContent($ajax_commands[0]['data']); $this->assertLink($this->term1->getName()); $this->assertLink($this->term2->getName()); $this->assertText('new term'); $this->assertNoLink('new term'); // Load the form again, which should now get it back from TempStore. $quickedit_uri = 'quickedit/form/node/' . $this->node->id() . '/' . $this->field_name . '/' . $this->node->language()->getId() . '/full'; $post = array('nocssjs' => 'true') + $this->getAjaxPageStatePostData(); $response = $this->drupalPost($quickedit_uri, 'application/vnd.drupal-ajax', $post); $ajax_commands = Json::decode($response); // The AjaxResponse's first command is an InsertCommand which contains // the form to edit the taxonomy term field, it should contain all three // taxonomy terms, including the one that has just been newly created and // which is not yet stored. $this->drupalSetContent($ajax_commands[0]['data']); $this->assertFieldByName($this->field_name, implode(', ', array($this->term1->getName(), 'new term', $this->term2->label()))); // Save the entity. $post = array('nocssjs' => 'true'); $response = $this->drupalPost('quickedit/entity/node/' . $this->node->id(), 'application/json', $post); $this->assertResponse(200); // The full node display should now link to all entities, with the new // one created in the database as well. $this->drupalGet('node/' . $this->node->id()); $this->assertLink($this->term1->getName()); $this->assertLink($this->term2->getName()); $this->assertLink('new term'); } }
/** * {@inheritdoc} */ public function write(NodeInterface $node, array $grants, $realm = NULL, $delete = TRUE) { if ($delete) { $query = $this->database->delete('node_access')->condition('nid', $node->id()); if ($realm) { $query->condition('realm', array($realm, 'all'), 'IN'); } $query->execute(); } // Only perform work when node_access modules are active. if (!empty($grants) && count($this->moduleHandler->getImplementations('node_grants'))) { $query = $this->database->insert('node_access')->fields(array('nid', 'langcode', 'fallback', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete')); // If we have defined a granted langcode, use it. But if not, add a grant // for every language this node is translated to. foreach ($grants as $grant) { if ($realm && $realm != $grant['realm']) { continue; } if (isset($grant['langcode'])) { $grant_languages = array($grant['langcode'] => $this->languageManager->getLanguage($grant['langcode'])); } else { $grant_languages = $node->getTranslationLanguages(TRUE); } foreach ($grant_languages as $grant_langcode => $grant_language) { // Only write grants; denies are implicit. if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) { $grant['nid'] = $node->id(); $grant['langcode'] = $grant_langcode; // The record with the original langcode is used as the fallback. if ($grant['langcode'] == $node->language()->getId()) { $grant['fallback'] = 1; } else { $grant['fallback'] = 0; } $query->values($grant); } } } $query->execute(); } }
/** * Build the default links (Read more) for a node. * * @param \Drupal\node\NodeInterface $entity * The node object. * @param string $view_mode * A view mode identifier. * * @return array * An array that can be processed by drupal_pre_render_links(). */ protected static function buildLinks(NodeInterface $entity, $view_mode) { $links = array(); // Always display a read more link on teasers because we have no way // to know when a teaser view is different than a full view. if ($view_mode == 'teaser') { $node_title_stripped = strip_tags($entity->label()); $links['node-readmore'] = array('title' => t('Read more<span class="visually-hidden"> about @title</span>', array('@title' => $node_title_stripped)), 'url' => $entity->urlInfo(), 'language' => $entity->language(), 'html' => TRUE, 'attributes' => array('rel' => 'tag', 'title' => $node_title_stripped)); } return array('#theme' => 'links__node__node', '#links' => $links, '#attributes' => array('class' => array('links', 'inline'))); }
/** * Checks node revision access. * * @param \Drupal\node\NodeInterface $node * The node to check. * @param \Drupal\Core\Session\AccountInterface $account * A user object representing the user for whom the operation is to be * performed. * @param string $op * (optional) The specific operation being checked. Defaults to 'view.' * * @return bool * TRUE if the operation may be performed, FALSE otherwise. */ public function checkAccess(NodeInterface $node, AccountInterface $account, $op = 'view') { $map = array('view' => 'view all revisions', 'update' => 'revert all revisions', 'delete' => 'delete all revisions'); $bundle = $node->bundle(); $type_map = array('view' => "view {$bundle} revisions", 'update' => "revert {$bundle} revisions", 'delete' => "delete {$bundle} revisions"); if (!$node || !isset($map[$op]) || !isset($type_map[$op])) { // If there was no node to check against, or the $op was not one of the // supported ones, we return access denied. return FALSE; } // Statically cache access by revision ID, language code, user account ID, // and operation. $langcode = $node->language()->getId(); $cid = $node->getRevisionId() . ':' . $langcode . ':' . $account->id() . ':' . $op; if (!isset($this->access[$cid])) { // Perform basic permission checks first. if (!$account->hasPermission($map[$op]) && !$account->hasPermission($type_map[$op]) && !$account->hasPermission('administer nodes')) { $this->access[$cid] = FALSE; return FALSE; } // There should be at least two revisions. If the vid of the given node // and the vid of the default revision differ, then we already have two // different revisions so there is no need for a separate database check. // Also, if you try to revert to or delete the default revision, that's // not good. if ($node->isDefaultRevision() && ($this->nodeStorage->countDefaultLanguageRevisions($node) == 1 || $op == 'update' || $op == 'delete')) { $this->access[$cid] = FALSE; } elseif ($account->hasPermission('administer nodes')) { $this->access[$cid] = TRUE; } else { // First check the access to the default revision and finally, if the // node passed in is not the default revision then access to that, too. $this->access[$cid] = $this->nodeAccess->access($this->nodeStorage->load($node->id()), $op, $account) && ($node->isDefaultRevision() || $this->nodeAccess->access($node, $op, $account)); } } return $this->access[$cid]; }
/** * Generates an overview table of older revisions of a node. * * @param \Drupal\node\NodeInterface $node * A node object. * * @return array * An array as expected by drupal_render(). */ public function revisionOverview(NodeInterface $node) { $account = $this->currentUser(); $langcode = $node->language()->getId(); $langname = $node->language()->getName(); $languages = $node->getTranslationLanguages(); $has_translations = count($languages) > 1; $node_storage = $this->entityManager()->getStorage('node'); $type = $node->getType(); $build['#title'] = $has_translations ? $this->t('@langname revisions for %title', ['@langname' => $langname, '%title' => $node->label()]) : $this->t('Revisions for %title', ['%title' => $node->label()]); $header = array($this->t('Revision'), $this->t('Operations')); $revert_permission = ($account->hasPermission("revert {$type} revisions") || $account->hasPermission('revert all revisions') || $account->hasPermission('administer nodes')) && $node->access('update'); $delete_permission = ($account->hasPermission("delete {$type} revisions") || $account->hasPermission('delete all revisions') || $account->hasPermission('administer nodes')) && $node->access('delete'); $rows = array(); $vids = $node_storage->revisionIds($node); $latest_revision = TRUE; foreach (array_reverse($vids) as $vid) { /** @var \Drupal\node\NodeInterface $revision */ $revision = $node_storage->loadRevision($vid); // Only show revisions that are affected by the language that is being // displayed. if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) { $username = ['#theme' => 'username', '#account' => $revision->getRevisionAuthor()]; // Use revision link to link to revisions that are not active. $date = $this->dateFormatter->format($revision->revision_timestamp->value, 'short'); if ($vid != $node->getRevisionId()) { $link = $this->l($date, new Url('entity.node.revision', ['node' => $node->id(), 'node_revision' => $vid])); } else { $link = $node->link($date); } $row = []; $column = ['data' => ['#type' => 'inline_template', '#template' => '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}', '#context' => ['date' => $link, 'username' => $this->renderer->renderPlain($username), 'message' => ['#markup' => $revision->revision_log->value, '#allowed_tags' => Xss::getHtmlTagList()]]]]; // @todo Simplify once https://www.drupal.org/node/2334319 lands. $this->renderer->addCacheableDependency($column['data'], $username); $row[] = $column; if ($latest_revision) { $row[] = ['data' => ['#prefix' => '<em>', '#markup' => $this->t('Current revision'), '#suffix' => '</em>']]; foreach ($row as &$current) { $current['class'] = ['revision-current']; } $latest_revision = FALSE; } else { $links = []; if ($revert_permission) { $links['revert'] = ['title' => $this->t('Revert'), 'url' => $has_translations ? Url::fromRoute('node.revision_revert_translation_confirm', ['node' => $node->id(), 'node_revision' => $vid, 'langcode' => $langcode]) : Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $vid])]; } if ($delete_permission) { $links['delete'] = ['title' => $this->t('Delete'), 'url' => Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $vid])]; } $row[] = ['data' => ['#type' => 'operations', '#links' => $links]]; } $rows[] = $row; } } $build['node_revisions_table'] = array('#theme' => 'table', '#rows' => $rows, '#header' => $header, '#attached' => array('library' => array('node/drupal.node.admin'))); return $build; }