Move into nested docroot

This commit is contained in:
Rob Davies 2017-02-13 15:31:17 +00:00
parent 83a0d3a149
commit c8b70abde9
13405 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,5 @@
name: Accept header based routing test
core: 8.x
type: module
package: Testing
version: VERSION

View file

@ -0,0 +1,5 @@
services:
accept_header_matcher:
class: Drupal\accept_header_routing_test\Routing\AcceptHeaderMatcher
tags:
- { name: route_filter }

View file

@ -0,0 +1,42 @@
<?php
namespace Drupal\accept_header_routing_test;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Example implementation of accept header based content negotation.
*/
class AcceptHeaderMiddleware implements HttpKernelInterface {
/**
* Constructs a new AcceptHeaderMiddleware instance.
*
* @param \Symfony\Component\HttpKernel\HttpKernelInterface $app
* The app.
*/
public function __construct(HttpKernelInterface $app) {
$this->app = $app;
}
/**
* {@inheritdoc}
*/
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
$mapping = [
'application/json' => 'json',
'application/hal+json' => 'hal_json',
'application/xml' => 'xml',
'text/html' => 'html',
];
$accept = $request->headers->get('Accept') ?: ['text/html'];
if (isset($mapping[$accept[0]])) {
$request->setRequestFormat($mapping[$accept[0]]);
}
return $this->app->handle($request, $type, $catch);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Drupal\accept_header_routing_test;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceModifierInterface;
/**
* Service provider for the accept_header_routing_test module.
*/
class AcceptHeaderRoutingTestServiceProvider implements ServiceModifierInterface {
/**
* {@inheritdoc}
*/
public function alter(ContainerBuilder $container) {
// Remove the basic content negotation middleware and replace it with a
// basic header based one.
$container->register('http_middleware.negotiation', 'Drupal\accept_header_routing_test\AcceptHeaderMiddleware')
->addTag('http_middleware', ['priority' => 400]);
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace Drupal\accept_header_routing_test\Routing;
use Drupal\Core\Routing\RouteFilterInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Filters routes based on the media type specified in the HTTP Accept headers.
*/
class AcceptHeaderMatcher implements RouteFilterInterface {
/**
* {@inheritdoc}
*/
public function filter(RouteCollection $collection, Request $request) {
// Generates a list of Symfony formats matching the acceptable MIME types.
// @todo replace by proper content negotiation library.
$acceptable_mime_types = $request->getAcceptableContentTypes();
$acceptable_formats = array_filter(array_map(array($request, 'getFormat'), $acceptable_mime_types));
$primary_format = $request->getRequestFormat();
foreach ($collection as $name => $route) {
// _format could be a |-delimited list of supported formats.
$supported_formats = array_filter(explode('|', $route->getRequirement('_format')));
if (empty($supported_formats)) {
// No format restriction on the route, so it always matches. Move it to
// the end of the collection by re-adding it.
$collection->add($name, $route);
}
elseif (in_array($primary_format, $supported_formats)) {
// Perfect match, which will get a higher priority by leaving the route
// on top of the list.
}
// The route partially matches if it doesn't care about format, if it
// explicitly allows any format, or if one of its allowed formats is
// in the request's list of acceptable formats.
elseif (in_array('*/*', $acceptable_mime_types) || array_intersect($acceptable_formats, $supported_formats)) {
// Move it to the end of the list.
$collection->add($name, $route);
}
else {
// Remove the route if it does not match at all.
$collection->remove($name);
}
}
if (count($collection)) {
return $collection;
}
// We do not throw a
// \Symfony\Component\Routing\Exception\ResourceNotFoundException here
// because we don't want to return a 404 status code, but rather a 406.
throw new NotAcceptableHttpException('No route found for the specified formats ' . implode(' ', $acceptable_mime_types));
}
/**
* {@inheritdoc}
*/
public function applies(Route $route) {
return TRUE;
}
}

View file

@ -0,0 +1,109 @@
<?php
namespace Drupal\Tests\accept_header_routing_teste\Unit\Routing;
use Drupal\accept_header_routing_test\Routing\AcceptHeaderMatcher;
use Drupal\Tests\Core\Routing\RoutingFixtures;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\Request;
/**
* Confirm that the mime types partial matcher is functioning properly.
*
* @group Routing
*/
class AcceptHeaderMatcherTest extends UnitTestCase {
/**
* A collection of shared fixture data for tests.
*
* @var \Drupal\Tests\Core\Routing\RoutingFixtures
*/
protected $fixtures;
/**
* The matcher object that is going to be tested.
*
* @var \Drupal\accept_header_routing_test\Routing\AcceptHeaderMatcher
*/
protected $matcher;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->fixtures = new RoutingFixtures();
$this->matcher = new AcceptHeaderMatcher();
}
/**
* Provides data for the Accept header filtering test.
*
* @see Drupal\Tests\Core\Routing\AcceptHeaderMatcherTest::testAcceptFiltering()
*/
public function acceptFilterProvider() {
return [
// Check that JSON routes get filtered and prioritized correctly.
['application/json, text/xml;q=0.9', 'json', 'route_c', 'route_e'],
// Tests a JSON request with alternative JSON MIME type Accept header.
['application/x-json, text/xml;q=0.9', 'json', 'route_c', 'route_e'],
// Tests a standard HTML request.
['text/html, text/xml;q=0.9', 'html', 'route_e', 'route_c'],
];
}
/**
* Tests that requests using Accept headers get filtered correctly.
*
* @param string $accept_header
* The HTTP Accept header value of the request.
* @param string $format
* The request format.
* @param string $included_route
* The route name that should survive the filter and be ranked first.
* @param string $excluded_route
* The route name that should be filtered out during matching.
*
* @dataProvider acceptFilterProvider
*/
public function testAcceptFiltering($accept_header, $format, $included_route, $excluded_route) {
$collection = $this->fixtures->sampleRouteCollection();
$request = Request::create('path/two', 'GET');
$request->headers->set('Accept', $accept_header);
$request->setRequestFormat($format);
$routes = $this->matcher->filter($collection, $request);
$this->assertEquals(count($routes), 4, 'The correct number of routes was found.');
$this->assertNotNull($routes->get($included_route), "Route $included_route was found when matching $accept_header.");
$this->assertNull($routes->get($excluded_route), "Route $excluded_route was not found when matching $accept_header.");
foreach ($routes as $name => $route) {
$this->assertEquals($name, $included_route, "Route $included_route is the first one in the collection when matching $accept_header.");
break;
}
}
/**
* Confirms that the AcceptHeaderMatcher throws an exception for no-route.
*
* @expectedException \Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException
* @expectedExceptionMessage No route found for the specified formats application/json text/xml.
*/
public function testNoRouteFound() {
// Remove the sample routes that would match any method.
$routes = $this->fixtures->sampleRouteCollection();
$routes->remove('route_a');
$routes->remove('route_b');
$routes->remove('route_c');
$routes->remove('route_d');
$request = Request::create('path/two', 'GET');
$request->headers->set('Accept', 'application/json, text/xml;q=0.9');
$request->setRequestFormat('json');
$this->matcher->filter($routes, $request);
$this->matcher->filter($routes, $request);
$this->fail('No exception was thrown.');
}
}

View file

@ -0,0 +1,6 @@
name: 'Action test'
type: module
description: 'Support module for action testing.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,33 @@
<?php
namespace Drupal\action_test\Plugin\Action;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Provides an operation with no type specified.
*
* @Action(
* id = "action_test_no_type",
* label = @Translation("An operation with no type specified")
* )
*/
class NoType extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
$result = AccessResult::allowed();
return $return_as_object ? $result : $result->isAllowed();
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\action_test\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Provides an operation to save user entities.
*
* @Action(
* id = "action_test_save_entity",
* label = @Translation("Saves entities"),
* type = "user"
* )
*/
class SaveEntity extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
$entity->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\Core\Entity\EntityInterface $object */
return $object->access('update', $account, $return_as_object);
}
}

View file

@ -0,0 +1,6 @@
name: 'AJAX form test mock module'
type: module
description: 'Test for AJAX form calls.'
core: 8.x
package: Testing
version: VERSION

View file

@ -0,0 +1,212 @@
<?php
/**
* @file
* Simpletest mock module for Ajax forms testing.
*/
use Drupal\Core\Ajax\AddCssCommand;
use Drupal\Core\Ajax\AfterCommand;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\AlertCommand;
use Drupal\Core\Ajax\AppendCommand;
use Drupal\Core\Ajax\BeforeCommand;
use Drupal\Core\Ajax\ChangedCommand;
use Drupal\Core\Ajax\CssCommand;
use Drupal\Core\Ajax\DataCommand;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\InsertCommand;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Ajax\PrependCommand;
use Drupal\Core\Ajax\RemoveCommand;
use Drupal\Core\Ajax\RestripeCommand;
use Drupal\Core\Ajax\SettingsCommand;
use Drupal\Core\Form\FormStateInterface;
/**
* Ajax form callback: Selects 'after'.
*/
function ajax_forms_test_advanced_commands_after_callback($form, FormStateInterface $form_state) {
$selector = '#after_div';
$response = new AjaxResponse();
$response->addCommand(new AfterCommand($selector, "This will be placed after"));
return $response;
}
/**
* Ajax form callback: Selects 'alert'.
*/
function ajax_forms_test_advanced_commands_alert_callback($form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new AlertCommand('Alert'));
return $response;
}
/**
* Ajax form callback: Selects 'append'.
*/
function ajax_forms_test_advanced_commands_append_callback($form, FormStateInterface $form_state) {
$selector = '#append_div';
$response = new AjaxResponse();
$response->addCommand(new AppendCommand($selector, "Appended text"));
return $response;
}
/**
* Ajax form callback: Selects 'before'.
*/
function ajax_forms_test_advanced_commands_before_callback($form, FormStateInterface $form_state) {
$selector = '#before_div';
$response = new AjaxResponse();
$response->addCommand(new BeforeCommand($selector, "Before text"));
return $response;
}
/**
* Ajax form callback: Selects 'changed'.
*/
function ajax_forms_test_advanced_commands_changed_callback($form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new ChangedCommand('#changed_div'));
return $response;
}
/**
* Ajax form callback: Selects 'changed' with asterisk marking inner div.
*/
function ajax_forms_test_advanced_commands_changed_asterisk_callback($form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new ChangedCommand('#changed_div', '#changed_div_mark_this'));
return $response;
}
/**
* Ajax form callback: Selects 'css'.
*/
function ajax_forms_test_advanced_commands_css_callback($form, FormStateInterface $form_state) {
$selector = '#css_div';
$color = 'blue';
$response = new AjaxResponse();
$response->addCommand(new CssCommand($selector, array('background-color' => $color)));
return $response;
}
/**
* Ajax form callback: Selects 'data'.
*/
function ajax_forms_test_advanced_commands_data_callback($form, FormStateInterface $form_state) {
$selector = '#data_div';
$response = new AjaxResponse();
$response->addCommand(new DataCommand($selector, 'testkey', 'testvalue'));
return $response;
}
/**
* Ajax form callback: Selects 'invoke'.
*/
function ajax_forms_test_advanced_commands_invoke_callback($form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new InvokeCommand('#invoke_div', 'addClass', array('error')));
return $response;
}
/**
* Ajax form callback: Selects 'html'.
*/
function ajax_forms_test_advanced_commands_html_callback($form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new HtmlCommand('#html_div', 'replacement text'));
return $response;
}
/**
* Ajax form callback: Selects 'insert'.
*/
function ajax_forms_test_advanced_commands_insert_callback($form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new InsertCommand('#insert_div', 'insert replacement text'));
return $response;
}
/**
* Ajax form callback: Selects 'prepend'.
*/
function ajax_forms_test_advanced_commands_prepend_callback($form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new PrependCommand('#prepend_div', "prepended text"));
return $response;
}
/**
* Ajax form callback: Selects 'remove'.
*/
function ajax_forms_test_advanced_commands_remove_callback($form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new RemoveCommand('#remove_text'));
return $response;
}
/**
* Ajax form callback: Selects 'restripe'.
*/
function ajax_forms_test_advanced_commands_restripe_callback($form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new RestripeCommand('#restripe_table'));
return $response;
}
/**
* Ajax form callback: Selects 'settings'.
*/
function ajax_forms_test_advanced_commands_settings_callback($form, FormStateInterface $form_state) {
$setting['ajax_forms_test']['foo'] = 42;
$response = new AjaxResponse();
$response->addCommand(new SettingsCommand($setting));
return $response;
}
/**
* Ajax callback for 'add_css'.
*/
function ajax_forms_test_advanced_commands_add_css_callback($form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new AddCssCommand('my/file.css'));
return $response;
}
/**
* Ajax form callback: Selects the 'drivertext' element of the validation form.
*/
function ajax_forms_test_validation_form_callback($form, FormStateInterface $form_state) {
drupal_set_message("ajax_forms_test_validation_form_callback invoked");
drupal_set_message(t("Callback: drivertext=%drivertext, spare_required_field=%spare_required_field", array('%drivertext' => $form_state->getValue('drivertext'), '%spare_required_field' => $form_state->getValue('spare_required_field'))));
return ['#markup' => '<div id="message_area">ajax_forms_test_validation_form_callback at ' . date('c') . '</div>'];
}
/**
* Ajax form callback: Selects the 'drivernumber' element of the validation form.
*/
function ajax_forms_test_validation_number_form_callback($form, FormStateInterface $form_state) {
drupal_set_message("ajax_forms_test_validation_number_form_callback invoked");
drupal_set_message(t("Callback: drivernumber=%drivernumber, spare_required_field=%spare_required_field", array('%drivernumber' => $form_state->getValue('drivernumber'), '%spare_required_field' => $form_state->getValue('spare_required_field'))));
return ['#markup' => '<div id="message_area_number">ajax_forms_test_validation_number_form_callback at ' . date('c') . '</div>'];
}
/**
* AJAX form callback: Selects for the ajax_forms_test_lazy_load_form() form.
*/
function ajax_forms_test_lazy_load_form_ajax($form, FormStateInterface $form_state) {
$build = [
'#markup' => 'new content',
];
if ($form_state->getValue('add_files')) {
$build['#attached']['library'][] = 'system/admin';
$build['#attached']['library'][] = 'system/drupal.system';
$build['#attached']['drupalSettings']['ajax_forms_test_lazy_load_form_submit'] = 'executed';
}
return $build;
}

View file

@ -0,0 +1,32 @@
ajax_forms_test.get_form:
path: '/ajax_forms_test_get_form'
defaults:
_title: 'AJAX forms simple form test'
_form: '\Drupal\ajax_forms_test\Form\AjaxFormsTestSimpleForm'
requirements:
_access: 'TRUE'
ajax_forms_test.commands_form:
path: '/ajax_forms_test_ajax_commands_form'
defaults:
_title: 'AJAX forms AJAX commands test'
_form: '\Drupal\ajax_forms_test\Form\AjaxFormsTestCommandsForm'
requirements:
_access: 'TRUE'
ajax_forms_test.validation_test:
path: '/ajax_validation_test'
defaults:
_title: 'AJAX Validation Test'
_form: '\Drupal\ajax_forms_test\Form\AjaxFormsTestValidationForm'
requirements:
_access: 'TRUE'
ajax_forms_test.lazy_load_form:
path: '/ajax_forms_test_lazy_load_form'
defaults:
_title: 'AJAX forms lazy load test'
_form: '\Drupal\ajax_forms_test\Form\AjaxFormsTestLazyLoadForm'
requirements:
_access: 'TRUE'

View file

@ -0,0 +1,42 @@
<?php
namespace Drupal\ajax_forms_test;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\DataCommand;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Form\FormStateInterface;
/**
* Simple object for testing methods as Ajax callbacks.
*/
class Callbacks {
/**
* Ajax callback triggered by select.
*/
function selectCallback($form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new HtmlCommand('#ajax_selected_color', $form_state->getValue('select')));
$response->addCommand(new DataCommand('#ajax_selected_color', 'form_state_value_select', $form_state->getValue('select')));
return $response;
}
/**
* Ajax callback triggered by checkbox.
*/
function checkboxCallback($form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new HtmlCommand('#ajax_checkbox_value', (int) $form_state->getValue('checkbox')));
$response->addCommand(new DataCommand('#ajax_checkbox_value', 'form_state_value_select', (int) $form_state->getValue('checkbox')));
return $response;
}
/**
* Ajax callback triggered by the checkbox in a #group.
*/
function checkboxGroupCallback($form, FormStateInterface $form_state) {
return $form['checkbox_in_group_wrapper'];
}
}

