Move into nested docroot
This commit is contained in:
parent
83a0d3a149
commit
c8b70abde9
13405 changed files with 0 additions and 0 deletions
47
web/core/modules/contextual/src/ContextualController.php
Normal file
47
web/core/modules/contextual/src/ContextualController.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\contextual;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
|
||||
/**
|
||||
* Returns responses for Contextual module routes.
|
||||
*/
|
||||
class ContextualController implements ContainerAwareInterface {
|
||||
|
||||
use ContainerAwareTrait;
|
||||
|
||||
/**
|
||||
* Returns the requested rendered contextual links.
|
||||
*
|
||||
* Given a list of contextual links IDs, render them. Hence this must be
|
||||
* robust to handle arbitrary input.
|
||||
*
|
||||
* @see contextual_preprocess()
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\JsonResponse
|
||||
* The JSON response.
|
||||
*/
|
||||
public function render(Request $request) {
|
||||
$ids = $request->request->get('ids');
|
||||
if (!isset($ids)) {
|
||||
throw new BadRequestHttpException(t('No contextual ids specified.'));
|
||||
}
|
||||
|
||||
$rendered = array();
|
||||
foreach ($ids as $id) {
|
||||
$element = array(
|
||||
'#type' => 'contextual_links',
|
||||
'#contextual_links' => _contextual_id_to_links($id),
|
||||
);
|
||||
$rendered[$id] = $this->container->get('renderer')->renderRoot($element);
|
||||
}
|
||||
|
||||
return new JsonResponse($rendered);
|
||||
}
|
||||
|
||||
}
|
115
web/core/modules/contextual/src/Element/ContextualLinks.php
Normal file
115
web/core/modules/contextual/src/Element/ContextualLinks.php
Normal file
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\contextual\Element;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Render\Element\RenderElement;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides a contextual_links element.
|
||||
*
|
||||
* @RenderElement("contextual_links")
|
||||
*/
|
||||
class ContextualLinks extends RenderElement {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo() {
|
||||
$class = get_class($this);
|
||||
return array(
|
||||
'#pre_render' => array(
|
||||
array($class, 'preRenderLinks'),
|
||||
),
|
||||
'#theme' => 'links__contextual',
|
||||
'#links' => array(),
|
||||
'#attributes' => array('class' => array('contextual-links')),
|
||||
'#attached' => array(
|
||||
'library' => array(
|
||||
'contextual/drupal.contextual-links',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-render callback: Builds a renderable array for contextual links.
|
||||
*
|
||||
* @param array $element
|
||||
* A renderable array containing a #contextual_links property, which is a
|
||||
* keyed array. Each key is the name of the group of contextual links to
|
||||
* render (based on the 'group' key in the *.links.contextual.yml files for
|
||||
* all enabled modules). The value contains an associative array containing
|
||||
* the following keys:
|
||||
* - route_parameters: The route parameters passed to the url generator.
|
||||
* - metadata: Any additional data needed in order to alter the link.
|
||||
* @code
|
||||
* array('#contextual_links' => array(
|
||||
* 'block' => array(
|
||||
* 'route_parameters' => array('block' => 'system.menu-tools'),
|
||||
* ),
|
||||
* 'menu' => array(
|
||||
* 'route_parameters' => array('menu' => 'tools'),
|
||||
* ),
|
||||
* ))
|
||||
* @endcode
|
||||
*
|
||||
* @return array
|
||||
* A renderable array representing contextual links.
|
||||
*/
|
||||
public static function preRenderLinks(array $element) {
|
||||
// Retrieve contextual menu links.
|
||||
$items = array();
|
||||
|
||||
$contextual_links_manager = static::contextualLinkManager();
|
||||
|
||||
foreach ($element['#contextual_links'] as $group => $args) {
|
||||
$args += array(
|
||||
'route_parameters' => array(),
|
||||
'metadata' => array(),
|
||||
);
|
||||
$items += $contextual_links_manager->getContextualLinksArrayByGroup($group, $args['route_parameters'], $args['metadata']);
|
||||
}
|
||||
|
||||
// Transform contextual links into parameters suitable for links.html.twig.
|
||||
$links = array();
|
||||
foreach ($items as $class => $item) {
|
||||
$class = Html::getClass($class);
|
||||
$links[$class] = array(
|
||||
'title' => $item['title'],
|
||||
'url' => Url::fromRoute(isset($item['route_name']) ? $item['route_name'] : '', isset($item['route_parameters']) ? $item['route_parameters'] : []),
|
||||
);
|
||||
}
|
||||
$element['#links'] = $links;
|
||||
|
||||
// Allow modules to alter the renderable contextual links element.
|
||||
static::moduleHandler()->alter('contextual_links_view', $element, $items);
|
||||
|
||||
// If there are no links, tell drupal_render() to abort rendering.
|
||||
if (empty($element['#links'])) {
|
||||
$element['#printed'] = TRUE;
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the contextual link manager.
|
||||
*
|
||||
* @return \Drupal\Core\Menu\ContextualLinkManager
|
||||
*/
|
||||
protected static function contextualLinkManager() {
|
||||
return \Drupal::service('plugin.manager.menu.contextual_link');
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the module handler.
|
||||
*
|
||||
* @return \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected static function moduleHandler() {
|
||||
return \Drupal::moduleHandler();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\contextual\Element;
|
||||
|
||||
use Drupal\Core\Template\Attribute;
|
||||
use Drupal\Core\Render\Element\RenderElement;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* Provides a contextual_links_placeholder element.
|
||||
*
|
||||
* @RenderElement("contextual_links_placeholder")
|
||||
*/
|
||||
class ContextualLinksPlaceholder extends RenderElement {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo() {
|
||||
$class = get_class($this);
|
||||
return array(
|
||||
'#pre_render' => array(
|
||||
array($class, 'preRenderPlaceholder'),
|
||||
),
|
||||
'#id' => NULL,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-render callback: Renders a contextual links placeholder into #markup.
|
||||
*
|
||||
* Renders an empty (hence invisible) placeholder div with a data-attribute
|
||||
* that contains an identifier ("contextual id"), which allows the JavaScript
|
||||
* of the drupal.contextual-links library to dynamically render contextual
|
||||
* links.
|
||||
*
|
||||
* @param array $element
|
||||
* A structured array with #id containing a "contextual id".
|
||||
*
|
||||
* @return array
|
||||
* The passed-in element with a contextual link placeholder in '#markup'.
|
||||
*
|
||||
* @see _contextual_links_to_id()
|
||||
*/
|
||||
public static function preRenderPlaceholder(array $element) {
|
||||
$element['#markup'] = SafeMarkup::format('<div@attributes></div>', ['@attributes' => new Attribute(['data-contextual-id' => $element['#id']])]);
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\contextual\Plugin\views\field;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Routing\RedirectDestinationTrait;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\views\Plugin\views\field\FieldPluginBase;
|
||||
use Drupal\views\ResultRow;
|
||||
|
||||
/**
|
||||
* Provides a handler that adds contextual links.
|
||||
*
|
||||
* @ingroup views_field_handlers
|
||||
*
|
||||
* @ViewsField("contextual_links")
|
||||
*/
|
||||
class ContextualLinks extends FieldPluginBase {
|
||||
|
||||
use RedirectDestinationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function usesGroupBy() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
|
||||
$options['fields'] = array('default' => array());
|
||||
$options['destination'] = array('default' => 1);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
$all_fields = $this->view->display_handler->getFieldLabels();
|
||||
// Offer to include only those fields that follow this one.
|
||||
$field_options = array_slice($all_fields, 0, array_search($this->options['id'], array_keys($all_fields)));
|
||||
$form['fields'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Fields'),
|
||||
'#description' => $this->t('Fields to be included as contextual links.'),
|
||||
'#options' => $field_options,
|
||||
'#default_value' => $this->options['fields'],
|
||||
);
|
||||
$form['destination'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Include destination'),
|
||||
'#description' => $this->t('Include a "destination" parameter in the link to return the user to the original view upon completing the contextual action.'),
|
||||
'#options' => array(
|
||||
'0' => $this->t('No'),
|
||||
'1' => $this->t('Yes'),
|
||||
),
|
||||
'#default_value' => $this->options['destination'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preRender(&$values) {
|
||||
// Add a row plugin css class for the contextual link.
|
||||
$class = 'contextual-region';
|
||||
if (!empty($this->view->style_plugin->options['row_class'])) {
|
||||
$this->view->style_plugin->options['row_class'] .= " $class";
|
||||
}
|
||||
else {
|
||||
$this->view->style_plugin->options['row_class'] = $class;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\views\Plugin\views\field\FieldPluginBase::render().
|
||||
*
|
||||
* Renders the contextual fields.
|
||||
*
|
||||
* @param \Drupal\views\ResultRow $values
|
||||
* The values retrieved from a single row of a view's query result.
|
||||
*
|
||||
* @see contextual_preprocess()
|
||||
* @see contextual_contextual_links_view_alter()
|
||||
*/
|
||||
public function render(ResultRow $values) {
|
||||
$links = array();
|
||||
foreach ($this->options['fields'] as $field) {
|
||||
$rendered_field = $this->view->style_plugin->getField($values->index, $field);
|
||||
if (empty($rendered_field)) {
|
||||
continue;
|
||||
}
|
||||
$title = $this->view->field[$field]->last_render_text;
|
||||
$path = '';
|
||||
if (!empty($this->view->field[$field]->options['alter']['path'])) {
|
||||
$path = $this->view->field[$field]->options['alter']['path'];
|
||||
}
|
||||
elseif (!empty($this->view->field[$field]->options['alter']['url']) && $this->view->field[$field]->options['alter']['url'] instanceof Url) {
|
||||
$path = $this->view->field[$field]->options['alter']['url']->toString();
|
||||
}
|
||||
if (!empty($title) && !empty($path)) {
|
||||
// Make sure that tokens are replaced for this paths as well.
|
||||
$tokens = $this->getRenderTokens(array());
|
||||
$path = strip_tags(Html::decodeEntities(strtr($path, $tokens)));
|
||||
|
||||
$links[$field] = array(
|
||||
'href' => $path,
|
||||
'title' => $title,
|
||||
);
|
||||
if (!empty($this->options['destination'])) {
|
||||
$links[$field]['query'] = $this->getDestinationArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Renders a contextual links placeholder.
|
||||
if (!empty($links)) {
|
||||
$contextual_links = array(
|
||||
'contextual' => array(
|
||||
'',
|
||||
array(),
|
||||
array(
|
||||
'contextual-views-field-links' => UrlHelper::encodePath(Json::encode($links)),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$element = array(
|
||||
'#type' => 'contextual_links_placeholder',
|
||||
'#id' => _contextual_links_to_id($contextual_links),
|
||||
);
|
||||
return drupal_render($element);
|
||||
}
|
||||
else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() { }
|
||||
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\contextual\Tests;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Core\Template\Attribute;
|
||||
|
||||
/**
|
||||
* Tests if contextual links are showing on the front page depending on
|
||||
* permissions.
|
||||
*
|
||||
* @group contextual
|
||||
*/
|
||||
class ContextualDynamicContextTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* A user with permission to access contextual links and edit content.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $editorUser;
|
||||
|
||||
/**
|
||||
* An authenticated user with permission to access contextual links.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $authenticatedUser;
|
||||
|
||||
/**
|
||||
* A simulated anonymous user with access only to node content.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $anonymousUser;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('contextual', 'node', 'views', 'views_ui', 'language', 'menu_test');
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
|
||||
$this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
|
||||
|
||||
ConfigurableLanguage::createFromLangcode('it')->save();
|
||||
$this->rebuildContainer();
|
||||
|
||||
$this->editorUser = $this->drupalCreateUser(array('access content', 'access contextual links', 'edit any article content'));
|
||||
$this->authenticatedUser = $this->drupalCreateUser(array('access content', 'access contextual links'));
|
||||
$this->anonymousUser = $this->drupalCreateUser(array('access content'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests contextual links with different permissions.
|
||||
*
|
||||
* Ensures that contextual link placeholders always exist, even if the user is
|
||||
* not allowed to use contextual links.
|
||||
*/
|
||||
function testDifferentPermissions() {
|
||||
$this->drupalLogin($this->editorUser);
|
||||
|
||||
// Create three nodes in the following order:
|
||||
// - An article, which should be user-editable.
|
||||
// - A page, which should not be user-editable.
|
||||
// - A second article, which should also be user-editable.
|
||||
$node1 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
|
||||
$node2 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 1));
|
||||
$node3 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
|
||||
|
||||
// Now, on the front page, all article nodes should have contextual links
|
||||
// placeholders, as should the view that contains them.
|
||||
$ids = [
|
||||
'node:node=' . $node1->id() . ':changed=' . $node1->getChangedTime() . '&langcode=en',
|
||||
'node:node=' . $node2->id() . ':changed=' . $node2->getChangedTime() . '&langcode=en',
|
||||
'node:node=' . $node3->id() . ':changed=' . $node3->getChangedTime() . '&langcode=en',
|
||||
'entity.view.edit_form:view=frontpage:location=page&name=frontpage&display_id=page_1&langcode=en',
|
||||
];
|
||||
|
||||
// Editor user: can access contextual links and can edit articles.
|
||||
$this->drupalGet('node');
|
||||
for ($i = 0; $i < count($ids); $i++) {
|
||||
$this->assertContextualLinkPlaceHolder($ids[$i]);
|
||||
}
|
||||
$this->renderContextualLinks(array(), 'node');
|
||||
$this->assertResponse(400);
|
||||
$this->assertRaw('No contextual ids specified.');
|
||||
$response = $this->renderContextualLinks($ids, 'node');
|
||||
$this->assertResponse(200);
|
||||
$json = Json::decode($response);
|
||||
$this->assertIdentical($json[$ids[0]], '<ul class="contextual-links"><li class="entitynodeedit-form"><a href="' . base_path() . 'node/1/edit">Edit</a></li></ul>');
|
||||
$this->assertIdentical($json[$ids[1]], '');
|
||||
$this->assertIdentical($json[$ids[2]], '<ul class="contextual-links"><li class="entitynodeedit-form"><a href="' . base_path() . 'node/3/edit">Edit</a></li></ul>');
|
||||
$this->assertIdentical($json[$ids[3]], '');
|
||||
|
||||
// Verify that link language is properly handled.
|
||||
$node3->addTranslation('it')->set('title', $this->randomString())->save();
|
||||
$id = 'node:node=' . $node3->id() . ':changed=' . $node3->getChangedTime() . '&langcode=it';
|
||||
$this->drupalGet('node', ['language' => ConfigurableLanguage::createFromLangcode('it')]);
|
||||
$this->assertContextualLinkPlaceHolder($id);
|
||||
|
||||
// Authenticated user: can access contextual links, cannot edit articles.
|
||||
$this->drupalLogin($this->authenticatedUser);
|
||||
$this->drupalGet('node');
|
||||
for ($i = 0; $i < count($ids); $i++) {
|
||||
$this->assertContextualLinkPlaceHolder($ids[$i]);
|
||||
}
|
||||
$this->renderContextualLinks(array(), 'node');
|
||||
$this->assertResponse(400);
|
||||
$this->assertRaw('No contextual ids specified.');
|
||||
$response = $this->renderContextualLinks($ids, 'node');
|
||||
$this->assertResponse(200);
|
||||
$json = Json::decode($response);
|
||||
$this->assertIdentical($json[$ids[0]], '');
|
||||
$this->assertIdentical($json[$ids[1]], '');
|
||||
$this->assertIdentical($json[$ids[2]], '');
|
||||
$this->assertIdentical($json[$ids[3]], '');
|
||||
|
||||
// Anonymous user: cannot access contextual links.
|
||||
$this->drupalLogin($this->anonymousUser);
|
||||
$this->drupalGet('node');
|
||||
for ($i = 0; $i < count($ids); $i++) {
|
||||
$this->assertNoContextualLinkPlaceHolder($ids[$i]);
|
||||
}
|
||||
$this->renderContextualLinks(array(), 'node');
|
||||
$this->assertResponse(403);
|
||||
$this->renderContextualLinks($ids, 'node');
|
||||
$this->assertResponse(403);
|
||||
|
||||
// Get a page where contextual links are directly rendered.
|
||||
$this->drupalGet(Url::fromRoute('menu_test.contextual_test'));
|
||||
$this->assertEscaped("<script>alert('Welcome to the jungle!')</script>");
|
||||
$this->assertLink('Edit menu - contextual');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a contextual link placeholder with the given id exists.
|
||||
*
|
||||
* @param string $id
|
||||
* A contextual link id.
|
||||
*
|
||||
* @return bool
|
||||
* The result of the assertion.
|
||||
*/
|
||||
protected function assertContextualLinkPlaceHolder($id) {
|
||||
return $this->assertRaw('<div' . new Attribute(array('data-contextual-id' => $id)) . '></div>', format_string('Contextual link placeholder with id @id exists.', array('@id' => $id)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a contextual link placeholder with the given id does not exist.
|
||||
*
|
||||
* @param string $id
|
||||
* A contextual link id.
|
||||
*
|
||||
* @return bool
|
||||
* The result of the assertion.
|
||||
*/
|
||||
protected function assertNoContextualLinkPlaceHolder($id) {
|
||||
return $this->assertNoRaw('<div' . new Attribute(array('data-contextual-id' => $id)) . '></div>', format_string('Contextual link placeholder with id @id does not exist.', array('@id' => $id)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get server-rendered contextual links for the given contextual link ids.
|
||||
*
|
||||
* @param array $ids
|
||||
* An array of contextual link ids.
|
||||
* @param string $current_path
|
||||
* The Drupal path for the page for which the contextual links are rendered.
|
||||
*
|
||||
* @return string
|
||||
* The response body.
|
||||
*/
|
||||
protected function renderContextualLinks($ids, $current_path) {
|
||||
$post = array();
|
||||
for ($i = 0; $i < count($ids); $i++) {
|
||||
$post['ids[' . $i . ']'] = $ids[$i];
|
||||
}
|
||||
return $this->drupalPostWithFormat('contextual/render', 'json', $post, array('query' => array('destination' => $current_path)));
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue