Commit b110cd1b authored by willzyx's avatar willzyx

Issue #2418897 by willzyx, pcambra: Provide devel integration for all entity types automatically

parent e73fff0d
devel.devel_node_tab:
route_name: devel.devel_node_load
title: 'Devel'
base_route: entity.node.canonical
weight: 100
devel.devel_node_load_tab:
route_name: devel.devel_node_load
title: 'Load'
parent_id: devel.devel_node_tab
devel.devel_node_render_tab:
route_name: devel.devel_node_render
title: 'Render'
parent_id: devel.devel_node_tab
weight: 100
devel.devel_comment_tab:
route_name: devel.devel_comment_load
title: 'Devel'
base_route: entity.comment.canonical
weight: 100
devel.devel_comment_load_tab:
route_name: devel.devel_comment_load
title: 'Load'
parent_id: devel.devel_comment_tab
devel.devel_comment_render_tab:
route_name: devel.devel_comment_render
title: 'Render'
parent_id: devel.devel_comment_tab
weight: 100
devel.devel_user_tab:
route_name: devel.devel_user_load
title: 'Devel'
base_route: entity.user.canonical
weight: 100
devel.devel_user_load_tab:
route_name: devel.devel_user_load
title: 'Load'
parent_id: devel.devel_user_tab
devel.devel_user_render_tab:
route_name: devel.devel_user_render
title: 'Render'
parent_id: devel.devel_user_tab
weight: 100
devel.devel_taxonomy_term_tab:
route_name: devel.devel_taxonomy_term_load
title: 'Devel'
base_route: entity.taxonomy_term.canonical
weight: 100
devel.devel_taxonomy_term_load_tab:
route_name: devel.devel_taxonomy_term_load
title: 'Load'
parent_id: devel.devel_taxonomy_term_tab
weight: -10
devel.devel_taxonomy_term_render_tab:
route_name: devel.devel_taxonomy_term_render
title: 'Render'
parent_id: devel.devel_taxonomy_term_tab
weight: 100
devel.entities:
class: \Drupal\Core\Menu\LocalTaskDefault
deriver: \Drupal\devel\Plugin\Derivative\DevelLocalTask
......@@ -23,6 +23,7 @@ use Drupal\Component\Utility\String;
use Drupal\Component\Utility\Timer;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Logger\RfcLogLevel;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
......@@ -44,6 +45,38 @@ function devel_help($route_name) {
}
}
/**
* Implements hook_entity_type_alter().
*/
function devel_entity_type_alter(array &$entity_types) {
/** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
foreach ($entity_types as $entity_type_id => $entity_type) {
if ($entity_type->hasViewBuilderClass() && $entity_type->hasLinkTemplate('canonical')) {
$entity_type->setLinkTemplate('devel-render', $entity_type->getLinkTemplate('canonical') . '/devel/render');
}
if ($entity_type->hasFormClasses() && $entity_type->hasLinkTemplate('edit-form')) {
$entity_type->setLinkTemplate('devel-load', $entity_type->getLinkTemplate('edit-form') . '/devel');
}
}
}
/**
* Implements hook_entity_operation().
*/
function devel_entity_operation(EntityInterface $entity) {
$operations = array();
if ($entity->hasLinkTemplate('devel-load') && \Drupal::currentUser()->hasPermission('access devel information')) {
$operations['devel'] = array(
'title' => t('Devel'),
'weight' => 50,
'url' => $entity->urlInfo('devel-load'),
);
}
return $operations;
}
/**
* Returns destinations.
*/
......
......@@ -107,86 +107,6 @@ devel.field_info_page:
requirements:
_permission: 'access devel information'
devel.devel_node_load:
path: '/node/{node}/devel'
defaults:
_controller: '\Drupal\devel\Controller\DevelController::nodeLoad'
_title: 'Devel'
options:
_admin_route: TRUE
requirements:
_permission: 'access devel information'
devel.devel_node_render:
path: '/node/{node}/devel/render'
defaults:
_controller: '\Drupal\devel\Controller\DevelController::nodeRender'
_title: 'Devel'
options:
_admin_route: TRUE
requirements:
_permission: 'access devel information'
devel.devel_user_load:
path: '/user/{user}/devel'
defaults:
_controller: '\Drupal\devel\Controller\DevelController::userLoad'
_title: 'Devel'
options:
_admin_route: TRUE
requirements:
_permission: 'access devel information'
devel.devel_user_render:
path: '/user/{user}/devel/render'
defaults:
_controller: '\Drupal\devel\Controller\DevelController::userRender'
_title: 'Devel'
options:
_admin_route: TRUE
requirements:
_permission: 'access devel information'
devel.devel_comment_load:
path: '/comment/{comment}/devel'
defaults:
_controller: '\Drupal\devel\Controller\DevelController::commentLoad'
_title: 'Devel'
options:
_admin_route: TRUE
requirements:
_permission: 'access devel information'
devel.devel_comment_render:
path: '/comment/{comment}/devel/render'
defaults:
_controller: '\Drupal\devel\Controller\DevelController::commentRender'
_title: 'Devel'
options:
_admin_route: TRUE
requirements:
_permission: 'access devel information'
devel.devel_taxonomy_term_load:
path: '/taxonomy/term/{taxonomy_term}/devel'
defaults:
_controller: '\Drupal\devel\Controller\DevelController::taxonomyTermLoad'
_title: 'Devel'
options:
_admin_route: TRUE
requirements:
_permission: 'access devel information'
devel.devel_taxonomy_term_render:
path: '/taxonomy/term/{taxonomy_term}/devel/render'
defaults:
_controller: '\Drupal\devel\Controller\DevelController::taxonomyTermRender'
_title: 'Devel'
options:
_admin_route: TRUE
requirements:
_permission: 'access devel information'
devel.execute_php:
path: '/devel/php'
defaults:
......
......@@ -5,6 +5,12 @@ services:
tags:
- { name: event_subscriber }
devel.route_subscriber:
class: Drupal\devel\Routing\RouteSubscriber
arguments: ['@entity.manager']
tags:
- { name: event_subscriber }
access_check.switch_user:
class: Drupal\devel\Access\SwitchAccess
arguments: ['@csrf_token']
......
......@@ -7,17 +7,14 @@
namespace Drupal\devel\Controller;
use Drupal\comment\CommentInterface;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Database;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\UserSession;
use Drupal\Core\Url;
use Drupal\node\NodeInterface;
use Drupal\taxonomy\TermInterface;
use Drupal\user\UserInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
......@@ -41,110 +38,6 @@ class DevelController extends ControllerBase {
return kdevel_print_object($item);
}
/**
* Prints the loaded structure of the current node.
*
* @param NodeInterface $node
* The current node.
*
* @return array
* Array of page elements to render.
*/
public function nodeLoad(NodeInterface $node) {
return $this->entityObject($node);
}
/**
* Prints the render structure of the current node.
*
* @param NodeInterface $node
* The current node.
*
* @return array
* Array of page elements to render.
*/
public function nodeRender(NodeInterface $node) {
return $this->renderEntity($node);
}
/**
* Prints the loaded structure of the current user.
*
* @param UserInterface $user
* The current user.
*
* @return array
* Array of page elements to render.
*/
public function userLoad(UserInterface $user) {
return $this->entityObject($user);
}
/**
* Prints the render structure of the current user.
*
* @param UserInterface $user
* The current user.
*
* @return array
* Array of page elements to render.
*/
public function userRender(UserInterface $user) {
return $this->renderEntity($user);
}
/**
* Prints the loaded structure of the current comment.
*
* @param CommentInterface $comment
* The current comment.
*
* @return array
* Array of page elements to render.
*/
public function commentLoad(CommentInterface $comment) {
return $this->entityObject($comment);
}
/**
* Prints the render structure of the current comment.
*
* @param CommentInterface $comment
* The current comment.
*
* @return array
* Array of page elements to render.
*/
public function commentRender(CommentInterface $comment) {
return $this->renderEntity($comment);
}
/**
* Prints the loaded structure of the current taxonomy term.
*
* @param TermInterface $taxonomy_term
* The current taxonomy term.
*
* @return array
* Array of page elements to render.
*/
public function taxonomyTermLoad(TermInterface $taxonomy_term) {
return $this->entityObject($taxonomy_term);
}
/**
* Prints the render structure of the current taxonomy term.
*
* @param TermInterface $taxonomy_term
* The current taxonomy term.
*
* @return array
* Array of page elements to render.
*/
public function taxonomyTermRender(TermInterface $taxonomy_term) {
return $this->renderEntity($taxonomy_term);
}
public function themeRegistry() {
$hooks = theme_get_registry();
ksort($hooks);
......@@ -284,54 +177,60 @@ class DevelController extends ControllerBase {
}
/**
* Return the printed structure of the provided entity.
* Prints the loaded structure of the current entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* An entity object.
* @param string $name
* (optional) The label for the entity. Used only if kint is not enabled.
* If not provided $entity->label() is used. Defaults to NULL.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* A RouteMatch object.
*
* @return array
* Render array containing the printed object.
* Array of page elements to render.
*/
protected function entityObject(EntityInterface $entity, $name = NULL) {
$name = isset($name) ? $name : $entity->label();
$print = kdevel_print_object($entity, '$' . $name . '->');
return array('#markup' => $print);
public function entityLoad(RouteMatchInterface $route_match) {
$output = array();
$parameter_name = $route_match->getRouteObject()->getOption('_devel_entity_type_id');
$entity = $route_match->getParameter($parameter_name);
if ($entity && $entity instanceof EntityInterface) {
$output = array('#markup' => kdevel_print_object($entity));
}
return $output;
}
/**
* Return the printed render structure of the provided entity.
* Prints the render structure of the current entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* An entity object.
* @param string $name
* (optional) The label for the entity. Used only if kint is not enabled.
* If not provided $entity->label() is used. Defaults to NULL.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* A RouteMatch object.
*
* @return array
* Render array containing the printed object.
* Array of page elements to render.
*/
protected function renderEntity(EntityInterface $entity, $name = NULL) {
$name = isset($name) ? $name : $entity->label();
public function entityRender(RouteMatchInterface $route_match) {
$output = array();
$entity_type_id = $entity->getEntityTypeId();
$view_hook = $entity_type_id . '_view';
$parameter_name = $route_match->getRouteObject()->getOption('_devel_entity_type_id');
$entity = $route_match->getParameter($parameter_name);
$build = array();
// If module implements own {entity_type}_view
if (function_exists($view_hook)) {
$build = $view_hook($entity);
}
// If entity has view_builder handler
elseif ($this->entityManager()->hasHandler($entity_type_id, 'view_builder')) {
$build = $this->entityManager()->getViewBuilder($entity_type_id)->view($entity);
}
if ($entity && $entity instanceof EntityInterface) {
$entity_type_id = $entity->getEntityTypeId();
$view_hook = $entity_type_id . '_view';
$print = kdevel_print_object($build, '$' . $name . '->');
$build = array();
// If module implements own {entity_type}_view
if (function_exists($view_hook)) {
$build = $view_hook($entity);
}
// If entity has view_builder handler
elseif ($this->entityManager()->hasHandler($entity_type_id, 'view_builder')) {
$build = $this->entityManager()->getViewBuilder($entity_type_id)->view($entity);
}
return array('#markup' => $print);
$output = array('#markup' => kdevel_print_object($build));
}
return $output;
}
/**
......
<?php
/**
* @file
* Contains \Drupal\devel\Plugin\Derivative\DevelLocalTask.
*/
namespace Drupal\devel\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides local task definitions for all entity bundles.
*/
class DevelLocalTask extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
/**
* The entity manager
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Creates an DevelLocalTask object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The translation manager.
*/
public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $string_translation) {
$this->entityManager = $entity_manager;
$this->stringTranslation = $string_translation;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity.manager'),
$container->get('string_translation')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$this->derivatives = array();
foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
$has_edit_path = $entity_type->hasLinkTemplate('devel-load');
$has_canonical_path = $entity_type->hasLinkTemplate('devel-render');
if ($has_edit_path || $has_canonical_path) {
$this->derivatives["$entity_type_id.devel_tab"] = array(
'route_name' => "entity.$entity_type_id." . ($has_edit_path ? 'devel_load' : 'devel_render'),
'title' => $this->t('Devel'),
'base_route' => "entity.$entity_type_id." . ($has_canonical_path ? "canonical" : "edit_form"),
'weight' => 100,
);
if ($has_canonical_path) {
$this->derivatives["$entity_type_id.devel_render_tab"] = array(
'route_name' => "entity.$entity_type_id.devel_render",
'weight' => 100,
'title' => $this->t('Render'),
'parent_id' => "devel.entities:$entity_type_id.devel_tab",
);
}
if ($has_edit_path) {
$this->derivatives["$entity_type_id.devel_load_tab"] = array(
'route_name' => "entity.$entity_type_id.devel_load",
'weight' => 100,
'title' => $this->t('Load'),
'parent_id' => "devel.entities:$entity_type_id.devel_tab",
);
}
}
}
foreach ($this->derivatives as &$entry) {
$entry += $base_plugin_definition;
}
return $this->derivatives;
}
}
<?php
/**
* @file
* Contains \Drupal\devel\Routing\RouteSubscriber.
*/
namespace Drupal\devel\Routing;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Subscriber for Devel routes.
*/
class RouteSubscriber extends RouteSubscriberBase {
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a new RouteSubscriber object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity type manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
if ($entity_type->hasLinkTemplate('devel-load') || $entity_type->hasLinkTemplate('devel-render')) {
$options = array(
'_admin_route' => TRUE,
'_devel_entity_type_id' => $entity_type_id,
'parameters' => array(
$entity_type_id => array(
'type' => 'entity:' . $entity_type_id,
),
),
);
if ($devel_load = $entity_type->getLinkTemplate('devel-load')) {
$route = new Route(
$devel_load,
array(
'_controller' => '\Drupal\devel\Controller\DevelController::entityLoad',
'_title' => 'Devel Load',
),
array('_permission' => 'access devel information'),
$options
);
$collection->add("entity.$entity_type_id.devel_load", $route);
}
if ($devel_render = $entity_type->getLinkTemplate('devel-render')) {
$route = new Route(
$devel_render,
array(
'_controller' => '\Drupal\devel\Controller\DevelController::entityRender',
'_title' => 'Devel Render',
),
array('_permission' => 'access devel information'),
$options
);
$collection->add("entity.$entity_type_id.devel_render", $route);
}
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events = parent::getSubscribedEvents();
$events[RoutingEvents::ALTER] = array('onAlterRoutes', 100);
return $events;
}
}
<?php
/**
* @file
* Contains \Drupal\devel\Tests\DevelControllerTest.
*/
namespace Drupal\devel\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests Devel controller.
*
* @group devel
*/
class DevelControllerTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('devel', 'node', 'entity_test', 'devel_test');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a test entity.
$random_label = $this->randomMachineName();
$data = array('type' => 'entity_test', 'name' => $random_label);
$this->entity = entity_create('entity_test', $data);
$this->entity->save();
// Create a test entity with only canonical route.
$random_label = $this->randomMachineName();
$data = array('type' => 'devel_entity_test_canonical', 'name' => $random_label);
$this->entity_canonical = entity_create('devel_entity_test_canonical', $data);
$this->entity_canonical->save();
// Create a test entity with only edit route.
$random_label = $this->randomMachineName();
$data = array('type' => 'devel_entity_test_edit', 'name' => $random_label);
$this->entity_edit = entity_create('devel_entity_test_edit', $data);
$this->entity_edit->save();
// Create a test entity with no routes.
$random_label = $this->randomMachineName();
$data = array('type' => 'devel_entity_test_no_links', 'name' => $random_label);
$this->entity_no_links = entity_create('devel_entity_test_no_links', $data);
$this->entity_no_links->save();
$web_user = $this->drupalCreateUser(array(
'view test entity',
'administer entity_test content',
'access devel information',
));
$this->drupalLogin($web_user);
}
function testRouteGeneration() {
// @TODO remove after https://www.drupal.org/node/2431263 is solved.
$this->container->get('module_installer')->install(array('kint'));
// Test Devel load and render routes for entities with both route
// definitions.
$this->drupalGet('entity_test/' . $this->entity->id());
$this->assertText('Devel', 'Devel tab is present');
$this->drupalGet('entity_test/manage/' . $this->entity->id() . '/devel');
$this->assertResponse(200);
$this->assertText('Load', 'Devel load tab is present');
$this->assertText('Render', 'Devel load tab is present');
$this->assertLinkByHref('entity_test/' . $this->entity->id() . '/devel/render');
$this->drupalGet('entity_test/' . $this->entity->id() . '/devel/render');
$this->assertResponse(200);
// Test Devel load and render routes for entities with only canonical route
// definitions.
$this->drupalGet('devel_entity_test_canonical/' . $this->entity_canonical->id());
$this->assertText('Devel', 'Devel tab is present');
$this->assertNoLinkByHref('devel_entity_test_canonical/manage/' . $this->entity_canonical->id() . '/devel');
$this->assertLinkByHref('devel_entity_test_canonical/' . $this->entity_canonical->id() . '/devel/render');
$this->drupalGet('devel_entity_test_canonical/manage/' . $this->entity_canonical->id() . '/devel');
$this->assertResponse(404);
$this->drupalGet('devel_entity_test_canonical/' . $this->entity_canonical->id() . '/devel/render');
$this->assertResponse(200);
// Test Devel load and render routes for entities with only edit route
// definitions.
$this->drupalGet('devel_entity_test_edit/manage/' . $this->entity_edit->id());
$this->assertText('Devel', 'Devel tab is present');
$this->assertLinkByHref('devel_entity_test_edit/manage/' . $this->entity_edit->id() . '/devel');
$this->assertNoLinkByHref('devel_entity_test_edit/' . $this->entity_edit->id() . '/devel/render');
$this->drupalGet('devel_entity_test_edit/manage/' . $this->entity_edit->id() . '/devel');
$this->assertResponse(200);
$this->drupalGet('devel_entity_test_edit/' . $this->entity_edit->id() . '/devel/render');
$this->assertResponse(404);
// Test Devel load and render routes for entities with no route
// definitions.