View file

@ -0,0 +1,206 @@
<?php
namespace Drupal\ajax_forms_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Form constructor for the Ajax Command display form.
*/
class AjaxFormsTestCommandsForm extends FormBase {
/**
* {@inheritdoc}.
*/
public function getFormId() {
return 'ajax_forms_test_ajax_commands_form';
}
/**
* {@inheritdoc}.
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = array();
// Shows the 'after' command with a callback generating commands.
$form['after_command_example'] = array(
'#value' => $this->t("AJAX 'After': Click to put something after the div"),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_after_callback',
),
'#suffix' => '<div id="after_div">Something can be inserted after this</div>',
);
// Shows the 'alert' command.
$form['alert_command_example'] = array(
'#value' => $this->t("AJAX 'Alert': Click to alert"),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_alert_callback',
),
);
// Shows the 'append' command.
$form['append_command_example'] = array(
'#value' => $this->t("AJAX 'Append': Click to append something"),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_append_callback',
),
'#suffix' => '<div id="append_div">Append inside this div</div>',
);
// Shows the 'before' command.
$form['before_command_example'] = array(
'#value' => $this->t("AJAX 'before': Click to put something before the div"),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_before_callback',
),
'#suffix' => '<div id="before_div">Insert something before this.</div>',
);
// Shows the 'changed' command without asterisk.
$form['changed_command_example'] = array(
'#value' => $this->t("AJAX changed: Click to mark div changed."),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_changed_callback',
),
'#suffix' => '<div id="changed_div"> <div id="changed_div_mark_this">This div can be marked as changed or not.</div></div>',
);
// Shows the 'changed' command adding the asterisk.
$form['changed_command_asterisk_example'] = array(
'#value' => $this->t("AJAX changed: Click to mark div changed with asterisk."),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_changed_asterisk_callback',
),
);
// Shows the Ajax 'css' command.
$form['css_command_example'] = array(
'#value' => $this->t("Set the '#box' div to be blue."),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_css_callback',
),
'#suffix' => '<div id="css_div" style="height: 50px; width: 50px; border: 1px solid black"> box</div>',
);
// Shows the Ajax 'data' command. But there is no use of this information,
// as this would require a javascript client to use the data.
$form['data_command_example'] = array(
'#value' => $this->t("AJAX data command: Issue command."),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_data_callback',
),
'#suffix' => '<div id="data_div">Data attached to this div.</div>',
);
// Shows the Ajax 'invoke' command.
$form['invoke_command_example'] = array(
'#value' => $this->t("AJAX invoke command: Invoke addClass() method."),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_invoke_callback',
),
'#suffix' => '<div id="invoke_div">Original contents</div>',
);
// Shows the Ajax 'html' command.
$form['html_command_example'] = array(
'#value' => $this->t("AJAX html: Replace the HTML in a selector."),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_html_callback',
),
'#suffix' => '<div id="html_div">Original contents</div>',
);
// Shows the Ajax 'insert' command.
$form['insert_command_example'] = array(
'#value' => $this->t("AJAX insert: Let client insert based on #ajax['method']."),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_insert_callback',
'method' => 'prepend',
),
'#suffix' => '<div id="insert_div">Original contents</div>',
);
// Shows the Ajax 'prepend' command.
$form['prepend_command_example'] = array(
'#value' => $this->t("AJAX 'prepend': Click to prepend something"),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_prepend_callback',
),
'#suffix' => '<div id="prepend_div">Something will be prepended to this div. </div>',
);
// Shows the Ajax 'remove' command.
$form['remove_command_example'] = array(
'#value' => $this->t("AJAX 'remove': Click to remove text"),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_remove_callback',
),
'#suffix' => '<div id="remove_div"><div id="remove_text">text to be removed</div></div>',
);
// Shows the Ajax 'restripe' command.
$form['restripe_command_example'] = array(
'#type' => 'submit',
'#value' => $this->t("AJAX 'restripe' command"),
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_restripe_callback',
),
'#suffix' => '<div id="restripe_div">
<table id="restripe_table" style="border: 1px solid black" >
<tr id="table-first"><td>first row</td></tr>
<tr ><td>second row</td></tr>
</table>
</div>',
);
// Demonstrates the Ajax 'settings' command. The 'settings' command has
// nothing visual to "show", but it can be tested via SimpleTest and via
// Firebug.
$form['settings_command_example'] = array(
'#type' => 'submit',
'#value' => $this->t("AJAX 'settings' command"),
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_settings_callback',
),
);
// Shows the Ajax 'add_css' command.
$form['add_css_command_example'] = array(
'#type' => 'submit',
'#value' => $this->t("AJAX 'add_css' command"),
'#ajax' => array(
'callback' => 'ajax_forms_test_advanced_commands_add_css_callback',
),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Submit'),
);
return $form;
}
/**
* {@inheritdoc}.
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace Drupal\ajax_forms_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Form builder: Builds a form that triggers a simple AJAX callback.
*/
class AjaxFormsTestLazyLoadForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'ajax_forms_test_lazy_load_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// We attach a JavaScript setting, so that one of the generated AJAX
// commands will be a settings command. We can then check the settings
// command to ensure that the 'currentPath' setting is not part
// of the Ajax response.
$form['#attached']['drupalSettings']['test'] = 'currentPathUpdate';
$form['add_files'] = array(
'#title' => $this->t('Add files'),
'#type' => 'checkbox',
'#default_value' => FALSE,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Submit'),
'#ajax' => array(
'wrapper' => 'ajax-forms-test-lazy-load-ajax-wrapper',
'callback' => 'ajax_forms_test_lazy_load_form_ajax',
),
'#prefix' => '<div id="ajax-forms-test-lazy-load-ajax-wrapper"></div>',
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$form_state->setRebuild();
}
}

View file

@ -0,0 +1,121 @@
<?php
namespace Drupal\ajax_forms_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\ajax_forms_test\Callbacks;
use Drupal\Core\Form\FormStateInterface;
/**
* Form builder: Builds a form that triggers a simple AJAX callback.
*/
class AjaxFormsTestSimpleForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'ajax_forms_test_simple_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$object = new Callbacks();
$form = array();
$form['select'] = array(
'#title' => $this->t('Color'),
'#type' => 'select',
'#options' => array(
'red' => 'red',
'green' => 'green',
'blue' => 'blue'),
'#ajax' => array(
'callback' => array($object, 'selectCallback'),
),
'#suffix' => '<div id="ajax_selected_color">No color yet selected</div>',
);
$form['checkbox'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Test checkbox'),
'#ajax' => array(
'callback' => array($object, 'checkboxCallback'),
),
'#suffix' => '<div id="ajax_checkbox_value">No action yet</div>',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('submit'),
);
// This is for testing invalid callbacks that should return a 500 error in
// \Drupal\Core\Form\FormAjaxResponseBuilderInterface::buildResponse().
$invalid_callbacks = array(
'null' => NULL,
'empty' => '',
'nonexistent' => 'some_function_that_does_not_exist',
);
foreach ($invalid_callbacks as $key => $value) {
$form['select_' . $key . '_callback'] = array(
'#type' => 'select',
'#title' => $this->t('Test %key callbacks', array('%key' => $key)),
'#options' => array('red' => 'red'),
'#ajax' => array('callback' => $value),
);
}
$form['test_group'] = [
'#type' => 'details',
'#title' => $this->t('Test group'),
'#open' => TRUE,
];
// Test ajax element in a #group.
$form['checkbox_in_group_wrapper'] = [
'#type' => 'container',
'#attributes' => ['id' => 'checkbox-wrapper'],
'#group' => 'test_group',
'checkbox_in_group' => [
'#type' => 'checkbox',
'#title' => $this->t('AJAX checkbox in a group'),
'#ajax' => [
'callback' => [$object, 'checkboxGroupCallback'],
'wrapper' => 'checkbox-wrapper',
],
],
'nested_group' => [
'#type' => 'details',
'#title' => $this->t('Nested group'),
'#open' => TRUE,
],
'checkbox_in_nested' => [
'#type' => 'checkbox',
'#group' => 'nested_group',
'#title' => $this->t('AJAX checkbox in a nested group'),
'#ajax' => [
'callback' => [$object, 'checkboxGroupCallback'],
'wrapper' => 'checkbox-wrapper',
],
],
];
$form['another_checkbox_in_nested'] = [
'#type' => 'checkbox',
'#group' => 'nested_group',
'#title' => $this->t('Another AJAX checkbox in a nested group'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace Drupal\ajax_forms_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Form builder: Builds a form that triggers a simple AJAX callback.
*/
class AjaxFormsTestValidationForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'ajax_forms_test_validation_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['drivertext'] = array(
'#title' => $this->t('AJAX-enabled textfield.'),
'#description' => $this->t("When this one AJAX-triggers and the spare required field is empty, you should not get an error."),
'#type' => 'textfield',
'#default_value' => $form_state->getValue('drivertext', ''),
'#ajax' => array(
'callback' => 'ajax_forms_test_validation_form_callback',
'wrapper' => 'message_area',
'method' => 'replace',
),
'#suffix' => '<div id="message_area"></div>',
);
$form['drivernumber'] = array(
'#title' => $this->t('AJAX-enabled number field.'),
'#description' => $this->t("When this one AJAX-triggers and the spare required field is empty, you should not get an error."),
'#type' => 'number',
'#default_value' => $form_state->getValue('drivernumber', ''),
'#ajax' => array(
'callback' => 'ajax_forms_test_validation_number_form_callback',
'wrapper' => 'message_area_number',
'method' => 'replace',
),
'#suffix' => '<div id="message_area_number"></div>',
);
$form['spare_required_field'] = array(
'#title' => $this->t("Spare Required Field"),
'#type' => 'textfield',
'#required' => TRUE,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Submit'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
drupal_set_message($this->t("Validation form submitted"));
}
}

View file

@ -0,0 +1,135 @@
<?php
namespace Drupal\ajax_forms_test\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides an AJAX form block.
*
* @Block(
* id = "ajax_forms_test_block",
* admin_label = @Translation("AJAX test form"),
* category = @Translation("Forms")
* )
*/
class AjaxFormBlock extends BlockBase implements FormInterface, ContainerFactoryPluginInterface {
/**
* The form builder.
*
* @var \Drupal\Core\Form\FormBuilderInterface
*/
protected $formBuilder;
/**
* Constructs a new AjaxFormBlock.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
* The form builder.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, FormBuilderInterface $form_builder) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->formBuilder = $form_builder;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('form_builder')
);
}
/**
* {@inheritdoc}
*/
public function build() {
return $this->formBuilder->getForm($this);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'ajax_forms_test_block';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['test1'] = [
'#type' => 'select',
'#title' => $this->t('Test 1'),
'#required' => TRUE,
'#options' => [
'option1' => $this->t('Option 1'),
'option2' => $this->t('Option 2'),
],
'#ajax' => [
'callback' => '::updateOptions',
'wrapper' => 'edit-test1-wrapper',
],
'#prefix' => '<div id="edit-test1-wrapper">',
'#suffix' => '</div>',
];
$form['actions'] = [
'#type' => 'actions',
];
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Submit'),
];
return $form;
}
/**
* Updates the options of a select list.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return array
* The updated form element.
*/
public function updateOptions(array $form, FormStateInterface $form_state) {
$form['test1']['#options']['option1'] = $this->t('Option 1!!!');
$form['test1']['#options'] += [
'option3' => $this->t('Option 3'),
'option4' => $this->t('Option 4'),
];
return $form['test1'];
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
drupal_set_message('Submission successful.');
}
}

View file

@ -0,0 +1,8 @@
name: 'AJAX Test'
type: module
description: 'Support module for AJAX framework tests.'
package: Testing
version: VERSION
core: 8.x
dependencies:
- contact

View file

@ -0,0 +1,23 @@
order:
drupalSettings:
ajax: test
dependencies:
- ajax_test/order-css-command
- ajax_test/order-footer-js-command
- ajax_test/order-header-js-command
order-css-command:
css:
theme:
# Two CSS files (order should remain the same).
a.css: {}
b.css: {}
order-footer-js-command:
js:
footer.js: {}
order-header-js-command:
header: true
js:
header.js: {}

View file

@ -0,0 +1,57 @@
ajax_test.dialog_contents:
path: '/ajax-test/dialog-contents'
defaults:
_title: 'AJAX Dialog contents routing'
_controller: '\Drupal\ajax_test\Controller\AjaxTestController::dialogContents'
requirements:
_access: 'TRUE'
ajax_test.dialog_form:
path: '/ajax-test/dialog-form'
defaults:
_title: 'Ajax Form contents'
_form: '\Drupal\ajax_test\Form\AjaxTestForm'
requirements:
_access: 'TRUE'
ajax_test.dialog:
path: '/ajax-test/dialog'
defaults:
_controller: '\Drupal\ajax_test\Controller\AjaxTestController::dialog'
requirements:
_access: 'TRUE'
ajax_test.dialog_close:
path: '/ajax-test/dialog-close'
defaults:
_controller: '\Drupal\ajax_test\Controller\AjaxTestController::dialogClose'
requirements:
_access: 'TRUE'
ajax_test.render:
path: '/ajax-test/render'
defaults:
_controller: '\Drupal\ajax_test\Controller\AjaxTestController::render'
requirements:
_access: 'TRUE'
ajax_test.admin.theme:
path: '/admin/ajax-test/theme'
defaults:
_controller: '\Drupal\ajax_test\Controller\AjaxTestController::theme'
requirements:
_access: 'TRUE'
ajax_test.order:
path: '/ajax-test/order'
defaults:
_controller: '\Drupal\ajax_test\Controller\AjaxTestController::order'
requirements:
_access: 'TRUE'
ajax_test.render_error:
path: '/ajax-test/render-error'
defaults:
_controller: '\Drupal\ajax_test\Controller\AjaxTestController::renderError'
requirements:
_access: 'TRUE'

View file

