diff --git a/src/TwigExtension.php b/src/TwigExtension.php
index 7f11d68..c4484bd 100644
--- a/src/TwigExtension.php
+++ b/src/TwigExtension.php
@@ -59,6 +59,7 @@ class TwigExtension extends \Twig_Extension {
new \Twig_SimpleFunction('drupal_messages', [$this, 'drupalMessages']),
new \Twig_SimpleFunction('drupal_breadcrumb', [$this, 'drupalBreadcrumb']),
new \Twig_SimpleFunction('drupal_breakpoint', [$this, 'drupalBreakpoint'], $all_options),
+ new \Twig_SimpleFunction('contextual_links', [$this, 'contextualLInks']),
];
}
@@ -709,6 +710,41 @@ class TwigExtension extends \Twig_Extension {
->toRenderable();
}
+ /**
+ * Builds contextual links.
+ *
+ * Examples:
+ * @code
+ * # Basic usage.
+ *
+ * {{ contextual_links('entity.view.edit_form:view=frontpage&display_id=feed_1') }}
+ * {{ drupal_view('frontpage') }}
+ *
+ *
+ * # Multiple links.
+ *
+ * {{ contextual_links('node:node=123|block_content:block_content=123') }}
+ * {{ content }}
+ *
+ * @endcode
+ *
+ * @param string $id
+ * A serialized representation of a #contextual_links property value array.
+ *
+ * @return array
+ * A renderable array representing contextual links.
+ *
+ * @see https://www.drupal.org/node/2133283
+ */
+ public function contextualLinks($id) {
+ $build['#cache']['contexts'] = ['user.permissions'];
+ if (\Drupal::currentUser()->hasPermission('access contextual links')) {
+ $build['#type'] = 'contextual_links_placeholder';
+ $build['#id'] = $id;
+ }
+ return $build;
+ }
+
/**
* Emits a breakpoint to the debug client.
*
@@ -798,7 +834,6 @@ class TwigExtension extends \Twig_Extension {
return file_url_transform_relative($image_style->buildUrl($path));
}
else {
- // @todo Throw an exception in 3.x.
trigger_error(sprintf('Could not load image style %s.', $style));
}
}
diff --git a/tests/src/Functional/TwigTweakTest.php b/tests/src/Functional/TwigTweakTest.php
index 3ea7115..86d879e 100644
--- a/tests/src/Functional/TwigTweakTest.php
+++ b/tests/src/Functional/TwigTweakTest.php
@@ -34,6 +34,7 @@ class TwigTweakTest extends BrowserTestBase {
'image',
'responsive_image',
'language',
+ 'contextual',
];
/**
@@ -285,6 +286,15 @@ class TwigTweakTest extends BrowserTestBase {
$xpath = '//div[@class = "tt-token-replace" and text() = "Site name: Drupal"]';
$this->assertByXpath($xpath);
+ // Test contextual links.
+ $xpath = '//div[@class="tt-contextual-links" and not(div[@data-contextual-id])]';
+ $this->assertByXpath($xpath);
+
+ $this->grantPermissions(Role::load(Role::ANONYMOUS_ID), ['access contextual links']);
+ $this->drupalGet($this->getUrl());
+ $xpath = '//div[@class="tt-contextual-links" and div[@data-contextual-id]]';
+ $this->assertByXpath($xpath);
+
// Test preg replacement.
$xpath = '//div[@class = "tt-preg-replace" and text() = "FOO-bar"]';
$this->assertByXpath($xpath);
diff --git a/tests/twig_tweak_test/templates/twig-tweak-test.html.twig b/tests/twig_tweak_test/templates/twig-tweak-test.html.twig
index 128a768..847ced8 100644
--- a/tests/twig_tweak_test/templates/twig-tweak-test.html.twig
+++ b/tests/twig_tweak_test/templates/twig-tweak-test.html.twig
@@ -52,6 +52,7 @@
{{ drupal_messages() }}
{{ drupal_breadcrumb() }}
{{ drupal_link('Administration', 'admin', {absolute: true}, true) }}
+ {{ contextual_links('node:node=1') }}
{{ 'Site name: [site:name]' | token_replace }}
{{ 'FOO' | preg_replace('/(foo)/i', '$1-bar') }}
{{ 'public://images/ocean.jpg' | image_style('thumbnail') }}