@ -0,0 +1,225 @@
<?php
namespace Drupal\ajax_test\Controller;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\AlertCommand;
use Drupal\Core\Ajax\CloseDialogCommand;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides content for dialog tests.
*/
class AjaxTestController {
/**
* Example content for dialog testing.
*
* @return array
* Renderable array of AJAX dialog contents.
*/
public static function dialogContents() {
// This is a regular render array; the keys do not have special meaning.
$content = array(
'#title' => '<em>AJAX Dialog & contents</em>',
'content' => array(
'#markup' => 'Example message',
),
'cancel' => array(
'#type' => 'link',
'#title' => 'Cancel',
'#url' => Url::fromRoute('<front>'),
'#attributes' => array(
// This is a special class to which JavaScript assigns dialog closing
// behavior.
'class' => array('dialog-cancel'),
),
),
);
return $content;
}
/**
* Returns a render array that will be rendered by AjaxRenderer.
*
* Verifies that the response incorporates JavaScript settings generated
* during the page request by adding a dummy setting.
*/
public function render() {
return [
'#attached' => [
'library' => [
'core/drupalSettings',
],
'drupalSettings' => [
'ajax' => 'test',
],
],
];
}
/**
* Returns the used theme.
*/
public function theme() {
return [
'#markup' => 'Current theme: ' . \Drupal::theme()->getActiveTheme()->getName(),
];
}
/**
* Returns an AjaxResponse; settings command set last.
*
* Helps verifying AjaxResponse reorders commands to ensure correct execution.
*/
public function order() {
$response = new AjaxResponse();
// HTML insertion command.
$response->addCommand(new HtmlCommand('body', 'Hello, world!'));
$build['#attached']['library'][] = 'ajax_test/order';
$response->setAttachments($build['#attached']);
return $response;
}
/**
* Returns an AjaxResponse with alert command.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request object.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* The JSON response object.
*/
public function renderError(Request $request) {
$message = '';
$query = $request->query;
if ($query->has('message')) {
$message = $query->get('message');
}
$response = new AjaxResponse();
$response->addCommand(new AlertCommand($message));
return $response;
}
/**
* Returns a render array of form elements and links for dialog.
*/
public function dialog() {
// Add two wrapper elements for testing non-modal dialogs. Modal dialogs use
// the global drupal-modal wrapper by default.
$build['dialog_wrappers'] = array('#markup' => '<div id="ajax-test-dialog-wrapper-1"></div><div id="ajax-test-dialog-wrapper-2"></div>');
// Dialog behavior applied to a button.
$build['form'] = \Drupal::formBuilder()->getForm('Drupal\ajax_test\Form\AjaxTestDialogForm');
// Dialog behavior applied to a #type => 'link'.
$build['link'] = array(
'#type' => 'link',
'#title' => 'Link 1 (modal)',
'#url' => Url::fromRoute('ajax_test.dialog_contents'),
'#attributes' => array(
'class' => array('use-ajax'),
'data-dialog-type' => 'modal',
),
);
// Dialog behavior applied to links rendered by links.html.twig.
$build['links'] = array(
'#theme' => 'links',
'#links' => array(
'link2' => array(
'title' => 'Link 2 (modal)',
'url' => Url::fromRoute('ajax_test.dialog_contents'),
'attributes' => array(
'class' => array('use-ajax'),
'data-dialog-type' => 'modal',
'data-dialog-options' => json_encode(array(
'width' => 400,
))
),
),
'link3' => array(
'title' => 'Link 3 (non-modal)',
'url' => Url::fromRoute('ajax_test.dialog_contents'),
'attributes' => array(
'class' => array('use-ajax'),
'data-dialog-type' => 'dialog',
'data-dialog-options' => json_encode(array(
'target' => 'ajax-test-dialog-wrapper-1',
'width' => 800,
))
),
),
'link4' => array(
'title' => 'Link 4 (close non-modal if open)',
'url' => Url::fromRoute('ajax_test.dialog_close'),
'attributes' => array(
'class' => array('use-ajax'),
'data-dialog-type' => 'modal',
),
),
'link5' => array(
'title' => 'Link 5 (form)',
'url' => Url::fromRoute('ajax_test.dialog_form'),
'attributes' => array(
'class' => array('use-ajax'),
'data-dialog-type' => 'modal',
),
),
'link6' => array(
'title' => 'Link 6 (entity form)',
'url' => Url::fromRoute('contact.form_add'),
'attributes' => array(
'class' => array('use-ajax'),
'data-dialog-type' => 'modal',
'data-dialog-options' => json_encode(array(
'width' => 800,
'height' => 500,
))
),
),
'link7' => array(
'title' => 'Link 7 (non-modal, no target)',
'url' => Url::fromRoute('ajax_test.dialog_contents'),
'attributes' => array(
'class' => array('use-ajax'),
'data-dialog-type' => 'dialog',
'data-dialog-options' => json_encode(array(
'width' => 800,
))
),
),
'link8' => [
'title' => 'Link 8 (ajax)',
'url' => Url::fromRoute('ajax_test.admin.theme'),
'attributes' => [
'class' => ['use-ajax'],
'data-dialog-type' => 'modal',
'data-dialog-options' => json_encode([
'width' => 400,
]),
],
],
),
);
return $build;
}
/**
* Returns an AjaxResponse with command to close dialog.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* The JSON response object.
*/
public function dialogClose() {
$response = new AjaxResponse();
$response->addCommand(new CloseDialogCommand('#ajax-test-dialog-wrapper-1'));
return $response;
}
}

View file

@ -0,0 +1,112 @@
<?php
namespace Drupal\ajax_test\Form;
use Drupal\ajax_test\Controller\AjaxTestController;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Ajax\OpenDialogCommand;
use Drupal\Core\Form\FormStateInterface;
/**
* Dummy form for testing DialogRenderer with _form routes.
*/
class AjaxTestDialogForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'ajax_test_dialog_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// In order to use WebTestBase::drupalPostAjaxForm() to POST from a link, we need
// to have a dummy field we can set in WebTestBase::drupalPostForm() else it won't
// submit anything.
$form['textfield'] = array(
'#type' => 'hidden'
);
$form['button1'] = array(
'#type' => 'submit',
'#name' => 'button1',
'#value' => 'Button 1 (modal)',
'#ajax' => array(
'callback' => '::modal',
),
);
$form['button2'] = array(
'#type' => 'submit',
'#name' => 'button2',
'#value' => 'Button 2 (non-modal)',
'#ajax' => array(
'callback' => '::nonModal',
),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$form_state->setRedirect('ajax_test.dialog_contents');
}
/**
* AJAX callback handler for AjaxTestDialogForm.
*/
public function modal(&$form, FormStateInterface $form_state) {
return $this->dialog(TRUE);
}
/**
* AJAX callback handler for AjaxTestDialogForm.
*/
public function nonModal(&$form, FormStateInterface $form_state) {
return $this->dialog(FALSE);
}
/**
* Util to render dialog in ajax callback.
*
* @param bool $is_modal
* (optional) TRUE if modal, FALSE if plain dialog. Defaults to FALSE.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* An ajax response object.
*/
protected function dialog($is_modal = FALSE) {
$content = AjaxTestController::dialogContents();
$response = new AjaxResponse();
$title = $this->t('AJAX Dialog & contents');
// Attach the library necessary for using the Open(Modal)DialogCommand and
// set the attachments for this Ajax response.
$content['#attached']['library'][] = 'core/drupal.dialog.ajax';
if ($is_modal) {
$response->addCommand(new OpenModalDialogCommand($title, $content));
}
else {
$selector = '#ajax-test-dialog-wrapper-1';
$response->addCommand(new OpenDialogCommand($selector, $title, $content));
}
return $response;
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace Drupal\ajax_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Dummy form for testing DialogRenderer with _form routes.
*/
class AjaxTestForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'ajax_test_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['#action'] = \Drupal::url('ajax_test.dialog');
$form['description'] = array(
'#markup' => '<p>' . $this->t("Ajax Form contents description.") . '</p>',
);
$form['actions'] = array(
'#type' => 'actions',
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Do it'),
);
$form['actions']['preview'] = array(
'#type' => 'submit',
'#value' => $this->t('Preview'),
// No regular submit-handler. This form only works via JavaScript.
'#submit' => array(),
'#ajax' => array(
// This means the ::preview() method on this class would be invoked in
// case of a click event. However, since Drupal core's test runner only
// is able to execute PHP, not JS, there is no point in actually
// implementing this method, because we can never let it be called from
// JS; we'd have to manually call it from PHP, at which point we would
// not actually be testing it.
// Therefore we consciously choose to not implement this method, because
// we cannot meaningfully test it anyway.
'callback' => '::preview',
'event' => 'click',
),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {}
}

View file

@ -0,0 +1,184 @@
<?php
/**
* @file
* Batch callbacks for the Batch API tests.
*/
use Drupal\Component\Utility\Html;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Implements callback_batch_operation().
*
* Performs a simple batch operation.
*/
function _batch_test_callback_1($id, $sleep, &$context) {
// No-op, but ensure the batch take a couple iterations.
// Batch needs time to run for the test, so sleep a bit.
usleep($sleep);
// Track execution, and store some result for post-processing in the
// 'finished' callback.
batch_test_stack("op 1 id $id");
$context['results'][1][] = $id;
}
/**
* Implements callback_batch_operation().
*
* Performs a multistep batch operation.
*/
function _batch_test_callback_2($start, $total, $sleep, &$context) {
// Initialize context with progress information.
if (!isset($context['sandbox']['current'])) {
$context['sandbox']['current'] = $start;
$context['sandbox']['count'] = 0;
}
// Process by groups of 5 (arbitrary value).
$limit = 5;
for ($i = 0; $i < $limit && $context['sandbox']['count'] < $total; $i++) {
// No-op, but ensure the batch take a couple iterations.
// Batch needs time to run for the test, so sleep a bit.
usleep($sleep);
// Track execution, and store some result for post-processing in the
// 'finished' callback.
$id = $context['sandbox']['current'] + $i;
batch_test_stack("op 2 id $id");
$context['results'][2][] = $id;
// Update progress information.
$context['sandbox']['count']++;
}
$context['sandbox']['current'] += $i;
// Inform batch engine about progress.
if ($context['sandbox']['count'] != $total) {
$context['finished'] = $context['sandbox']['count'] / $total;
}
}
/**
* Implements callback_batch_operation().
*/
function _batch_test_callback_5($id, $sleep, &$context) {
// No-op, but ensure the batch take a couple iterations.
// Batch needs time to run for the test, so sleep a bit.
usleep($sleep);
// Track execution, and store some result for post-processing in the
// 'finished' callback.
batch_test_stack("op 5 id $id");
$context['results'][5][] = $id;
// This test is to test finished > 1
$context['finished'] = 3.14;
}
/**
* Implements callback_batch_operation().
*
* Performs a batch operation setting up its own batch.
*/
function _batch_test_nested_batch_callback() {
batch_test_stack('setting up batch 2');
batch_set(_batch_test_batch_2());
}
/**
* Provides a common 'finished' callback for batches 1 to 4.
*/
function _batch_test_finished_helper($batch_id, $success, $results, $operations) {
if ($results) {
foreach ($results as $op => $op_results) {
$messages[] = 'op ' . Html::escape($op) . ': processed ' . count($op_results) . ' elements';
}
}
else {
$messages[] = 'none';
}
if (!$success) {
// A fatal error occurred during the processing.
$error_operation = reset($operations);
$messages[] = t('An error occurred while processing @op with arguments:<br />@args', array('@op' => $error_operation[0], '@args' => print_r($error_operation[1], TRUE)));
}
// Use item list template to render the messages.
$error_message = [
'#type' => 'inline_template',
'#template' => 'results for batch {{ batch_id }}{{ errors }}',
'#context' => [
'batch_id' => $batch_id,
'errors' => [
'#theme' => 'item_list',
'#items' => $messages,
],
],
];
drupal_set_message(\Drupal::service('renderer')->renderPlain($error_message));
}
/**
* Implements callback_batch_finished().
*
* Triggers 'finished' callback for batch 0.
*/
function _batch_test_finished_0($success, $results, $operations) {
_batch_test_finished_helper(0, $success, $results, $operations);
}
/**
* Implements callback_batch_finished().
*
* Triggers 'finished' callback for batch 1.
*/
function _batch_test_finished_1($success, $results, $operations) {
_batch_test_finished_helper(1, $success, $results, $operations);
}
/**
* Implements callback_batch_finished().
*
* Triggers 'finished' callback for batch 1.
*/
function _batch_test_finished_1_finished($success, $results, $operations) {
_batch_test_finished_helper(1, $success, $results, $operations);
return new RedirectResponse(Url::fromRoute('test_page_test.test_page', [], ['absolute' => TRUE])->toString());
}
/**
* Implements callback_batch_finished().
*
* Triggers 'finished' callback for batch 2.
*/
function _batch_test_finished_2($success, $results, $operations) {
_batch_test_finished_helper(2, $success, $results, $operations);
}
/**
* Implements callback_batch_finished().
*
* Triggers 'finished' callback for batch 3.
*/
function _batch_test_finished_3($success, $results, $operations) {
_batch_test_finished_helper(3, $success, $results, $operations);
}
/**
* Implements callback_batch_finished().
*
* Triggers 'finished' callback for batch 4.
*/
function _batch_test_finished_4($success, $results, $operations) {
_batch_test_finished_helper(4, $success, $results, $operations);
}
/**
* Implements callback_batch_finished().
*
* Triggers 'finished' callback for batch 5.
*/
function _batch_test_finished_5($success, $results, $operations) {
_batch_test_finished_helper(5, $success, $results, $operations);
}

View file

@ -0,0 +1,6 @@
name: 'Batch API test'
type: module
description: 'Support module for Batch API tests.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,46 @@
batch_test.test_form:
title: Simple
route_name: batch_test.test_form
base_route: batch_test.test_form
batch_test.multistep:
title: Multistep
route_name: batch_test.multistep
base_route: batch_test.test_form
weight: 1
batch_test.chained:
title: Chained
route_name: batch_test.chained
base_route: batch_test.test_form
weight: 2
batch_test.programmatic:
title: Chained
route_name: batch_test.programmatic
base_route: batch_test.test_form
weight: 3
batch_test.no_form:
title: 'No form'
route_name: batch_test.no_form
base_route: batch_test.test_form
weight: 4
batch_test.large_percentage:
title: 'Large percentage'
route_name: batch_test.large_percentage
base_route: batch_test.test_form
weight: 5
batch_test.nested_programmatic:
title: 'Nested programmatic'
route_name: batch_test.nested_programmatic
base_route: batch_test.test_form
weight: 6
batch_test.redirect:
title: 'Redirect'
route_name: batch_test.redirect
base_route: batch_test.test_form
weight: 7

View file

@ -0,0 +1,199 @@
<?php
/**
* @file
* Helper module for the Batch API tests.
*/
use Drupal\Core\Form\FormState;
/**
* Batch operation: Submits form_test_mock_form().
*/
function _batch_test_nested_drupal_form_submit_callback($value) {
$form_state = (new FormState())
->setValue('test_value', $value);
\Drupal::formBuilder()->submitForm('Drupal\batch_test\Form\BatchTestMockForm', $form_state);
}
/**
* Batch 0: Does nothing.
*/
function _batch_test_batch_0() {
$batch = array(
'operations' => array(),
'finished' => '_batch_test_finished_0',
'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
);
return $batch;
}
/**
* Batch 1: Repeats a simple operation.
*
* Operations: op 1 from 1 to 10.
*/
function _batch_test_batch_1() {
// Ensure the batch takes at least two iterations.
$total = 10;
$sleep = (1000000 / $total) * 2;
$operations = array();
for ($i = 1; $i <= $total; $i++) {
$operations[] = array('_batch_test_callback_1', array($i, $sleep));
}
$batch = array(
'operations' => $operations,
'finished' => '_batch_test_finished_1',
'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
);
return $batch;
}
/**
* Batch 2: Performs a single multistep operation.
*
* Operations: op 2 from 1 to 10.
*/
function _batch_test_batch_2() {
// Ensure the batch takes at least two iterations.
$total = 10;
$sleep = (1000000 / $total) * 2;
$operations = array(
array('_batch_test_callback_2', array(1, $total, $sleep)),
);
$batch = array(
'operations' => $operations,
'finished' => '_batch_test_finished_2',
'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
);
return $batch;
}
/**
* Batch 3: Performs both single and multistep operations.
*
* Operations:
* - op 1 from 1 to 5,
* - op 2 from 1 to 5,
* - op 1 from 6 to 10,
* - op 2 from 6 to 10.
*/
function _batch_test_batch_3() {
// Ensure the batch takes at least two iterations.
$total = 10;
$sleep = (1000000 / $total) * 2;
$operations = array();
for ($i = 1; $i <= round($total / 2); $i++) {
$operations[] = array('_batch_test_callback_1', array($i, $sleep));
}
$operations[] = array('_batch_test_callback_2', array(1, $total / 2, $sleep));
for ($i = round($total / 2) + 1; $i <= $total; $i++) {
$operations[] = array('_batch_test_callback_1', array($i, $sleep));
}
$operations[] = array('_batch_test_callback_2', array(6, $total / 2, $sleep));
$batch = array(
'operations' => $operations,
'finished' => '_batch_test_finished_3',
'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
);
return $batch;
}
/**
* Batch 4: Performs a batch within a batch.
*
* Operations:
* - op 1 from 1 to 5,
* - set batch 2 (op 2 from 1 to 10, should run at the end)
* - op 1 from 6 to 10,
*/
function _batch_test_batch_4() {
// Ensure the batch takes at least two iterations.
$total = 10;
$sleep = (1000000 / $total) * 2;
$operations = array();
for ($i = 1; $i <= round($total / 2); $i++) {
$operations[] = array('_batch_test_callback_1', array($i, $sleep));
}
$operations[] = array('_batch_test_nested_batch_callback', array());
for ($i = round($total / 2) + 1; $i <= $total; $i++) {
$operations[] = array('_batch_test_callback_1', array($i, $sleep));
}
$batch = array(
'operations' => $operations,
'finished' => '_batch_test_finished_4',
'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
);
return $batch;
}
/**
* Batch 5: Repeats a simple operation.
*
* Operations: op 1 from 1 to 10.
*/
function _batch_test_batch_5() {
// Ensure the batch takes at least two iterations.
$total = 10;
$sleep = (1000000 / $total) * 2;
$operations = array();
for ($i = 1; $i <= $total; $i++) {
$operations[] = array('_batch_test_callback_5', array($i, $sleep));
}
$batch = array(
'operations' => $operations,
'finished' => '_batch_test_finished_5',
'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
);
return $batch;
}
/**
* Implements callback_batch_operation().
*
* Tests the progress page theme.
*/
function _batch_test_theme_callback() {
// Because drupalGet() steps through the full progressive batch before
// returning control to the test function, we cannot test that the correct
// theme is being used on the batch processing page by viewing that page
// directly. Instead, we save the theme being used in a variable here, so
// that it can be loaded and inspected in the thread running the test.
$theme = \Drupal::theme()->getActiveTheme()->getName();
batch_test_stack($theme);
}
/**
* Tests the title on the progress page by performing a batch callback.
*/
function _batch_test_title_callback() {
// Because drupalGet() steps through the full progressive batch before
// returning control to the test function, we cannot test that the correct
// title is being used on the batch processing page by viewing that page
// directly. Instead, we save the title being used in a variable here, so
// that it can be loaded and inspected in the thread running the test.
$request = \Drupal::request();
$route_match = \Drupal::routeMatch();
$title = \Drupal::service('title_resolver')->getTitle($request, $route_match->getRouteObject());
batch_test_stack($title);
}
/**
* Helper function: Stores or retrieves traced execution data.
*/
function batch_test_stack($data = NULL, $reset = FALSE) {
if ($reset) {
\Drupal::state()->delete('batch_test.stack');
}
if (!isset($data)) {
return \Drupal::state()->get('batch_test.stack');
}
$stack = \Drupal::state()->get('batch_test.stack');
$stack[] = $data;
\Drupal::state()->set('batch_test.stack', $stack);
}

View file

@ -0,0 +1,87 @@
batch_test.redirect:
path: '/batch-test/redirect'
defaults:
_controller: '\Drupal\batch_test\Controller\BatchTestController::testRedirect'
_title: 'Redirect'
requirements:
_access: 'TRUE'
batch_test.large_percentage:
path: '/batch-test/large-percentage'
defaults:
_controller: '\Drupal\batch_test\Controller\BatchTestController::testLargePercentage'
_title: 'Simple page with batch over 100% complete'
requirements:
_access: 'TRUE'
batch_test.nested_programmatic:
path: '/batch-test/nested-programmatic/{value}'
defaults:
_controller: '\Drupal\batch_test\Controller\BatchTestController::testNestedDrupalFormSubmit'
_title: 'Nested programmatic'
value: '1'
requirements:
_access: 'TRUE'
batch_test.no_form:
path: '/batch-test/no-form'
defaults:
_controller: '\Drupal\batch_test\Controller\BatchTestController::testNoForm'
_title: 'Simple page'
requirements:
_access: 'TRUE'
batch_test.finish_redirect:
path: '/batch-test/finish-redirect'
defaults:
_controller: '\Drupal\batch_test\Controller\BatchTestController::testFinishRedirect'
_title: 'Simple page with finish redirect call'
requirements:
_access: 'TRUE'
batch_test.test_form:
path: '/batch-test'
defaults:
_form: '\Drupal\batch_test\Form\BatchTestSimpleForm'
_title: 'Batch test'
requirements:
_access: 'TRUE'
batch_test.multistep:
path: '/batch-test/multistep'
defaults:
_form: '\Drupal\batch_test\Form\BatchTestMultiStepForm'
_title: 'Multistep'
requirements:
_access: 'TRUE'
batch_test.chained:
path: '/batch-test/chained'
defaults:
_form: '\Drupal\batch_test\Form\BatchTestChainedForm'
_title: 'Chained'
requirements:
_access: 'TRUE'
batch_test.programmatic:
path: '/batch-test/programmatic/{value}'
defaults:
_controller: '\Drupal\batch_test\Controller\BatchTestController::testProgrammatic'
_title: 'Programmatic'
value: '1'
requirements:
_access: 'TRUE'
batch_test.test_theme:
path: '/admin/batch-test/test-theme'
defaults:
_controller: '\Drupal\batch_test\Controller\BatchTestController::testThemeBatch'
requirements:
_access: 'TRUE'
batch_test.test_title:
path: '/batch-test/test-title'
defaults:
_controller: '\Drupal\batch_test\Controller\BatchTestController::testTitleBatch'
requirements:
_access: 'TRUE'

View file

@ -0,0 +1,145 @@
<?php
namespace Drupal\batch_test\Controller;
use Drupal\Core\Form\FormState;
/**
* Controller routines for batch tests.
*/
class BatchTestController {
/**
* Redirects successfully.
*
* @return array
* Render array containing success message.
*/
public function testRedirect() {
return array(
'success' => array(
'#markup' => 'Redirection successful.',
)
);
}
/**
* Fires a batch process without a form submission.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse|null
* A redirect response if the batch is progressive. No return value otherwise.
*/
public function testLargePercentage() {
batch_test_stack(NULL, TRUE);
batch_set(_batch_test_batch_5());
return batch_process('batch-test/redirect');
}
/**
* Submits a form within a batch programmatically.
*
* @param int $value
* Some value passed to a custom batch callback.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse|null
* A redirect response if the batch is progressive. No return value otherwise.
*/
public function testNestedDrupalFormSubmit($value = 1) {
// Set the batch and process it.
$batch['operations'] = array(
array('_batch_test_nested_drupal_form_submit_callback', array($value)),
);
batch_set($batch);
return batch_process('batch-test/redirect');
}
/**
* Fires a batch process without a form submission.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse|null
* A redirect response if the batch is progressive. No return value otherwise.
*/
public function testNoForm() {
batch_test_stack(NULL, TRUE);
batch_set(_batch_test_batch_1());
return batch_process('batch-test/redirect');
}
/**
* Fires a batch process without a form submission and a finish redirect.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse|null
* A redirect response if the batch is progressive. No return value otherwise.
*/
public function testFinishRedirect() {
batch_test_stack(NULL, TRUE);
$batch = _batch_test_batch_1();
$batch['finished'] = '_batch_test_finished_1_finished';
batch_set($batch);
return batch_process('batch-test/redirect');
}
/**
* Submits the 'Chained' form programmatically.
*
* Programmatic form: the page submits the 'Chained' form through
* \Drupal::formBuilder()->submitForm().
*
* @param int $value
* Some value passed to a the chained form.
*
* @return array
* Render array containing markup.
*/
function testProgrammatic($value = 1) {
$form_state = (new FormState())->setValues([
'value' => $value,
]);
\Drupal::formBuilder()->submitForm('Drupal\batch_test\Form\BatchTestChainedForm', $form_state);
return array(
'success' => array(
'#markup' => 'Got out of a programmatic batched form.',
)
);
}
/**
* Runs a batch for testing theme used on the progress page.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse|null
* A redirect response if the batch is progressive. No return value otherwise.
*/
public function testThemeBatch() {
batch_test_stack(NULL, TRUE);
$batch = array(
'operations' => array(
array('_batch_test_theme_callback', array()),
),
);
batch_set($batch);
return batch_process('batch-test/redirect');
}
/**
* Runs a batch for testing the title shown on the progress page.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse|null
* A redirect response if the batch is progressive. No return value otherwise.
*/
public function testTitleBatch() {
batch_test_stack(NULL, TRUE);
$batch = [
'title' => 'Batch Test',
'operations' => [
['_batch_test_title_callback', []],
],
];
batch_set($batch);
return batch_process('batch-test/redirect');
}
}

View file

@ -0,0 +1,107 @@
<?php
namespace Drupal\batch_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Generate form of id batch_test_chained_form.
*/
class BatchTestChainedForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'batch_test_chained_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// This value is used to test that $form_state persists through batched
// submit handlers.
$form['value'] = array(
'#type' => 'textfield',
'#title' => 'Value',
'#default_value' => 1,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
$form['#submit'] = array(
'Drupal\batch_test\Form\BatchTestChainedForm::batchTestChainedFormSubmit1',
'Drupal\batch_test\Form\BatchTestChainedForm::batchTestChainedFormSubmit2',
'Drupal\batch_test\Form\BatchTestChainedForm::batchTestChainedFormSubmit3',
'Drupal\batch_test\Form\BatchTestChainedForm::batchTestChainedFormSubmit4',
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
}
/**
* Form submission handler #1 for batch_test_chained_form
*/
public static function batchTestChainedFormSubmit1($form, FormStateInterface $form_state) {
batch_test_stack(NULL, TRUE);
batch_test_stack('submit handler 1');
batch_test_stack('value = ' . $form_state->getValue('value'));
$value = &$form_state->getValue('value');
$value++;
batch_set(_batch_test_batch_1());
$form_state->setRedirect('batch_test.redirect');
}
/**
* Form submission handler #2 for batch_test_chained_form
*/
public static function batchTestChainedFormSubmit2($form, FormStateInterface $form_state) {
batch_test_stack('submit handler 2');
batch_test_stack('value = ' . $form_state->getValue('value'));
$value = &$form_state->getValue('value');
$value++;
batch_set(_batch_test_batch_2());
$form_state->setRedirect('batch_test.redirect');
}
/**
* Form submission handler #3 for batch_test_chained_form
*/
public static function batchTestChainedFormSubmit3($form, FormStateInterface $form_state) {
batch_test_stack('submit handler 3');
batch_test_stack('value = ' . $form_state->getValue('value'));
$value = &$form_state->getValue('value');
$value++;
$form_state->setRedirect('batch_test.redirect');
}
/**
* Form submission handler #4 for batch_test_chained_form
*/
public static function batchTestChainedFormSubmit4($form, FormStateInterface $form_state) {
batch_test_stack('submit handler 4');
batch_test_stack('value = ' . $form_state->getValue('value'));
$value = &$form_state->getValue('value');
$value++;
batch_set(_batch_test_batch_3());
$form_state->setRedirect('batch_test.redirect');
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Drupal\batch_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Generate form of id batch_test_mock_form.
*/
class BatchTestMockForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'batch_test_mock_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['test_value'] = array(
'#title' => t('Test value'),
'#type' => 'textfield',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
batch_test_stack('mock form submitted with value = ' . $form_state->getValue('test_value'));
}
}

View file

@ -0,0 +1,70 @@
<?php
namespace Drupal\batch_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Generate form of id batch_test_multistep_form.
*/
class BatchTestMultiStepForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'batch_test_multistep_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$step = $form_state->get('step');
if (empty($step)) {
$step = 1;
$form_state->set('step', $step);
}
$form['step_display'] = array(
'#markup' => 'step ' . $step . '<br/>',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
// This is a POST form with multiple steps that does not transition from one
// step to the next via POST requests, but via GET requests, because it uses
// Batch API to advance through the steps.
$form['#cache']['max-age'] = 0;
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
batch_test_stack(NULL, TRUE);
$step = $form_state->get('step');
switch ($step) {
case 1:
batch_set(_batch_test_batch_1());
break;
case 2:
batch_set(_batch_test_batch_2());
break;
}
if ($step < 2) {
$form_state->set('step', ++$step);
$form_state->setRebuild();
}
$form_state->setRedirect('batch_test.redirect');
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Drupal\batch_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Generate form of id batch_test_simple_form.
*/
class BatchTestSimpleForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'batch_test_simple_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['batch'] = array(
'#type' => 'select',
'#title' => 'Choose batch',
'#options' => array(
'batch_0' => 'batch 0',
'batch_1' => 'batch 1',
'batch_2' => 'batch 2',
'batch_3' => 'batch 3',
'batch_4' => 'batch 4',
),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
batch_test_stack(NULL, TRUE);
$function = '_batch_test_' . $form_state->getValue('batch');
batch_set($function());
$form_state->setRedirect('batch_test.redirect');
}
}

View file

@ -0,0 +1,6 @@
name: 'Cache test'
type: module
description: 'Support module for cache system testing.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,6 @@
cache_test.url_bubbling:
path: '/cache-test/url-bubbling'
defaults:
_controller: '\Drupal\cache_test\Controller\CacheTestController::urlBubbling'
requirements:
_access: 'TRUE'

View file

@ -0,0 +1,22 @@
<?php
namespace Drupal\cache_test\Controller;
use Drupal\Core\Url;
/**
* Controller routines for cache_test routes.
*/
class CacheTestController {
/**
* Early renders a URL to test bubbleable metadata bubbling.
*/
public function urlBubbling() {
$url = Url::fromRoute('<current>')->setAbsolute();
return [
'#markup' => 'This URL is early-rendered: ' . $url->toString() . '. Yet, its bubbleable metadata should be bubbled.',
];
}
}

View file

@ -0,0 +1,2 @@
/* This file is for testing CSS file inclusion, no contents are necessary. */

View file

@ -0,0 +1,6 @@
name: 'Common Test'
type: module
description: 'Support module for Common tests.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,115 @@
jquery.farbtastic:
version: 0.1
js:
assets/vendor/farbtastic/farbtastic.js: {}
css:
component:
assets/vendor/farbtastic/farbtastic.css: {}
dependencies:
- core/jquery
# Library to test CSS and JS file assets.
files:
js:
foo.js: {}
css:
theme:
bar.css: {}
# Library to test external CSS and JS file assets.
external:
version: 1
js:
http://example.com/script.js: { type: external }
css:
theme:
http://example.com/stylesheet.css: { type: external }
# Library to test JS file asset attributes (both internal and external).
js-attributes:
version: 1
js:
deferred-internal.js: { attributes: { defer: true, bar: foo } }
http://example.com/deferred-external.js:
type: external
attributes:
foo: bar
defer: true
js-header:
header: true
js:
header.js: {}
dependencies:
- core/drupal
# Library to test setting cache = FALSE, to prevent aggregation.
no-cache:
js:
nocache.js: { cache: false }
order:
js:
weight_-3_1.js: { weight: -3 }
weight_0_1.js: {}
weight_0_2.js: {}
weight_-8_1.js: { weight: -8 }
weight_-8_2.js: { weight: -8 }
weight_-8_3.js: { weight: -8 }
http://example.com/weight_-5_1.js: { type: external, weight: -5 }
weight_-8_4.js: { weight: -8 }
weight_-3_2.js: { weight: -3 }
weight_0_3.js: {}
css:
base:
base_weight_0_1.js: {}
base_weight_0_2.js: {}
base_weight_-8_1.js: { weight: -8 }
base_weight_-101_1.js: { weight: -101 }
layout:
layout_weight_0_1.js: {}
layout_weight_0_2.js: {}
layout_weight_-8_1.js: { weight: -8 }
layout_weight_-101_1.js: { weight: -101 }
component:
component_weight_0_1.js: {}
component_weight_0_2.js: {}
component_weight_-8_1.js: { weight: -8}
component_weight_-101_1.js: { weight: -101}
state:
state_weight_0_1.js: {}
state_weight_0_2.js: {}
state_weight_-8_1.js: { weight: -8}
state_weight_-101_1.js: { weight: -101}
theme:
theme_weight_0_1.js: {}
theme_weight_0_2.js: {}
theme_weight_-8_1.js: { weight: -8}
theme_weight_-101_1.js: { weight: -101}
weight:
css:
theme:
first.css: {}
lighter.js: { weight: -1 }
js:
first.js: {}
lighter.js: { weight: -1 }
before-jquery.js: { weight: -21 }
browsers:
js:
old-ie.js:
browsers:
'IE': 'lte IE 8'
'!IE': false
no-ie.js:
browsers:
IE: false
querystring:
js:
querystring.js?arg1=value1&arg2=value2: {}
css:
theme:
querystring.css?arg1=value1&arg2=value2: {}

View file

@ -0,0 +1,279 @@
<?php
/**
* @file
* Helper module for the Common tests.
*/
use \Drupal\Core\Asset\AttachedAssetsInterface;
/**
* Applies #printed to an element to help test #pre_render.
*/
function common_test_drupal_render_printing_pre_render($elements) {
$elements['#printed'] = TRUE;
return $elements;
}
/**
* Implements hook_TYPE_alter().
*/
function common_test_drupal_alter_alter(&$data, &$arg2 = NULL, &$arg3 = NULL) {
// Alter first argument.
if (is_array($data)) {
$data['foo'] = 'Drupal';
}
elseif (is_object($data)) {
$data->foo = 'Drupal';
}
// Alter second argument, if present.
if (isset($arg2)) {
if (is_array($arg2)) {
$arg2['foo'] = 'Drupal';
}
elseif (is_object($arg2)) {
$arg2->foo = 'Drupal';
}
}
// Try to alter third argument, if present.
if (isset($arg3)) {
if (is_array($arg3)) {
$arg3['foo'] = 'Drupal';
}
elseif (is_object($arg3)) {
$arg3->foo = 'Drupal';
}
}
}
/**
* Implements hook_TYPE_alter() on behalf of Bartik theme.
*
* Same as common_test_drupal_alter_alter(), but here, we verify that themes
* can also alter and come last.
*/
function bartik_drupal_alter_alter(&$data, &$arg2 = NULL, &$arg3 = NULL) {
// Alter first argument.
if (is_array($data)) {
$data['foo'] .= ' theme';
}
elseif (is_object($data)) {
$data->foo .= ' theme';
}
// Alter second argument, if present.
if (isset($arg2)) {
if (is_array($arg2)) {
$arg2['foo'] .= ' theme';
}
elseif (is_object($arg2)) {
$arg2->foo .= ' theme';
}
}
// Try to alter third argument, if present.
if (isset($arg3)) {
if (is_array($arg3)) {
$arg3['foo'] .= ' theme';
}
elseif (is_object($arg3)) {
$arg3->foo .= ' theme';
}
}
}
/**
* Implements hook_TYPE_alter() on behalf of block module.
*
* This is to verify that
* \Drupal::moduleHandler()->alter(array(TYPE1, TYPE2), ...) allows
* hook_module_implements_alter() to affect the order in which module
* implementations are executed.
*/
function block_drupal_alter_foo_alter(&$data, &$arg2 = NULL, &$arg3 = NULL) {
$data['foo'] .= ' block';
}
/**
* Implements hook_module_implements_alter().
*
* @see block_drupal_alter_foo_alter()
*/
function common_test_module_implements_alter(&$implementations, $hook) {
// For
// \Drupal::moduleHandler()->alter(array('drupal_alter', 'drupal_alter_foo'), ...),
// make the block module implementations run after all the other modules. Note
// that when \Drupal::moduleHandler->alter() is called with an array of types,
// the first type is considered primary and controls the module order.
if ($hook == 'drupal_alter_alter' && isset($implementations['block'])) {
$group = $implementations['block'];
unset($implementations['block']);
$implementations['block'] = $group;
}
}
/**
* Implements hook_theme().
*/
function common_test_theme() {
return array(
'common_test_foo' => array(
'variables' => array('foo' => 'foo', 'bar' => 'bar'),
),
'common_test_render_element' => array(
'render element' => 'foo',
),
'common_test_empty' => array(
'variables' => array('foo' => 'foo'),
'function' => 'theme_common_test_empty',
),
);
}
/**
* Provides a theme function for drupal_render().
*/
function theme_common_test_foo($variables) {
return $variables['foo'] . $variables['bar'];
}
/**
* Always returns an empty string.
*/
function theme_common_test_empty($variables) {
return '';
}
/**
* Implements MODULE_preprocess().
*
* @see RenderTest::testDrupalRenderThemePreprocessAttached()
*/
function common_test_preprocess(&$variables, $hook) {
if (!\Drupal::state()->get('theme_preprocess_attached_test', FALSE)) {
return;
}
$variables['#attached']['library'][] = 'test/generic_preprocess';
}
/**
* Implements MODULE_preprocess_HOOK().
*
* @see RenderTest::testDrupalRenderThemePreprocessAttached()
*/
function common_test_preprocess_common_test_render_element(&$variables) {
if (!\Drupal::state()->get('theme_preprocess_attached_test', FALSE)) {
return;
}
$variables['#attached']['library'][] = 'test/specific_preprocess';
}
/**
* Implements hook_library_info_build().
*/
function common_test_library_info_build() {
$libraries = [];
if (\Drupal::state()->get('common_test.library_info_build_test')) {
$libraries['dynamic_library'] = [
'version' => '1.0',
'css' => [
'base' => [
'common_test.css' => [],
],
],
];
}
return $libraries;
}
/**
* Implements hook_library_info_alter().
*/
function common_test_library_info_alter(&$libraries, $module) {
if ($module == 'core' && isset($libraries['jquery.farbtastic'])) {
// Change the version of Farbtastic to 0.0.
$libraries['jquery.farbtastic']['version'] = '0.0';
// Make Farbtastic depend on jQuery Form to test library dependencies.
$libraries['jquery.farbtastic']['dependencies'][] = 'core/jquery.form';
}
// Alter the dynamically registered library definition.
if ($module == 'common_test' && isset($libraries['dynamic_library'])) {
$libraries['dynamic_library']['dependencies'] = [
'core/jquery',
];
}
}
/**
* Implements hook_cron().
*
* System module should handle if a module does not catch an exception and keep
* cron going.
*
* @see common_test_cron_helper()
*/
function common_test_cron() {
throw new Exception(t('Uncaught exception'));
}
/**
* Implements hook_page_attachments().
*
* @see \Drupal\system\Tests\Common\PageRenderTest::assertPageRenderHookExceptions()
*/
function common_test_page_attachments(array &$page) {
$page['#attached']['library'][] = 'core/foo';
$page['#attached']['library'][] = 'core/bar';
$page['#cache']['tags'] = ['example'];
$page['#cache']['contexts'] = ['user.permissions'];
if (\Drupal::state()->get('common_test.hook_page_attachments.descendant_attached', FALSE)) {
$page['content']['#attached']['library'][] = 'core/jquery';
}
if (\Drupal::state()->get('common_test.hook_page_attachments.render_array', FALSE)) {
$page['something'] = [
'#markup' => 'test',
];
}
}
/**
* Implements hook_page_attachments_alter().
*
* @see \Drupal\system\Tests\Common\PageRenderTest::assertPageRenderHookExceptions()
*/
function common_test_page_attachments_alter(array &$page) {
// Remove a library that was added in common_test_page_attachments(), to test
// that this hook can do what it claims to do.
if (isset($page['#attached']['library']) && ($index = array_search('core/bar', $page['#attached']['library'])) && $index !== FALSE) {
unset($page['#attached']['library'][$index]);
}
$page['#attached']['library'][] = 'core/baz';
$page['#cache']['tags'] = ['example'];
$page['#cache']['contexts'] = ['user.permissions'];
if (\Drupal::state()->get('common_test.hook_page_attachments_alter.descendant_attached', FALSE)) {
$page['content']['#attached']['library'][] = 'core/jquery';
}
if (\Drupal::state()->get('common_test.hook_page_attachments_alter.render_array', FALSE)) {
$page['something'] = [
'#markup' => 'test',
];
}
}
/**
* Implements hook_js_settings_alter().
*
* @see \Drupal\system\Tests\Common\JavaScriptTest::testHeaderSetting()
*/
function common_test_js_settings_alter(&$settings, AttachedAssetsInterface $assets) {
// Modify an existing setting.
if (array_key_exists('pluralDelimiter', $settings)) {
$settings['pluralDelimiter'] = '☃';
}
// Add a setting.
$settings['foo'] = 'bar';
}

View file

@ -0,0 +1,2 @@
/* This file is for testing CSS file inclusion, no contents are necessary. */

View file

@ -0,0 +1,22 @@
common_test.l_active_class:
path: '/common-test/type-link-active-class'
defaults:
_title: 'Test active link class'
_controller: '\Drupal\common_test\Controller\CommonTestController::typeLinkActiveClass'
requirements:
_access: 'TRUE'
common_test.destination:
path: '/common-test/destination'
defaults:
_controller: '\Drupal\common_test\Controller\CommonTestController::destination'
requirements:
_access: 'TRUE'
common_test.js_and_css_querystring:
path: '/common-test/query-string'
defaults:
_title: 'Test querystring'
_controller: '\Drupal\common_test\Controller\CommonTestController::jsAndCssQuerystring'
requirements:
_access: 'TRUE'

View file

@ -0,0 +1,6 @@
services:
main_content_renderer.json:
class: Drupal\common_test\Render\MainContent\JsonRenderer
arguments: ['@title_resolver', '@renderer']
tags:
- { name: render.main_content_renderer, format: json }

View file

@ -0,0 +1,92 @@
<?php
namespace Drupal\common_test\Controller;
use Drupal\Component\Utility\Html;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\Response;
/**
* Controller routines for common_test routes.
*/
class CommonTestController {
/**
* Returns links to the current page, with and without query strings.
*
* Using #type 'link' causes these links to be rendered with the link
* generator.
*/
public function typeLinkActiveClass() {
return array(
'no_query' => array(
'#type' => 'link',
'#title' => t('Link with no query string'),
'#url' => Url::fromRoute('<current>'),
'#options' => array(
'set_active_class' => TRUE,
),
),
'with_query' => array(
'#type' => 'link',
'#title' => t('Link with a query string'),
'#url' => Url::fromRoute('<current>'),
'#options' => array(
'query' => array(
'foo' => 'bar',
'one' => 'two',
),
'set_active_class' => TRUE,
),
),
'with_query_reversed' => array(
'#type' => 'link',
'#title' => t('Link with the same query string in reverse order'),
'#url' => Url::fromRoute('<current>'),
'#options' => array(
'query' => array(
'one' => 'two',
'foo' => 'bar',
),
'set_active_class' => TRUE,
),
),
);
}
/**
* Adds a JavaScript file and a CSS file with a query string appended.
*
* @return string
* An empty string.
*/
public function jsAndCssQuerystring() {
$attached = array(
'#attached' => array(
'library' => array(
'node/drupal.node',
),
'css' => array(
drupal_get_path('module', 'node') . '/css/node.admin.css' => array(),
// A relative URI may have a query string.
'/' . drupal_get_path('module', 'node') . '/node-fake.css?arg1=value1&arg2=value2' => array(),
),
),
);
return \Drupal::service('renderer')->renderRoot($attached);
}
/**
* Prints a destination query parameter.
*
* @return \Symfony\Component\HttpFoundation\Response
* A new Response object containing a string with the destination query
* parameter.
*/
public function destination() {
$destination = \Drupal::destination()->getAsArray();
$output = "The destination: " . Html::escape($destination['destination']);
return new Response($output);
}
}

View file

@ -0,0 +1,64 @@
<?php
namespace Drupal\common_test\Render\MainContent;
use Drupal\Core\Cache\CacheableJsonResponse;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Controller\TitleResolverInterface;
use Drupal\Core\Render\MainContent\MainContentRendererInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Default main content renderer for JSON requests.
*/
class JsonRenderer implements MainContentRendererInterface {
/**
* The title resolver.
*
* @var \Drupal\Core\Controller\TitleResolverInterface
*/
protected $titleResolver;
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs a new JsonRenderer.
*
* @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
* The title resolver.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
*/
public function __construct(TitleResolverInterface $title_resolver, RendererInterface $renderer) {
$this->titleResolver = $title_resolver;
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public function renderResponse(array $main_content, Request $request, RouteMatchInterface $route_match) {
$json = [];
$json['content'] = (string) $this->renderer->renderRoot($main_content);
if (!empty($main_content['#title'])) {
$json['title'] = (string) $main_content['#title'];
}
else {
$json['title'] = (string) $this->titleResolver->getTitle($request, $route_match->getRouteObject());
}
$response = new CacheableJsonResponse($json, 200);
$response->addCacheableDependency(CacheableMetadata::createFromRenderArray($main_content));
return $response;
}
}

View file

@ -0,0 +1,13 @@
{#
/**
* @file
* Default theme implementation for the common test foo.
*
* Available variables:
* - foo: foo.
* - bar: bar.
*
* @ingroup themeable
*/
#}
{{ foo }}{{ bar -}}

View file

@ -0,0 +1,12 @@
{#
/**
* @file
* Default theme implementation for the common test render element.
*
* Available variables:
* - foo: A render array.
*
* @ingroup themeable
*/
#}
{{ foo }}

View file

@ -0,0 +1,6 @@
name: 'Common Test Cron Helper'
type: module
description: 'Helper module for CronRunTestCase::testCronExceptions().'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,18 @@
<?php
/**
* @file
* Helper module for the testCronExceptions in addition to common_test module.
*/
/**
* Implements hook_cron().
*
* common_test_cron() throws an exception, but the execution should reach this
* function as well.
*
* @see common_test_cron()
*/
function common_test_cron_helper_cron() {
\Drupal::state()->set('common_test.cron', 'success');
}

View file

@ -0,0 +1,6 @@
name: "Condition Test Support"
type: module
description: "Test general form component for condition plugins."
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,6 @@
condition_test.1:
path: '/condition_test'
defaults:
_form: '\Drupal\condition_test\FormController'
requirements:
_access: 'TRUE'

View file

@ -0,0 +1,73 @@
<?php
namespace Drupal\condition_test;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Condition\ConditionManager;
use Drupal\Core\Form\FormStateInterface;
use Drupal\node\Entity\Node;
/**
* Routing controller class for condition_test testing of condition forms.
*/
class FormController implements FormInterface {
/**
* The condition plugin we will be working with.
*
* @var \Drupal\Core\Condition\ConditionInterface
*/
protected $condition;
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'condition_node_type_test_form';
}
/**
* Constructs a \Drupal\condition_test\FormController object.
*/
public function __construct() {
$manager = new ConditionManager(\Drupal::service('container.namespaces'), \Drupal::cache('discovery'), \Drupal::moduleHandler());
$this->condition = $manager->createInstance('node_type');
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = $this->condition->buildConfigurationForm($form, $form_state);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Implements \Drupal\Core\Form\FormInterface::validateForm().
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$this->condition->validateConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->condition->submitConfigurationForm($form, $form_state);
$config = $this->condition->getConfig();
foreach ($config['bundles'] as $bundle) {
drupal_set_message('Bundle: ' . $bundle);
}
$article = Node::load(1);
$this->condition->setContextValue('node', $article);
if ($this->condition->execute()) {
drupal_set_message(t('Executed successfully.'));
}
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace Drupal\condition_test\Plugin\Condition;
use Drupal\Core\Condition\ConditionPluginBase;
/**
* Provides a condition that requires two users.
*
* @Condition(
* id = "condition_test_dual_user",
* label = @Translation("Dual user"),
* context = {
* "user1" = @ContextDefinition("entity:user", label = @Translation("User 1")),
* "user2" = @ContextDefinition("entity:user", label = @Translation("User 2"))
* }
* )
*/
class ConditionTestDualUser extends ConditionPluginBase {
/**
* {@inheritdoc}
*/
public function evaluate() {
$user1 = $this->getContextValue('user1');
$user2 = $this->getContextValue('user2');
return $user1->id() === $user2->id();
}
/**
* {@inheritdoc}
*/
public function summary() {
return $this->t('This condition has two users.');
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\condition_test\Plugin\Condition;
use Drupal\Core\Condition\ConditionPluginBase;
/**
* Provides a condition that has a no existing context.
*
* @Condition(
* id = "condition_test_no_existing_type",
* label = @Translation("No existing type"),
* context = {
* "no_existing_type" = @ContextDefinition("no_existing_type", label = @Translation("No existing type")),
* }
* )
*/
class ConditionTestNoExistingType extends ConditionPluginBase {
/**
* {@inheritdoc}
*/
public function evaluate() {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function summary() {
return $this->t('Condition that requires a non-existent context.');
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\condition_test\Plugin\Condition;
use Drupal\Core\Condition\ConditionPluginBase;
/**
* Provides a condition with an optional node context.
*
* The context type entity:node is used since that would allow to also use this
* for web tests with the node route context.
*
* @Condition(
* id = "condition_test_optional_context",
* label = @Translation("Optional context"),
* context = {
* "node" = @ContextDefinition("entity:node", label = @Translation("Node"), required = FALSE),
* }
* )
*/
class OptionalContextCondition extends ConditionPluginBase {
/**
* {@inheritdoc}
*/
public function evaluate() {
// Grant access if no context value is given.
return !$this->getContextValue('node');
}
/**
* {@inheritdoc}
*/
public function summary() {
return $this->t('Context with optional context.');
}
}

View file

@ -0,0 +1,6 @@
name: Content negotiation test
type: module
description: 'Support testing content negotiation variations.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,32 @@
# Tests
conneg.simpletest:
path: conneg/simple.json
defaults:
_controller: '\Drupal\conneg_test\Controller\TestController::simple'
requirements:
_access: 'TRUE'
conneg.html:
path: conneg/html
defaults:
_controller: '\Drupal\conneg_test\Controller\TestController::html'
requirements:
_access: 'TRUE'
conneg.simple_conneg:
path: conneg/html
defaults:
_controller: '\Drupal\conneg_test\Controller\TestController::format'
requirements:
_access: 'TRUE'
_format: 'json|xml'
conneg.variable_with_period:
path: conneg/plugin/{plugin_id}
defaults:
_controller: '\Drupal\conneg_test\Controller\TestController::variable'
requirements:
_access: 'TRUE'
conneg.full_content_negotiation:
path: conneg/negotiate
defaults:
_controller: '\Drupal\conneg_test\Controller\TestController::format'
requirements:
_access: 'TRUE'

View file

@ -0,0 +1,71 @@
<?php
namespace Drupal\conneg_test\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Test controller for content negotation tests.
*/
class TestController {
/**
* Returns a json response.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function simple() {
return new JsonResponse(['some' => 'data']);
}
/**
* Returns a simple render array.
*
* @return array
*/
public function html() {
return [
'#markup' => 'here',
];
}
/**
* Returns different responses dependening on the request format.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request
*
* @return \Symfony\Component\HttpFoundation\Response
* The response.
*/
public function format(Request $request) {
switch ($request->getRequestFormat()) {
case 'json':
return new JsonResponse(['some' => 'data']);
case 'xml':
return new Response('<xml></xml>', Response::HTTP_OK, ['Content-Type' => 'application/xml']);
default:
return new Response($request->getRequestFormat());
}
}
/**
* Returns a render array depending on some passed in value.
*
* @param string $plugin_id
* The plugin ID.
*
* @return array
* The render array
*/
public function variable($plugin_id) {
return [
'#markup' => $plugin_id,
];
}
}

View file

@ -0,0 +1,6 @@
name: 'Cron Queue test'
type: module
description: 'Support module for the cron queue runner.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,27 @@
<?php
namespace Drupal\cron_queue_test\Plugin\QueueWorker;
use Drupal\Core\Queue\QueueWorkerBase;
use Drupal\Core\Queue\SuspendQueueException;
/**
* @QueueWorker(
* id = "cron_queue_test_broken_queue",
* title = @Translation("Broken queue test"),
* cron = {"time" = 60}
* )
*/
class CronQueueTestBrokenQueue extends QueueWorkerBase {
/**
* {@inheritdoc}
*/
public function processItem($data) {
if ($data == 'crash') {
throw new SuspendQueueException('The queue is broken.');
}
// Do nothing otherwise.
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Drupal\cron_queue_test\Plugin\QueueWorker;
use Drupal\Core\Queue\QueueWorkerBase;
/**
* @QueueWorker(
* id = "cron_queue_test_exception",
* title = @Translation("Exception test"),
* cron = {"time" = 1}
* )
*/
class CronQueueTestException extends QueueWorkerBase {
/**
* {@inheritdoc}
*/
public function processItem($data) {
$state = \Drupal::state();
if (!$state->get('cron_queue_test_exception')) {
$state->set('cron_queue_test_exception', 1);
throw new \Exception('That is not supposed to happen.');
}
else {
$state->set('cron_queue_test_exception', 2);
}
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace Drupal\cron_queue_test\Plugin\QueueWorker;
use Drupal\Core\Queue\QueueWorkerBase;
use Drupal\Core\Queue\RequeueException;
/**
* @QueueWorker(
* id = "cron_queue_test_requeue_exception",
* title = @Translation("RequeueException test"),
* cron = {"time" = 60}
* )
*/
class CronQueueTestRequeueException extends QueueWorkerBase {
/**
* {@inheritdoc}
*/
public function processItem($data) {
$state = \Drupal::state();
if (!$state->get('cron_queue_test_requeue_exception')) {
$state->set('cron_queue_test_requeue_exception', 1);
throw new RequeueException('I am not done yet!');
}
else {
$state->set('cron_queue_test_requeue_exception', 2);
}
}
}

View file

@ -0,0 +1,6 @@
name: CSRF test
type: module
description: 'Support testing protecting routes with CSRF token.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,27 @@
# Tests CSRF request header token protection.
csrf_test.protected:
path: csrf/protected
defaults:
_controller: '\Drupal\csrf_test\Controller\TestController::testMethod'
requirements:
_csrf_request_header_token: 'TRUE'
_method: 'POST'
# Tests deprecated _access_rest_csrf protection.
# This originally was in the REST module but now is supported in core/lib.
# @see https://www.drupal.org/node/2753681
# @todo Remove this test route in Drupal 9.0.0.
csrf_test.deprecated.protected:
path: csrf/deprecated/protected
defaults:
_controller: '\Drupal\csrf_test\Controller\TestController::testMethod'
requirements:
_access_rest_csrf: 'TRUE'
_method: 'POST'
# @todo This route can be removed in 8.3.
# @see \Drupal\Core\Access\CsrfRequestHeaderAccessCheck::access()
csrf_test.deprecated.csrftoken:
path: '/deprecated/session/token'
defaults:
_controller: '\Drupal\csrf_test\Controller\DeprecatedCsrfTokenController::csrfToken'
requirements:
_access: 'TRUE'

View file

@ -0,0 +1,57 @@
<?php
namespace Drupal\csrf_test\Controller;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
/**
* Returns responses for Deprecated CSRF token routes.
*
* This controller tests using the deprecated CSRF token key 'rest'.
*
* @todo This class can be removed in 8.3.
*
* @see \Drupal\Core\Access\CsrfRequestHeaderAccessCheck::access()
*/
class DeprecatedCsrfTokenController implements ContainerInjectionInterface {
/**
* The CSRF token generator.
*
* @var \Drupal\Core\Access\CsrfTokenGenerator
*/
protected $tokenGenerator;
/**
* Constructs a new CsrfTokenController object.
*
* @param \Drupal\Core\Access\CsrfTokenGenerator $token_generator
* The CSRF token generator.
*/
public function __construct(CsrfTokenGenerator $token_generator) {
$this->tokenGenerator = $token_generator;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('csrf_token')
);
}
/**
* Returns a CSRF using the deprecated 'rest' value protecting session token.
*
* @return \Symfony\Component\HttpFoundation\Response
* The response object.
*/
public function csrfToken() {
return new Response($this->tokenGenerator->get('rest'), 200, ['Content-Type' => 'text/plain']);
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Drupal\csrf_test\Controller;
use Symfony\Component\HttpFoundation\Response;
/**
* Just a test controller for test routes.
*/
class TestController {
/**
* Just a test method for the test routes.
*
* @return \Symfony\Component\HttpFoundation\Response
* The response object.
*/
public function testMethod() {
return new Response('Sometimes it is hard to think of test content!');
}
}

View file

@ -0,0 +1,6 @@
name: 'Database Test'
type: module
description: 'Support module for Database layer tests.'
core: 8.x
package: Testing
version: VERSION

View file

@ -0,0 +1,296 @@
<?php
/**
* @file
* Install, update and uninstall functions for the database_test module.
*/
/**
* Implements hook_schema().
*
* The database tests use the database API which depends on schema
* information for certain operations on certain databases.
* Therefore, the schema must actually be declared in a normal module
* like any other, not directly in the test file.
*/
function database_test_schema() {
$schema['test'] = array(
'description' => 'Basic test table for the database unit tests.',
'fields' => array(
'id' => array(
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'name' => array(
'description' => "A person's name",
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
'default' => '',
'binary' => TRUE,
),
'age' => array(
'description' => "The person's age",
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'job' => array(
'description' => "The person's job",
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => 'Undefined',
),
),
'primary key' => array('id'),
'unique keys' => array(
'name' => array('name')
),
'indexes' => array(
'ages' => array('age'),
),
);
// This is an alternate version of the same table that is structured the same
// but has a non-serial Primary Key.
$schema['test_people'] = array(
'description' => 'A duplicate version of the test table, used for additional tests.',
'fields' => array(
'name' => array(
'description' => "A person's name",
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'age' => array(
'description' => "The person's age",
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'job' => array(
'description' => "The person's job",
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
),
'primary key' => array('job'),
'indexes' => array(
'ages' => array('age'),
),
);
$schema['test_people_copy'] = array(
'description' => 'A duplicate version of the test_people table, used for additional tests.',
'fields' => array(
'name' => array(
'description' => "A person's name",
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'age' => array(
'description' => "The person's age",
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'job' => array(
'description' => "The person's job",
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
),
'primary key' => array('job'),
'indexes' => array(
'ages' => array('age'),
),
);
$schema['test_one_blob'] = array(
'description' => 'A simple table including a BLOB field for testing BLOB behavior.',
'fields' => array(
'id' => array(
'description' => 'Simple unique ID.',
'type' => 'serial',
'not null' => TRUE,
),
'blob1' => array(
'description' => 'A BLOB field.',
'type' => 'blob',
),
),
'primary key' => array('id'),
);
$schema['test_two_blobs'] = array(
'description' => 'A simple test table with two BLOB fields.',
'fields' => array(
'id' => array(
'description' => 'Simple unique ID.',
'type' => 'serial',
'not null' => TRUE,
),
'blob1' => array(
'description' => 'A dummy BLOB field.',
'type' => 'blob',
),
'blob2' => array(
'description' => 'A second BLOB field.',
'type' => 'blob'
),
),
'primary key' => array('id'),
);
$schema['test_task'] = array(
'description' => 'A task list for people in the test table.',
'fields' => array(
'tid' => array(
'description' => 'Task ID, primary key.',
'type' => 'serial',
'not null' => TRUE,
),
'pid' => array(
'description' => 'The {test_people}.pid, foreign key for the test table.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'task' => array(
'description' => 'The task to be completed.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'priority' => array(
'description' => 'The priority of the task.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('tid'),
);
$schema['test_null'] = array(
'description' => 'Basic test table for NULL value handling.',
'fields' => array(
'id' => array(
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'name' => array(
'description' => "A person's name.",
'type' => 'varchar_ascii',
'length' => 255,
'not null' => FALSE,
'default' => '',
),
'age' => array(
'description' => "The person's age.",
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
'default' => 0),
),
'primary key' => array('id'),
'unique keys' => array(
'name' => array('name')
),
'indexes' => array(
'ages' => array('age'),
),
);
$schema['test_serialized'] = array(
'description' => 'Basic test table for NULL value handling.',
'fields' => array(
'id' => array(
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'name' => array(
'description' => "A person's name.",
'type' => 'varchar_ascii',
'length' => 255,
'not null' => FALSE,
'default' => '',
),
'info' => array(
'description' => "The person's data in serialized form.",
'type' => 'blob',
'serialize' => TRUE,
),
),
'primary key' => array('id'),
'unique keys' => array(
'name' => array('name')
),
);
$schema['test_composite_primary'] = array(
'description' => 'Basic test table with a composite primary key',
'fields' => array(
'name' => array(
'description' => "A person's name",
'type' => 'varchar',
'length' => 50,
'not null' => TRUE,
'default' => '',
'binary' => TRUE,
),
'age' => array(
'description' => "The person's age",
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'job' => array(
'description' => "The person's job",
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => 'Undefined',
),
),
'primary key' => array('name', 'age'),
);
$schema['test_special_columns'] = array(
'description' => 'A simple test table with special column names.',
'fields' => array(
'id' => array(
'description' => 'Simple unique ID.',
'type' => 'int',
'not null' => TRUE,
),
'offset' => array(
'description' => 'A column with preserved name.',
'type' => 'text',
),
),
'primary key' => array('id'),
);
$schema['TEST_UPPERCASE'] = $schema['test'];
return $schema;
}

View file

@ -0,0 +1,49 @@
<?php
/**
* @file
* Database test module.
*/
use Drupal\Core\Database\Query\AlterableInterface;
/**
* Implements hook_query_alter().
*/
function database_test_query_alter(AlterableInterface $query) {
if ($query->hasTag('database_test_alter_add_range')) {
$query->range(0, 2);
}
if ($query->hasTag('database_test_alter_add_join')) {
$people_alias = $query->join('test', 'people', "test_task.pid = %alias.id");
$query->addField($people_alias, 'name', 'name');
$query->condition($people_alias . '.id', 2);
}
if ($query->hasTag('database_test_alter_change_conditional')) {
$conditions =& $query->conditions();
$conditions[0]['value'] = 2;
}
if ($query->hasTag('database_test_alter_change_fields')) {
$fields =& $query->getFields();
unset($fields['age']);
}
if ($query->hasTag('database_test_alter_change_expressions')) {
$expressions =& $query->getExpressions();
$expressions['double_age']['expression'] = 'age*3';
}
}
/**
* Implements hook_query_TAG_alter().
*
* Called by DatabaseTestCase::testAlterRemoveRange.
*/
function database_test_query_database_test_alter_remove_range_alter(AlterableInterface $query) {
$query->range();
}

View file

@ -0,0 +1,41 @@
database_test.db_query_temporary:
path: '/database_test/db_query_temporary'
defaults:
_controller: '\Drupal\database_test\Controller\DatabaseTestController::dbQueryTemporary'
requirements:
_access: 'TRUE'
database_test.pager_query_even:
path: '/database_test/pager_query_even/{limit}'
defaults:
_controller: '\Drupal\database_test\Controller\DatabaseTestController::pagerQueryEven'
requirements:
_access: 'TRUE'
database_test.pager_query_odd:
path: '/database_test/pager_query_odd/{limit}'
defaults:
_controller: '\Drupal\database_test\Controller\DatabaseTestController::pagerQueryOdd'
requirements:
_access: 'TRUE'
database_test.tablesort:
path: '/database_test/tablesort'
defaults:
_controller: '\Drupal\database_test\Controller\DatabaseTestController::testTablesort'
requirements:
_access: 'TRUE'
database_test.tablesort_first:
path: '/database_test/tablesort_first'
defaults:
_controller: '\Drupal\database_test\Controller\DatabaseTestController::testTablesortFirst'
requirements:
_access: 'TRUE'
database_test.tablesort_default_sort:
path: '/database_test/tablesort_default_sort'
defaults:
_form: '\Drupal\database_test\Form\DatabaseTestForm'
requirements:
_access: 'TRUE'

View file

@ -0,0 +1,146 @@
<?php
namespace Drupal\database_test\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
/**
* Controller routines for database_test routes.
*/
class DatabaseTestController {
/**
* Runs db_query_temporary() and outputs the table name and its number of rows.
*
* We need to test that the table created is temporary, so we run it here, in a
* separate menu callback request; After this request is done, the temporary
* table should automatically dropped.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function dbQueryTemporary() {
$table_name = db_query_temporary('SELECT age FROM {test}', array());
return new JsonResponse(array(
'table_name' => $table_name,
'row_count' => db_select($table_name)->countQuery()->execute()->fetchField(),
));
}
/**
* Runs a pager query and returns the results.
*
* This function does care about the page GET parameter, as set by the
* simpletest HTTP call.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function pagerQueryEven($limit) {
$query = db_select('test', 't');
$query
->fields('t', array('name'))
->orderBy('age');
// This should result in 2 pages of results.
$query = $query
->extend('Drupal\Core\Database\Query\PagerSelectExtender')
->limit($limit);
$names = $query->execute()->fetchCol();
return new JsonResponse(array(
'names' => $names,
));
}
/**
* Runs a pager query and returns the results.
*
* This function does care about the page GET parameter, as set by the
* simpletest HTTP call.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function pagerQueryOdd($limit) {
$query = db_select('test_task', 't');
$query
->fields('t', array('task'))
->orderBy('pid');
// This should result in 4 pages of results.
$query = $query
->extend('Drupal\Core\Database\Query\PagerSelectExtender')
->limit($limit);
$names = $query->execute()->fetchCol();
return new JsonResponse(array(
'names' => $names,
));
}
/**
* Runs a tablesort query and returns the results.
*
* This function does care about the page GET parameter, as set by the
* simpletest HTTP call.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function testTablesort() {
$header = array(
'tid' => array('data' => t('Task ID'), 'field' => 'tid', 'sort' => 'desc'),
'pid' => array('data' => t('Person ID'), 'field' => 'pid'),
'task' => array('data' => t('Task'), 'field' => 'task'),
'priority' => array('data' => t('Priority'), 'field' => 'priority', ),
);
$query = db_select('test_task', 't');
$query
->fields('t', array('tid', 'pid', 'task', 'priority'));
$query = $query
->extend('Drupal\Core\Database\Query\TableSortExtender')
->orderByHeader($header);
// We need all the results at once to check the sort.
$tasks = $query->execute()->fetchAll();
return new JsonResponse(array(
'tasks' => $tasks,
));
}
/**
* Runs a tablesort query with a second order_by after and returns the results.
*
* This function does care about the page GET parameter, as set by the
* simpletest HTTP call.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function testTablesortFirst() {
$header = array(
'tid' => array('data' => t('Task ID'), 'field' => 'tid', 'sort' => 'desc'),
'pid' => array('data' => t('Person ID'), 'field' => 'pid'),
'task' => array('data' => t('Task'), 'field' => 'task'),
'priority' => array('data' => t('Priority'), 'field' => 'priority', ),
);
$query = db_select('test_task', 't');
$query
->fields('t', array('tid', 'pid', 'task', 'priority'));
$query = $query
->extend('Drupal\Core\Database\Query\TableSortExtender')
->orderByHeader($header)
->orderBy('priority');
// We need all the results at once to check the sort.
$tasks = $query->execute()->fetchAll();
return new JsonResponse(array(
'tasks' => $tasks,
));
}
}

View file

@ -0,0 +1,75 @@
<?php
namespace Drupal\database_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\user\Entity\User;
/**
* Form controller for database_test module.
*/
class DatabaseTestForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'database_test_theme_tablesort';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$header = array(
'username' => array('data' => t('Username'), 'field' => 'u.name'),
'status' => array('data' => t('Status'), 'field' => 'u.status'),
);
$query = db_select('users_field_data', 'u');
$query->condition('u.uid', 0, '<>');
$query->condition('u.default_langcode', 1);
$count_query = clone $query;
$count_query->addExpression('COUNT(u.uid)');
$query = $query
->extend('Drupal\Core\Database\Query\PagerSelectExtender')
->extend('Drupal\Core\Database\Query\TableSortExtender');
$query
->fields('u', array('uid'))
->limit(50)
->orderByHeader($header)
->setCountQuery($count_query);
$uids = $query
->execute()
->fetchCol();
$options = array();
foreach (User::loadMultiple($uids) as $account) {
$options[$account->id()] = array(
'title' => array('data' => array('#title' => $account->getUsername())),
'username' => $account->getUsername(),
'status' => $account->isActive() ? t('active') : t('blocked'),
);
}
$form['accounts'] = array(
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
'#empty' => t('No people available.'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
}
}

View file

@ -0,0 +1,6 @@
name: 'Display variant tests'
type: module
description: 'Support module for testing display variants.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,5 @@
services:
display_variant_test.page_display_variant_subscriber:
class: Drupal\display_variant_test\EventSubscriber\TestPageDisplayVariantSubscriber
tags:
- { name: 'event_subscriber', priority: 1000 }

View file

@ -0,0 +1,39 @@
<?php
namespace Drupal\display_variant_test\EventSubscriber;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Render\PageDisplayVariantSelectionEvent;
use Drupal\Core\Render\RenderEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Selects the test page display variant.
*/
class TestPageDisplayVariantSubscriber implements EventSubscriberInterface {
/**
* Selects the page display variant.
*
* @param \Drupal\Core\Render\PageDisplayVariantSelectionEvent $event
* The event to process.
*/
public function onSelectPageDisplayVariant(PageDisplayVariantSelectionEvent $event) {
$event->setPluginId('display_variant_test');
$event->setPluginConfiguration(['required_configuration' => 'A very important, required value.']);
$event->addCacheTags(['custom_cache_tag']);
$context = new Context(new ContextDefinition('string', NULL, TRUE), 'Explicitly passed in context.');
$event->setContexts(['context' => $context]);
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[RenderEvents::SELECT_PAGE_DISPLAY_VARIANT][] = array('onSelectPageDisplayVariant');
return $events;
}
}

View file

@ -0,0 +1,106 @@
<?php
namespace Drupal\display_variant_test\Plugin\DisplayVariant;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Display\VariantBase;
use Drupal\Core\Display\PageVariantInterface;
use Drupal\Core\Display\ContextAwareVariantInterface;
/**
* Provides a display variant that requires configuration.
*
* @DisplayVariant(
* id = "display_variant_test",
* admin_label = @Translation("Test display variant")
* )
*/
class TestDisplayVariant extends VariantBase implements PageVariantInterface, ContextAwareVariantInterface {
/**
* The render array representing the main page content.
*
* @var array
*/
protected $mainContent = [];
/**
* The page title: a string (plain title) or a render array (formatted title).
*
* @var string|array
*/
protected $title = '';
/**
* An array of collected contexts.
*
* This is only used on runtime, and is not stored.
*
* @var \Drupal\Component\Plugin\Context\ContextInterface[]
*/
protected $contexts = [];
/**
* Gets the contexts.
*
* @return \Drupal\Component\Plugin\Context\ContextInterface[]
* An array of set contexts, keyed by context name.
*/
public function getContexts() {
return $this->contexts;
}
/**
* Sets the contexts.
*
* @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
* An array of contexts, keyed by context name.
*
* @return $this
*/
public function setContexts(array $contexts) {
$this->contexts = $contexts;
return $this;
}
/**
* {@inheritdoc}
*/
public function setMainContent(array $main_content) {
$this->mainContent = $main_content;
return $this;
}
/**
* {@inheritdoc}
*/
public function setTitle($title) {
$this->title = $title;
return $this;
}
/**
* {@inheritdoc}
*/
public function build() {
$config = $this->getConfiguration();
if (empty($config['required_configuration'])) {
throw new \Exception('Required configuration is missing!');
}
$contexts = $this->getContexts();
if (!isset($contexts['context'])) {
throw new \Exception('Required context is missing!');
}
$build = [];
$build['content']['default'] = [
'#markup' => $config['required_configuration'] . ' ' . $contexts['context']->getContextValue(),
];
CacheableMetadata::createFromObject($this)->applyTo($build);
return $build;
}
}

View file

@ -0,0 +1,6 @@
name: 'Drupal system listing compatible test'
type: module
description: 'Support module for testing the drupal_system_listing function.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,6 @@
name: 'Early rendering controller test'
type: module
description: 'Support module for EarlyRenderingControllerTest.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,111 @@
# Controller returning a render array.
early_rendering_controller_test.render_array:
path: '/early-rendering-controller-test/render-array'
defaults:
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::renderArray'
requirements:
_access: 'TRUE'
early_rendering_controller_test.render_array.early:
path: '/early-rendering-controller-test/render-array/early'
defaults:
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::renderArrayEarly'
requirements:
_access: 'TRUE'
# Controller returning an AjaxResponse.
early_rendering_controller_test.ajax_response:
path: '/early-rendering-controller-test/ajax-response'
defaults:
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::ajaxResponse'
requirements:
_access: 'TRUE'
early_rendering_controller_test.ajax_response.early:
path: '/early-rendering-controller-test/ajax-response/early'
defaults:
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::ajaxResponseEarly'
requirements:
_access: 'TRUE'
# Controller returning a basic Response object.
early_rendering_controller_test.response:
path: '/early-rendering-controller-test/response'
defaults:
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::response'
requirements:
_access: 'TRUE'
early_rendering_controller_test.response.early:
path: '/early-rendering-controller-test/response/early'
defaults:
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::responseEarly'
requirements:
_access: 'TRUE'
# Controller returning a Response object with attachments.
early_rendering_controller_test.response-with-attachments:
path: '/early-rendering-controller-test/response-with-attachments'
defaults:
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::responseWithAttachments'
requirements:
_access: 'TRUE'
early_rendering_controller_test.response-with-attachments.early:
path: '/early-rendering-controller-test/response-with-attachments/early'
defaults:
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::responseWithAttachmentsEarly'
requirements:
_access: 'TRUE'
# Controller returning a cacheable Response object.
early_rendering_controller_test.cacheable-response:
path: '/early-rendering-controller-test/cacheable-response'
defaults:
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::cacheableResponse'
requirements:
_access: 'TRUE'
early_rendering_controller_test.cacheable-response.early:
path: '/early-rendering-controller-test/cacheable-response/early'
defaults:
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::cacheableResponseEarly'
requirements:
_access: 'TRUE'
# Controller returning a basic domain object.
early_rendering_controller_test.domain-object:
path: '/early-rendering-controller-test/domain-object'
defaults:
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::domainObject'
requirements:
_access: 'TRUE'
early_rendering_controller_test.domain-object.early:
path: '/early-rendering-controller-test/domain-object/early'
defaults:
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::domainObjectEarly'
requirements:
_access: 'TRUE'
# Controller returning a domain object with attachments.
early_rendering_controller_test.domain-object-with-attachments:
path: '/early-rendering-controller-test/domain-object-with-attachments'
defaults:
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::domainObjectWithAttachments'
requirements:
_access: 'TRUE'
early_rendering_controller_test.domain-object-with-attachments.early:
path: '/early-rendering-controller-test/domain-object-with-attachments/early'
defaults:
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::domainObjectWithAttachmentsEarly'
requirements:
_access: 'TRUE'
# Controller returning a cacheable domain object.
early_rendering_controller_test.cacheable-domain-object:
path: '/early-rendering-controller-test/cacheable-domain-object'
defaults:
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::cacheableDomainObject'
requirements:
_access: 'TRUE'
early_rendering_controller_test.cacheable-domain-object.early:
path: '/early-rendering-controller-test/cacheable-domain-object/early'
defaults:
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::cacheableDomainObjectEarly'
requirements:
_access: 'TRUE'

View file

@ -0,0 +1,5 @@
services:
test_domain_object.view_subscriber:
class: Drupal\early_rendering_controller_test\TestDomainObjectViewSubscriber
tags:
- { name: event_subscriber }

View file

@ -0,0 +1,12 @@
<?php
namespace Drupal\early_rendering_controller_test;
use Drupal\Core\Render\AttachmentsInterface;
use Drupal\Core\Render\AttachmentsTrait;
class AttachmentsTestDomainObject extends TestDomainObject implements AttachmentsInterface {
use AttachmentsTrait;
}

View file

@ -0,0 +1,13 @@
<?php
namespace Drupal\early_rendering_controller_test;
use Drupal\Core\Render\AttachmentsInterface;
use Drupal\Core\Render\AttachmentsTrait;
use Symfony\Component\HttpFoundation\Response;
class AttachmentsTestResponse extends Response implements AttachmentsInterface {
use AttachmentsTrait;
}

View file

@ -0,0 +1,12 @@
<?php
namespace Drupal\early_rendering_controller_test;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\UncacheableDependencyTrait;
class CacheableTestDomainObject extends TestDomainObject implements CacheableDependencyInterface {
use UncacheableDependencyTrait;
}

View file

@ -0,0 +1,13 @@
<?php
namespace Drupal\early_rendering_controller_test;
use Drupal\Core\Cache\CacheableResponseInterface;
use Drupal\Core\Cache\CacheableResponseTrait;
use Symfony\Component\HttpFoundation\Response;
class CacheableTestResponse extends Response implements CacheableResponseInterface {
use CacheableResponseTrait;
}

View file

@ -0,0 +1,145 @@
<?php
namespace Drupal\early_rendering_controller_test;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\InsertCommand;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Render\RendererInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
/**
* Controller routines for early_rendering_test routes.
*
* The methods on this controller each correspond to a route for this module,
* each of which exist solely for test cases in EarlyRenderingControllerTest;
* see that test for documentation.
*
* @see core/modules/early_rendering_controller_test/early_rendering_controller_test.routing.yml
* @see \Drupal\system\Tests\Common\EarlyRenderingControllerTest::testEarlyRendering()
*/
class EarlyRenderingTestController extends ControllerBase {
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs a EarlyRenderingTestController.
*
* @param \Drupal\Core\Render\RendererInterface $renderer
*/
public function __construct(RendererInterface $renderer) {
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('renderer')
);
}
protected function earlyRenderContent() {
return [
'#markup' => 'Hello world!',
'#cache' => [
'tags' => [
'foo',
],
],
];
}
public function renderArray() {
return [
'#pre_render' => [function () {
$elements = $this->earlyRenderContent();
return $elements;
}],
];
}
public function renderArrayEarly() {
$render_array = $this->earlyRenderContent();
return [
'#markup' => $this->renderer->render($render_array),
];
}
public function ajaxResponse() {
$response = new AjaxResponse();
$response->addCommand(new InsertCommand(NULL, $this->renderArray()));
return $response;
}
public function ajaxResponseEarly() {
$response = new AjaxResponse();
$response->addCommand(new InsertCommand(NULL, $this->renderArrayEarly()));
return $response;
}
public function response() {
return new Response('Hello world!');
}
public function responseEarly() {
$render_array = $this->earlyRenderContent();
return new Response($this->renderer->render($render_array));
}
public function responseWithAttachments() {
return new AttachmentsTestResponse('Hello world!');
}
public function responseWithAttachmentsEarly() {
$render_array = $this->earlyRenderContent();
return new AttachmentsTestResponse($this->renderer->render($render_array));
}
public function cacheableResponse() {
return new CacheableTestResponse('Hello world!');
}
public function cacheableResponseEarly() {
$render_array = $this->earlyRenderContent();
return new CacheableTestResponse($this->renderer->render($render_array));
}
public function domainObject() {
return new TestDomainObject();
}
public function domainObjectEarly() {
$render_array = $this->earlyRenderContent();
$this->renderer->render($render_array);
return new TestDomainObject();
}
public function domainObjectWithAttachments() {
return new AttachmentsTestDomainObject();
}
public function domainObjectWithAttachmentsEarly() {
$render_array = $this->earlyRenderContent();
$this->renderer->render($render_array);
return new AttachmentsTestDomainObject();
}
public function cacheableDomainObject() {
return new CacheableTestDomainObject();
}
public function cacheableDomainObjectEarly() {
$render_array = $this->earlyRenderContent();
$this->renderer->render($render_array);
return new CacheableTestDomainObject();
}
}

View file

@ -0,0 +1,5 @@
<?php
namespace Drupal\early_rendering_controller_test;
class TestDomainObject { }

View file

@ -0,0 +1,46 @@
<?php
namespace Drupal\early_rendering_controller_test;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* View subscriber for turning TestDomainObject objects into Response objects.
*/
class TestDomainObjectViewSubscriber implements EventSubscriberInterface {
/**
* Sets a response given a TestDomainObject instance.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event
* The event to process.
*/
public function onViewTestDomainObject(GetResponseForControllerResultEvent $event) {
$result = $event->getControllerResult();
if ($result instanceof TestDomainObject) {
if ($result instanceof AttachmentsTestDomainObject) {
$event->setResponse(new AttachmentsTestResponse('AttachmentsTestDomainObject'));
}
elseif ($result instanceof CacheableTestDomainObject) {
$event->setResponse(new CacheableTestResponse('CacheableTestDomainObject'));
}
else {
$event->setResponse(new Response('TestDomainObject'));
}
}
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[KernelEvents::VIEW][] = ['onViewTestDomainObject'];
return $events;
}
}

View file

@ -0,0 +1,6 @@
name: 'Entity CRUD Hooks Test'
type: module
description: 'Support module for CRUD hook tests.'
core: 8.x
package: Testing
version: VERSION

View file

@ -0,0 +1,400 @@
<?php
/**
* @file
* Test module for the Entity CRUD API.
*/
use Drupal\Core\Entity\EntityInterface;
/**
* Implements hook_entity_create().
*/
function entity_crud_hook_test_entity_create(EntityInterface $entity) {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called for type ' . $entity->getEntityTypeId());
}
/**
* Implements hook_ENTITY_TYPE_create() for block entities.
*/
function entity_crud_hook_test_block_create() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_create() for comment entities.
*/
function entity_crud_hook_test_comment_create() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_create() for file entities.
*/
function entity_crud_hook_test_file_create() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_create() for node entities.
*/
function entity_crud_hook_test_node_create() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_create() for taxonomy_term entities.
*/
function entity_crud_hook_test_taxonomy_term_create() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_create() for taxonomy_vocabulary entities.
*/
function entity_crud_hook_test_taxonomy_vocabulary_create() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_create() for user entities.
*/
function entity_crud_hook_test_user_create() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_entity_presave().
*/
function entity_crud_hook_test_entity_presave(EntityInterface $entity) {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called for type ' . $entity->getEntityTypeId());
}
/**
* Implements hook_ENTITY_TYPE_presave() for block entities.
*/
function entity_crud_hook_test_block_presave() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_presave() for comment entities.
*/
function entity_crud_hook_test_comment_presave() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_presave() for file entities.
*/
function entity_crud_hook_test_file_presave() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_presave() for node entities.
*/
function entity_crud_hook_test_node_presave() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_presave() for taxonomy_term entities.
*/
function entity_crud_hook_test_taxonomy_term_presave() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_presave() for taxonomy_vocabulary entities.
*/
function entity_crud_hook_test_taxonomy_vocabulary_presave() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_presave() for user entities.
*/
function entity_crud_hook_test_user_presave() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_entity_insert().
*/
function entity_crud_hook_test_entity_insert(EntityInterface $entity) {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called for type ' . $entity->getEntityTypeId());
}
/**
* Implements hook_ENTITY_TYPE_insert() for block entities.
*/
function entity_crud_hook_test_block_insert() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_insert() for comment entities.
*/
function entity_crud_hook_test_comment_insert() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_insert() for file entities.
*/
function entity_crud_hook_test_file_insert() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_insert() for node entities.
*/
function entity_crud_hook_test_node_insert() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_insert() for taxonomy_term entities.
*/
function entity_crud_hook_test_taxonomy_term_insert() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_insert() for taxonomy_vocabulary entities.
*/
function entity_crud_hook_test_taxonomy_vocabulary_insert() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_insert() for user entities.
*/
function entity_crud_hook_test_user_insert() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_entity_load().
*/
function entity_crud_hook_test_entity_load(array $entities, $type) {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called for type ' . $type);
}
/**
* Implements hook_ENTITY_TYPE_load() for block entities.
*/
function entity_crud_hook_test_block_load() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_load() for comment entities.
*/
function entity_crud_hook_test_comment_load() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_load() for file entities.
*/
function entity_crud_hook_test_file_load() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_load() for node entities.
*/
function entity_crud_hook_test_node_load() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_load() for taxonomy_term entities.
*/
function entity_crud_hook_test_taxonomy_term_load() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_load() for taxonomy_vocabulary entities.
*/
function entity_crud_hook_test_taxonomy_vocabulary_load() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_load() for user entities.
*/
function entity_crud_hook_test_user_load() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_entity_update().
*/
function entity_crud_hook_test_entity_update(EntityInterface $entity) {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called for type ' . $entity->getEntityTypeId());
}
/**
* Implements hook_ENTITY_TYPE_update() for block entities.
*/
function entity_crud_hook_test_block_update() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_update() for comment entities.
*/
function entity_crud_hook_test_comment_update() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_update() for file entities.
*/
function entity_crud_hook_test_file_update() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_update() for node entities.
*/
function entity_crud_hook_test_node_update() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_update() for taxonomy_term entities.
*/
function entity_crud_hook_test_taxonomy_term_update() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_update() for taxonomy_vocabulary entities.
*/
function entity_crud_hook_test_taxonomy_vocabulary_update() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_update() for user entities.
*/
function entity_crud_hook_test_user_update() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_entity_predelete().
*/
function entity_crud_hook_test_entity_predelete(EntityInterface $entity) {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called for type ' . $entity->getEntityTypeId());
}
/**
* Implements hook_ENTITY_TYPE_predelete() for block entities.
*/
function entity_crud_hook_test_block_predelete() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_predelete() for comment entities.
*/
function entity_crud_hook_test_comment_predelete() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_predelete() for file entities.
*/
function entity_crud_hook_test_file_predelete() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_predelete() for node entities.
*/
function entity_crud_hook_test_node_predelete() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_predelete() for taxonomy_term entities.
*/
function entity_crud_hook_test_taxonomy_term_predelete() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_predelete() for taxonomy_vocabulary entities.
*/
function entity_crud_hook_test_taxonomy_vocabulary_predelete() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_predelete() for user entities.
*/
function entity_crud_hook_test_user_predelete() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_entity_delete().
*/
function entity_crud_hook_test_entity_delete(EntityInterface $entity) {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called for type ' . $entity->getEntityTypeId());
}
/**
* Implements hook_ENTITY_TYPE_delete() for block entities.
*/
function entity_crud_hook_test_block_delete() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_delete() for comment entities.
*/
function entity_crud_hook_test_comment_delete() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_delete() for file entities.
*/
function entity_crud_hook_test_file_delete() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_delete() for node entities.
*/
function entity_crud_hook_test_node_delete() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_delete() for taxonomy_term entities.
*/
function entity_crud_hook_test_taxonomy_term_delete() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_delete() for taxonomy_vocabulary entities.
*/
function entity_crud_hook_test_taxonomy_vocabulary_delete() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}
/**
* Implements hook_ENTITY_TYPE_delete() for user entities.
*/
function entity_crud_hook_test_user_delete() {
$GLOBALS['entity_crud_hook_test'][] = (__FUNCTION__ . ' called');
}

View file

@ -0,0 +1,125 @@
langcode: en
status: true
dependencies:
module:
- entity_reference_test
- node
- user
id: test_entity_reference
label: 'Entity reference'
module: entity_reference_test
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: '8'
display:
default:
display_plugin: default
id: default
display_title: Master
position: null
display_options:
access:
type: perm
cache:
type: tag
query:
type: views_query
exposed_form:
type: basic
pager:
type: full
style:
type: default
row:
type: fields
fields:
title:
id: title
table: node_field_data
field: title
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: false
ellipsis: false
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
plugin_id: field
entity_type: node
entity_field: title
filters:
status:
value: '1'
table: node_field_data
field: status
id: status
expose:
operator: ''
group: 1
plugin_id: boolean
entity_type: node
entity_field: status
sorts:
created:
id: created
table: node_field_data
field: created
order: DESC
plugin_id: date
entity_type: node
entity_field: created
entity_reference_1:
display_plugin: entity_reference
id: entity_reference_1
display_title: EntityReference
position: null
display_options:
style:
type: entity_reference
options:
grouping: { }
search_fields:
title: title
pager:
type: none
options:
offset: 0

View file

@ -0,0 +1,120 @@
langcode: en
status: true
dependencies:
module:
- entity_reference_test
- entity_test
id: test_entity_reference_entity_test
label: 'Entity reference'
module: views
description: ''
tag: ''
base_table: entity_test
base_field: id
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: none
cache:
type: tag
query:
type: views_query
exposed_form:
type: basic
pager:
type: full
style:
type: default
row:
type: fields
fields:
name:
table: entity_test
field: name
id: name
entity_type: null
entity_field: name
plugin_id: field
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: string
settings: { }
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
filters: { }
sorts: { }
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
entity_reference_1:
display_plugin: entity_reference
id: entity_reference_1
display_title: EntityReference
position: null
display_options:
display_extenders: { }
style:
type: entity_reference
options:
search_fields:
name: name

View file

@ -0,0 +1,11 @@
name: "Entity Reference Test"
type: module
description: "Support module for the Entity Reference tests."
core: 8.x
package: Testing
version: VERSION
dependencies:
- node
- user
- views
- entity_test

View file

@ -0,0 +1,42 @@
<?php
/**
* @file
* Helper module for the Entity Reference tests.
*/
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
/**
* Implements hook_entity_base_field_info().
*/
function entity_reference_test_entity_base_field_info(EntityTypeInterface $entity_type) {
$fields = array();
if ($entity_type->id() === 'entity_test') {
$fields['user_role'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('User role'))
->setDescription(t('The role of the associated user.'))
->setSetting('target_type', 'user_role')
->setSetting('handler', 'default');
}
return $fields;
}
/**
* Implements hook_entity_base_field_info_alter().
*/
function entity_reference_test_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) {
if ($entity_type->id() === 'entity_test') {
// Allow user_id field to use configurable widget.
$fields['user_id']
->setSetting('handler', 'default')
->setDisplayOptions('form', array(
'type' => 'entity_reference_autocomplete',
'weight' => 0,
))
->setDisplayConfigurable('form', TRUE);
}
}

View file

@ -0,0 +1,8 @@
name: 'Entity reference test views'
type: module
description: 'Provides default views for views entity reference tests.'
package: Testing
version: VERSION
core: 8.x
dependencies:
- views

View file

@ -0,0 +1,120 @@
langcode: en
status: true
dependencies:
module:
- entity_test
id: test_entity_reference_entity_test_mul_view
label: test_entity_reference_entity_test_mul_view
module: views
description: ''
tag: ''
base_table: entity_test_mul_property_data
base_field: id
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: none
options: { }
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: full
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous: ' Previous'
next: 'Next '
first: '« First'
last: 'Last »'
quantity: 9
style:
type: default
options:
grouping: { }
row_class: ''
default_row_class: true
uses_fields: false
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
fields:
id:
id: id
table: entity_test_mul_property_data
field: id
entity_type: entity_test_mul
entity_field: id
plugin_id: field
id_1:
id: id_1
table: entity_test
field: id
entity_type: entity_test
entity_field: id
plugin_id: field
relationship: field_data_test
filters: { }
sorts:
id:
id: id
table: entity_test_mul_property_data
field: id
entity_type: entity_test_mul
entity_field: id
plugin_id: standard
header: { }
footer: { }
empty: { }
relationships:
field_data_test:
id: field_data_test
table: entity_test_mul__field_data_test
field: field_data_test
plugin_id: standard
arguments: { }
display_extenders: { }
cache_metadata:
contexts:
- languages
- 'languages:language_interface'
max-age: 0

View file

@ -0,0 +1,121 @@
langcode: en
status: true
dependencies:
module:
- entity_test
id: test_entity_reference_entity_test_view
label: test_entity_reference_entity_test_view
module: views
description: ''
tag: ''
base_table: entity_test
base_field: id
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: none
options: { }
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: full
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous: ' Previous'
next: 'Next '
first: '« First'
last: 'Last »'
quantity: 9
style:
type: default
options:
grouping: { }
row_class: ''
default_row_class: true
uses_fields: false
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
fields:
id:
id: id
table: entity_test
field: id
entity_type: entity_test
entity_field: id
plugin_id: field
id_1:
id: id_1
table: entity_test_mul
field: id
entity_type: entity_test_mul
entity_field: id
plugin_id: field
relationship: field_test_data
filters: { }
sorts:
id:
id: id
table: entity_test
field: id
entity_type: entity_test
entity_field: id
plugin_id: standard
header: { }
footer: { }
empty: { }
relationships:
field_test_data:
id: field_test_data
table: entity_test__field_test_data
field: field_test_data
plugin_id: standard
arguments: { }
display_extenders: { }
cache_metadata:
contexts:
- entity_test_view_grants
- languages
- 'languages:language_interface'
max-age: 0

View file

@ -0,0 +1,121 @@
langcode: en
status: true
dependencies:
module:
- entity_test
id: test_entity_reference_entity_test_view_long
label: test_entity_reference_entity_test_view_long
module: views
description: ''
tag: ''
base_table: entity_test_mul_changed_property
base_field: id
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: none
options: { }
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: full
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous: ' Previous'
next: 'Next '
first: '« First'
last: 'Last »'
quantity: 9
style:
type: default
options:
grouping: { }
row_class: ''
default_row_class: true
uses_fields: false
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
fields:
id:
id: id
table: entity_test_mul_changed_property
field: id
entity_type: entity_test
entity_field: id
plugin_id: field
id_1:
id: id_1
table: entity_test
field: id
entity_type: entity_test
entity_field: id
plugin_id: field
relationship: field_test_data_with_a_long_name
filters: { }
sorts:
id:
id: id
table: entity_test_mul_changed_property
field: id
entity_type: entity_test
entity_field: id
plugin_id: standard
header: { }
footer: { }
empty: { }
relationships:
field_test_data_with_a_long_name:
id: field_test_data_with_a_long_name_data
table: entity_test_mul_changed__field_test_data_with_a_long_name
field: field_test_data_with_a_long_name
plugin_id: standard
arguments: { }
display_extenders: { }
cache_metadata:
contexts:
- entity_test_view_grants
- languages
- 'languages:language_interface'
max-age: 0

View file

@ -0,0 +1,130 @@
langcode: en
status: true
dependencies:
module:
- entity_test
id: test_entity_reference_reverse_entity_test_mul_view
label: test_entity_reference_reverse_entity_test_mul_view
module: views
description: ''
tag: ''
base_table: entity_test
base_field: id
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: none
options: { }
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: full
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous: ' Previous'
next: 'Next '
first: '« First'
last: 'Last »'
quantity: 9
style:
type: default
options:
grouping: { }
row_class: ''
default_row_class: true
uses_fields: false
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
fields:
id:
id: id
table: entity_test
field: id
entity_type: entity_test
entity_field: id
plugin_id: field
id_1:
id: id_1
table: entity_test_mul_property_data
field: id
entity_type: entity_test_mul
entity_field: id
plugin_id: field
relationship: reverse__entity_test_mul__field_data_test
filters: { }
sorts:
id:
id: id
table: entity_test
field: id
entity_type: entity_test
entity_field: id
plugin_id: standard
id_1:
id: id_1
table: entity_test_mul_property_data
field: id
entity_type: entity_test_mul
entity_field: id
plugin_id: standard
relationship: reverse__entity_test_mul__field_data_test
header: { }
footer: { }
empty: { }
relationships:
reverse__entity_test_mul__field_data_test:
id: reverse__entity_test_mul__field_data_test
table: entity_test
field: reverse__entity_test_mul__field_data_test
entity_type: entity_test
plugin_id: entity_reverse
arguments: { }
display_extenders: { }
cache_metadata:
contexts:
- entity_test_view_grants
- languages
- 'languages:language_interface'
max-age: 0

View file

@ -0,0 +1,129 @@
langcode: en
status: true
dependencies:
module:
- entity_test
id: test_entity_reference_reverse_entity_test_view
label: test_entity_reference_reverse_entity_test_view
module: views
description: ''
tag: ''
base_table: entity_test_mul_property_data
base_field: id
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: none
options: { }
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: full
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous: ' Previous'
next: 'Next '
first: '« First'
last: 'Last »'
quantity: 9
style:
type: default
options:
grouping: { }
row_class: ''
default_row_class: true
uses_fields: false
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
fields:
id:
id: id
table: entity_test_mul_property_data
field: id
entity_type: entity_test_mul
entity_field: id
plugin_id: field
id_1:
id: id_1
table: entity_test
field: id
entity_type: entity_test
entity_field: id
plugin_id: field
relationship: reverse__entity_test__field_test_data
filters: { }
sorts:
id:
id: id
table: entity_test_mul_property_data
field: id
entity_type: entity_test_mul
entity_field: id
plugin_id: standard
id_1:
id: id_1
table: entity_test
field: id
entity_type: entity_test
entity_field: id
plugin_id: standard
relationship: reverse__entity_test__field_test_data
header: { }
footer: { }
empty: { }
relationships:
reverse__entity_test__field_test_data:
id: reverse__entity_test__field_test_data
table: entity_test_mul_property_data
field: reverse__entity_test__field_test_data
entity_type: entity_test_mul
plugin_id: entity_reverse
arguments: { }
display_extenders: { }
cache_metadata:
contexts:
- languages
- 'languages:language_interface'
max-age: 0

Some files were not shown because too many files have changed in this diff Show more