a few more base modules

This commit is contained in:
Bachir Soussi Chiadmi
2016-09-06 16:05:07 +02:00
parent c6cff46234
commit 027aa99b32
455 changed files with 43606 additions and 0 deletions
@@ -0,0 +1,44 @@
<?php
namespace Drupal\devel\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a DevelDumper annotation object.
*
* @Annotation
*
* @see \Drupal\devel\DevelDumperPluginManager
* @see \Drupal\devel\DevelDumperInterface
* @see \Drupal\devel\DevelDumperBase
* @see plugin_api
*/
class DevelDumper extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The human-readable name of the DevelDumper type.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*/
public $label;
/**
* A short description of the DevelDumper type.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*/
public $description;
}
@@ -0,0 +1,235 @@
<?php
namespace Drupal\devel\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Symfony\Component\HttpFoundation\Request;
/**
* Returns responses for devel module routes.
*/
class DevelController extends ControllerBase {
/**
* Clears all caches, then redirects to the previous page.
*/
public function cacheClear() {
drupal_flush_all_caches();
drupal_set_message('Cache cleared.');
return $this->redirect('<front>');
}
/**
* Returns a dump of a route object.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* Page request object.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
*
* @return array
* A render array containing the route object.
*/
public function menuItem(Request $request, RouteMatchInterface $route_match) {
$output = [];
// Get the route object from the path query string if available.
if ($path = $request->query->get('path')) {
try {
/* @var \Symfony\Cmf\Component\Routing\ChainRouter $router */
$router = \Drupal::service('router');
$route = $router->match($path);
$output['route'] = ['#markup' => kpr($route, TRUE)];
}
catch (\Exception $e) {
drupal_set_message($this->t("Unable to load route for url '%url'", ['%url' => $path]), 'warning');
}
}
// No path specified, get the current route.
else {
$route = $route_match->getRouteObject();
$output['route'] = ['#markup' => kpr($route, TRUE)];
}
return $output;
}
public function themeRegistry() {
$hooks = theme_get_registry();
ksort($hooks);
return array('#markup' => kprint_r($hooks, TRUE));
}
/**
* Builds the elements info overview page.
*
* @return array
* Array of page elements to render.
*/
public function elementsPage() {
$element_info_manager = \Drupal::service('element_info');
$elements_info = array();
foreach ($element_info_manager->getDefinitions() as $element_type => $definition) {
$elements_info[$element_type] = $definition + $element_info_manager->getInfo($element_type);
}
ksort($elements_info);
return array('#markup' => kdevel_print_object($elements_info));
}
/**
* Builds the fields info overview page.
*
* @return array
* Array of page elements to render.
*/
public function fieldInfoPage() {
$fields = FieldStorageConfig::loadMultiple();
ksort($fields);
$output['fields'] = array('#markup' => kprint_r($fields, TRUE, $this->t('Fields')));
$field_instances = FieldConfig::loadMultiple();
ksort($field_instances);
$output['instances'] = array('#markup' => kprint_r($field_instances, TRUE, $this->t('Instances')));
$bundles = \Drupal::service('entity_type.bundle.info')->getAllBundleInfo();
ksort($bundles);
$output['bundles'] = array('#markup' => kprint_r($bundles, TRUE, $this->t('Bundles')));
$field_types = \Drupal::service('plugin.manager.field.field_type')->getUiDefinitions();
ksort($field_types);
$output['field_types'] = array('#markup' => kprint_r($field_types, TRUE, $this->t('Field types')));
$formatter_types = \Drupal::service('plugin.manager.field.formatter')->getDefinitions();
ksort($formatter_types);
$output['formatter_types'] = array('#markup' => kprint_r($formatter_types, TRUE, $this->t('Formatter types')));
$widget_types = \Drupal::service('plugin.manager.field.widget')->getDefinitions();
ksort($widget_types);
$output['widget_types'] = array('#markup' => kprint_r($widget_types, TRUE, $this->t('Widget types')));
return $output;
}
/**
* Builds the entity types overview page.
*
* @return array
* Array of page elements to render.
*/
public function entityInfoPage() {
$types = $this->entityTypeManager()->getDefinitions();
ksort($types);
return array('#markup' => kprint_r($types, TRUE));
}
/**
* Builds the state variable overview page.
*
* @return array
* Array of page elements to render.
*/
public function stateSystemPage() {
$output['#attached']['library'][] = 'system/drupal.system.modules';
$output['filters'] = array(
'#type' => 'container',
'#attributes' => array(
'class' => array('table-filter', 'js-show'),
),
);
$output['filters']['text'] = array(
'#type' => 'search',
'#title' => $this->t('Search'),
'#size' => 30,
'#placeholder' => $this->t('Enter state name'),
'#attributes' => array(
'class' => array('table-filter-text'),
'data-table' => '.devel-state-list',
'autocomplete' => 'off',
'title' => $this->t('Enter a part of the state name to filter by.'),
),
);
$can_edit = $this->currentUser()->hasPermission('administer site configuration');
$header = array(
'name' => $this->t('Name'),
'value' => $this->t('Value'),
);
if ($can_edit) {
$header['edit'] = $this->t('Operations');
}
$rows = array();
// State class doesn't have getAll method so we get all states from the
// KeyValueStorage.
foreach ($this->keyValue('state')->getAll() as $state_name => $state) {
$rows[$state_name] = array(
'name' => array(
'data' => $state_name,
'class' => 'table-filter-text-source',
),
'value' => array(
'data' => kprint_r($state, TRUE),
),
);
if ($can_edit) {
$operations['edit'] = array(
'title' => $this->t('Edit'),
'url' => Url::fromRoute('devel.system_state_edit', array('state_name' => $state_name)),
);
$rows[$state_name]['edit'] = array(
'data' => array('#type' => 'operations', '#links' => $operations),
);
}
}
$output['states'] = array(
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
'#empty' => $this->t('No state variables found.'),
'#attributes' => array(
'class' => array('devel-state-list'),
),
);
return $output;
}
/**
* Builds the session overview page.
*
* @return array
* Array of page elements to render.
*/
public function session() {
$output['description'] = array(
'#markup' => '<p>' . $this->t('Here are the contents of your $_SESSION variable.') . '</p>',
);
$output['session'] = array(
'#type' => 'table',
'#header' => array($this->t('Session name'), $this->t('Session ID')),
'#rows' => array(array(session_name(), session_id())),
'#empty' => $this->t('No session available.'),
);
$output['data'] = array(
'#markup' => kprint_r($_SESSION, TRUE),
);
return $output;
}
}
@@ -0,0 +1,122 @@
<?php
namespace Drupal\devel\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\devel\DevelDumperManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Controller for devel entity debug.
*
* @see \Drupal\devel\Routing\RouteSubscriber
* @see \Drupal\devel\Plugin\Derivative\DevelLocalTask
*/
class EntityDebugController extends ControllerBase {
/**
* The dumper service.
*
* @var \Drupal\devel\DevelDumperManagerInterface
*/
protected $dumper;
/**
* EntityDebugController constructor.
*
* @param \Drupal\devel\DevelDumperManagerInterface $dumper
* The dumper service.
*/
public function __construct(DevelDumperManagerInterface $dumper) {
$this->dumper = $dumper;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('devel.dumper'));
}
/**
* Returns the loaded structure of the current entity.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* A RouteMatch object.
*
* @return array
* Array of page elements to render.
*/
public function entityLoad(RouteMatchInterface $route_match) {
$output = [];
$entity = $this->getEntityFromRouteMatch($route_match);
if ($entity instanceof EntityInterface) {
// Field definitions are lazy loaded and are populated only when needed.
// By calling ::getFieldDefinitions() we are sure that field definitions
// are populated and available in the dump output.
// @see https://www.drupal.org/node/2311557
if($entity instanceof FieldableEntityInterface) {
$entity->getFieldDefinitions();
}
$output = $this->dumper->exportAsRenderable($entity);
}
return $output;
}
/**
* Returns the render structure of the current entity.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* A RouteMatch object.
*
* @return array
* Array of page elements to render.
*/
public function entityRender(RouteMatchInterface $route_match) {
$output = [];
$entity = $this->getEntityFromRouteMatch($route_match);
if ($entity instanceof EntityInterface) {
$entity_type_id = $entity->getEntityTypeId();
$view_hook = $entity_type_id . '_view';
$build = [];
// If module implements own {entity_type}_view() hook use it, otherwise
// fallback to the entity view builder if available.
if (function_exists($view_hook)) {
$build = $view_hook($entity);
}
elseif ($this->entityTypeManager()->hasHandler($entity_type_id, 'view_builder')) {
$build = $this->entityTypeManager()->getViewBuilder($entity_type_id)->view($entity);
}
$output = $this->dumper->exportAsRenderable($build);
}
return $output;
}
/**
* Retrieves entity from route match.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The entity object as determined from the passed-in route match.
*/
protected function getEntityFromRouteMatch(RouteMatchInterface $route_match) {
$parameter_name = $route_match->getRouteObject()->getOption('_devel_entity_type_id');
$entity = $route_match->getParameter($parameter_name);
return $entity;
}
}
@@ -0,0 +1,120 @@
<?php
namespace Drupal\devel\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Session\SessionManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
* Controller for switch to another user account.
*/
class SwitchUserController extends ControllerBase {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $account;
/**
* The user storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $userStorage;
/**
* The session manager service.
*
* @var \Drupal\Core\Session\SessionManagerInterface
*/
protected $sessionManager;
/**
* The session.
*
* @var \Symfony\Component\HttpFoundation\Session\Session
*/
protected $session;
/**
* Constructs a new SwitchUserController object
*
* @param \Drupal\Core\Session\AccountProxyInterface $account
* The current user.
* @param \Drupal\Core\Entity\EntityStorageInterface $user_storage
* The user storage.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The user storage.
* @param \Drupal\Core\Session\SessionManagerInterface $session_manager
* The session manager service.
* @param \Symfony\Component\HttpFoundation\Session\Session $session
* The session.
*/
public function __construct(AccountProxyInterface $account, EntityStorageInterface $user_storage, ModuleHandlerInterface $module_handler, SessionManagerInterface $session_manager, Session $session) {
$this->account = $account;
$this->userStorage = $user_storage;
$this->moduleHandler = $module_handler;
$this->sessionManager = $session_manager;
$this->session = $session;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('current_user'),
$container->get('entity.manager')->getStorage('user'),
$container->get('module_handler'),
$container->get('session_manager'),
$container->get('session')
);
}
/**
* Switches to a different user.
*
* We don't call session_save_session() because we really want to change users.
* Usually unsafe!
*
* @param string $name
* The username to switch to, or NULL to log out.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirect response object.
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function switchUser($name = NULL) {
if (empty($name) || !($account = $this->userStorage->loadByProperties(['name' => $name]))) {
throw new AccessDeniedHttpException();
}
$account = reset($account);
// Call logout hooks when switching from original user.
$this->moduleHandler->invokeAll('user_logout', [$this->account]);
// Regenerate the session ID to prevent against session fixation attacks.
$this->sessionManager->regenerate();
// Based off masquarade module as:
// https://www.drupal.org/node/218104 doesn't stick and instead only
// keeps context until redirect.
$this->account->setAccount($account);
$this->session->set('uid', $account->id());
// Call all login hooks when switching to masquerading user.
$this->moduleHandler->invokeAll('user_login', [$account]);
return $this->redirect('<front>');
}
}
@@ -0,0 +1,38 @@
<?php
namespace Drupal\devel;
use Drupal\Core\Render\Markup;
use Drupal\Core\Plugin\PluginBase;
/**
* Defines a base devel dumper implementation.
*
* @see \Drupal\devel\Annotation\DevelDumper
* @see \Drupal\devel\DevelDumperInterface
* @see \Drupal\devel\DevelDumperPluginManager
* @see plugin_api
*/
abstract class DevelDumperBase extends PluginBase implements DevelDumperInterface {
/**
* {@inheritdoc}
*/
public function exportAsRenderable($input, $name = NULL) {
return ['#markup' => $this->export($input, $name)];
}
/**
* Wrapper for \Drupal\Core\Render\Markup::create().
*
* @param string $input
* The input string to mark as safe.
*
* @return string
* The unaltered input value.
*/
protected function setSafeMarkup($input) {
return Markup::create($input);
}
}
@@ -0,0 +1,59 @@
<?php
namespace Drupal\devel;
/**
* Base interface definition for DevelDumper plugins.
*
* @see \Drupal\devel\Annotation\DevelDumper
* @see \Drupal\devel\DevelDumperPluginManager
* @see \Drupal\devel\DevelDumperBase
* @see plugin_api
*/
interface DevelDumperInterface {
/**
* Dumps information about a variable.
*
* @param mixed $input
* The variable to dump.
* @param string $name
* (optional) The label to output before variable, defaults to NULL.
*/
public function dump($input, $name = NULL);
/**
* Returns a string representation of a variable.
*
* @param mixed $input
* The variable to export.
* @param string $name
* (optional) The label to output before variable, defaults to NULL.
*
* @return string
* String representation of a variable.
*/
public function export($input, $name = NULL);
/**
* Returns a string representation of a variable wrapped in a render array.
*
* @param mixed $input
* The variable to export.
* @param string $name
* (optional) The label to output before variable, defaults to NULL.
*
* @return array
* String representation of a variable wrapped in a render array.
*/
public function exportAsRenderable($input, $name = NULL);
/**
* Checks if requirements for this plugin are satisfied.
*
* @return bool
* TRUE is requirements are satisfied, FALSE otherwise.
*/
public static function checkRequirements();
}
@@ -0,0 +1,142 @@
<?php
namespace Drupal\devel;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Session\AccountProxyInterface;
/**
* Class DevelDumperManager
*/
class DevelDumperManager implements DevelDumperManagerInterface {
/**
* The devel config.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected $config;
/**
* The current account.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $account;
/**
* The devel dumper plugin manager.
*
* @var \Drupal\devel\DevelDumperPluginManagerInterface
*/
protected $dumperManager;
/**
* Constructs a DevelDumperPluginManager object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
* @param \Drupal\Core\Session\AccountProxyInterface $account
* The current account.
* @param \Drupal\devel\DevelDumperPluginManagerInterface $dumper_manager
* The devel dumper plugin manager.
*/
public function __construct(ConfigFactoryInterface $config_factory, AccountProxyInterface $account, DevelDumperPluginManagerInterface $dumper_manager) {
$this->config = $config_factory->get('devel.settings');
$this->account = $account;
$this->dumperManager = $dumper_manager;
}
/**
* Instances a new dumper plugin.
*
* @param string $plugin_id
* (optional) The plugin ID, defaults to NULL.
*
* @return \Drupal\devel\DevelDumperInterface
* Returns the devel dumper plugin instance.
*/
protected function createInstance($plugin_id = NULL) {
if (!$plugin_id || !$this->dumperManager->isPluginSupported($plugin_id)) {
$plugin_id = $this->config->get('devel_dumper');
}
return $this->dumperManager->createInstance($plugin_id);
}
/**
* {@inheritdoc}
*/
public function dump($input, $name = NULL, $plugin_id = NULL) {
if ($this->hasAccessToDevelInformation()) {
$this->createInstance($plugin_id)->dump($input, $name);
}
}
/**
* {@inheritdoc}
*/
public function export($input, $name = NULL, $plugin_id = NULL) {
if ($this->hasAccessToDevelInformation()) {
return $this->createInstance($plugin_id)->export($input, $name);
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function message($input, $name = NULL, $type = 'status', $plugin_id = NULL) {
if ($this->hasAccessToDevelInformation()) {
$output = $this->export($input, $name, $plugin_id);
drupal_set_message($output, $type, TRUE);
}
}
/**
* {@inheritdoc}
*/
public function debug($input, $name = NULL, $plugin_id = NULL) {
$output = $this->createInstance($plugin_id)->export($input, $name) . "\n";
// The temp directory does vary across multiple simpletest instances.
$file = file_directory_temp() . '/drupal_debug.txt';
if (file_put_contents($file, $output, FILE_APPEND) === FALSE && $this->hasAccessToDevelInformation()) {
drupal_set_message(t('Devel was unable to write to %file.', ['%file' => $file]), 'error');
return FALSE;
}
}
/**
* {@inheritdoc}
*/
public function dumpOrExport($input, $name = NULL, $export = TRUE, $plugin_id = NULL) {
if ($this->hasAccessToDevelInformation()) {
$dumper = $this->createInstance($plugin_id);
if ($export) {
return $dumper->export($input, $name);
}
$dumper->dump($input, $name);
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function exportAsRenderable($input, $name = NULL, $plugin_id = NULL) {
if ($this->hasAccessToDevelInformation()) {
return $this->createInstance($plugin_id)->exportAsRenderable($input, $name);
}
return [];
}
/**
* Checks whether a user has access to devel information.
*
* @return bool
* TRUE if the user has the permission, FALSE otherwise.
*/
protected function hasAccessToDevelInformation() {
return $this->account && $this->account->hasPermission('access devel information');
}
}
@@ -0,0 +1,102 @@
<?php
namespace Drupal\devel;
/**
* Interface DevelDumperManagerInterface
*/
interface DevelDumperManagerInterface {
/**
* Dumps information about a variable.
*
* @param mixed $input
* The variable to dump.
* @param string $name
* (optional) The label to output before variable, defaults to NULL.
* @param string $plugin_id
* (optional) The plugin ID, defaults to NULL.
*/
public function dump($input, $name = NULL, $plugin_id = NULL);
/**
* Returns a string representation of a variable.
*
* @param mixed $input
* The variable to dump.
* @param string $name
* (optional) The label to output before variable, defaults to NULL.
* @param string $plugin_id
* (optional) The plugin ID, defaults to NULL.
*
* @return string
* String representation of a variable.
*/
public function export($input, $name = NULL, $plugin_id = NULL);
/**
* Sets a message with a string representation of a variable.
*
* @param mixed $input
* The variable to dump.
* @param string $name
* (optional) The label to output before variable, defaults to NULL.
* @param string $type
* (optional) The message's type. Defaults to 'status'.
* @param string $plugin_id
* (optional) The plugin ID, defaults to NULL.
*/
public function message($input, $name = NULL, $type = 'status', $plugin_id = NULL);
/**
* Logs a variable to a drupal_debug.txt in the site's temp directory.
*
* @param mixed $input
* The variable to log to the drupal_debug.txt log file.
* @param string $name
* (optional) If set, a label to output before $data in the log file.
* @param string $plugin_id
* (optional) The plugin ID, defaults to NULL.
*
* @return void|false
* Empty if successful, FALSE if the log file could not be written.
*
* @see dd()
* @see http://drupal.org/node/314112
*/
public function debug($input, $name = NULL, $plugin_id = NULL);
/**
* Wrapper for ::dump() and ::export().
*
* @param mixed $input
* The variable to dump.
* @param string $name
* (optional) The label to output before variable, defaults to NULL.
* @param bool $export
* (optional) Whether return string representation of a variable.
* @param string $plugin_id
* (optional) The plugin ID, defaults to NULL.
*
* @return string|null
* String representation of a variable if $export is set to TRUE,
* NULL otherwise.
*/
public function dumpOrExport($input, $name = NULL, $export = TRUE, $plugin_id = NULL);
/**
* Returns a render array representation of a variable.
*
* @param mixed $input
* The variable to export.
* @param string $name
* (optional) The label to output before variable, defaults to NULL.
* @param string $plugin_id
* (optional) The plugin ID, defaults to NULL.
*
* @return array
* String representation of a variable wrapped in a render array.
*/
public function exportAsRenderable($input, $name = NULL, $plugin_id = NULL);
}
@@ -0,0 +1,71 @@
<?php
namespace Drupal\devel;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\devel\Annotation\DevelDumper;
/**
* Plugin type manager for Devel Dumper plugins.
*
* @see \Drupal\devel\Annotation\DevelDumper
* @see \Drupal\devel\DevelDumperInterface
* @see \Drupal\devel\DevelDumperBase
* @see plugin_api
*/
class DevelDumperPluginManager extends DefaultPluginManager implements DevelDumperPluginManagerInterface {
/**
* Constructs a DevelDumperPluginManager object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct('Plugin/Devel/Dumper', $namespaces, $module_handler, DevelDumperInterface::class, DevelDumper::class);
$this->setCacheBackend($cache_backend, 'devel_dumper_plugins');
$this->alterInfo('devel_dumper_info');
}
/**
* {@inheritdoc}
*/
public function processDefinition(&$definition, $plugin_id) {
parent::processDefinition($definition, $plugin_id);
$definition['supported'] = (bool) call_user_func([$definition['class'], 'checkRequirements']);
}
/**
* {@inheritdoc}
*/
public function isPluginSupported($plugin_id) {
$definition = $this->getDefinition($plugin_id, FALSE);
return $definition && $definition['supported'];
}
/**
* {@inheritdoc}
*/
public function createInstance($plugin_id, array $configuration = []) {
if (!$this->isPluginSupported($plugin_id)) {
$plugin_id = $this->getFallbackPluginId($plugin_id);
}
return parent::createInstance($plugin_id, $configuration);
}
/**
* {@inheritdoc}
*/
public function getFallbackPluginId($plugin_id, array $configuration = []) {
return 'default';
}
}
@@ -0,0 +1,24 @@
<?php
namespace Drupal\devel;
use Drupal\Component\Plugin\FallbackPluginManagerInterface;
use Drupal\Component\Plugin\PluginManagerInterface;
/**
* Interface DevelDumperPluginManagerInterface.
*/
interface DevelDumperPluginManagerInterface extends PluginManagerInterface, FallbackPluginManagerInterface {
/**
* Checks if plugin has a definition and is supported.
*
* @param string $plugin_id
* The ID of the plugin to check.
*
* @return bool
* TRUE if the plugin is supported, FALSE otherwise.
*/
public function isPluginSupported($plugin_id);
}
@@ -0,0 +1,108 @@
<?php
namespace Drupal\devel\EventSubscriber;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class DevelEventSubscriber implements EventSubscriberInterface {
/**
* The devel.settings config object.
*
* @var \Drupal\Core\Config\Config;
*/
protected $config;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* @var \Drupal\Core\Routing\UrlGeneratorInterface
*/
protected $urlGenerator;
/**
* Constructs a DevelEventSubscriber object.
*/
public function __construct(ConfigFactoryInterface $config, AccountInterface $account, ModuleHandlerInterface $module_handler, UrlGeneratorInterface $url_generator) {
$this->config = $config->get('devel.settings');
$this->account = $account;
$this->moduleHandler = $module_handler;
$this->urlGenerator = $url_generator;
}
/**
* Register the devel error handler.
*
* @param \Symfony\Component\EventDispatcher\Event $event
* The event to process.
*/
public function registerErrorHandler(Event $event = NULL) {
if ($this->account && $this->account->hasPermission('access devel information')) {
devel_set_handler(devel_get_handlers());
}
}
/**
* Initializes devel module requirements.
*/
public function onRequest(GetResponseEvent $event) {
if ($this->config->get('rebuild_theme')) {
drupal_theme_rebuild();
// Ensure that the active theme object is cleared.
$theme_name = \Drupal::theme()->getActiveTheme()->getName();
\Drupal::state()->delete('theme.active_theme.' . $theme_name);
\Drupal::theme()->resetActiveTheme();
/** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler*/
$theme_handler = \Drupal::service('theme_handler');
$theme_handler->refreshInfo();
// @todo This is not needed after https://www.drupal.org/node/2330755
$list = $theme_handler->listInfo();
$theme_handler->addTheme($list[$theme_name]);
if (\Drupal::service('flood')->isAllowed('devel.rebuild_theme_warning', 1)) {
\Drupal::service('flood')->register('devel.rebuild_theme_warning');
if ($this->account->hasPermission('access devel information')) {
drupal_set_message(t('The theme information is being rebuilt on every request. Remember to <a href=":url">turn off</a> this feature on production websites.', array(':url' => $this->urlGenerator->generateFromRoute('devel.admin_settings'))));
}
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
// Set a low value to start as early as possible.
$events[KernelEvents::REQUEST][] = ['onRequest', -100];
// Runs as soon as possible in the request but after
// AuthenticationSubscriber (priority 300) because you need to access to
// the current user for determine whether register the devel error handler
// or not.
$events[KernelEvents::REQUEST][] = ['registerErrorHandler', 256];
return $events;
}
}
@@ -0,0 +1,146 @@
<?php
namespace Drupal\devel\Form;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Component\Serialization\Yaml;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Edit config variable form.
*/
class ConfigEditor extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'devel_config_system_edit_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $config_name = '') {
$config = $this->config($config_name);
if ($config === FALSE || $config->isNew()) {
drupal_set_message(t('Config @name does not exist in the system.', array('@name' => $config_name)), 'error');
return;
}
$data = $config->get();
if (empty($data)) {
drupal_set_message(t('Config @name exists but has no data.', array('@name' => $config_name)), 'warning');
return;
}
try {
$output = Yaml::encode($data);
}
catch (InvalidDataTypeException $e) {
drupal_set_message(t('Invalid data detected for @name : %error', array('@name' => $config_name, '%error' => $e->getMessage())), 'error');
return;
}
$form['current'] = array(
'#type' => 'details',
'#title' => $this->t('Current value for %variable', array('%variable' => $config_name)),
'#attributes' => array('class' => array('container-inline')),
);
$form['current']['value'] = array(
'#type' => 'item',
'#markup' => dpr($output, TRUE),
);
$form['name'] = array(
'#type' => 'value',
'#value' => $config_name,
);
$form['new'] = array(
'#type' => 'textarea',
'#title' => $this->t('New value'),
'#default_value' => $output,
'#rows' => 24,
'#required' => TRUE,
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save'),
);
$form['actions']['cancel'] = array(
'#type' => 'link',
'#title' => $this->t('Cancel'),
'#url' => $this->buildCancelLinkUrl(),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$value = $form_state->getValue('new');
// try to parse the new provided value
try {
$parsed_value = Yaml::decode($value);
// Config::setData needs array for the new configuration and
// a simple string is valid YAML for any reason.
if (is_array($parsed_value)) {
$form_state->setValue('parsed_value', $parsed_value);
}
else {
$form_state->setErrorByName('new', $this->t('Invalid input'));
}
}
catch (InvalidDataTypeException $e) {
$form_state->setErrorByName('new', $this->t('Invalid input: %error', array('%error' => $e->getMessage())));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$values = $form_state->getValues();
try {
$this->configFactory()->getEditable($values['name'])->setData($values['parsed_value'])->save();
drupal_set_message($this->t('Configuration variable %variable was successfully saved.', array('%variable' => $values['name'])));
$this->logger('devel')->info('Configuration variable %variable was successfully saved.', array('%variable' => $values['name']));
$form_state->setRedirectUrl(Url::fromRoute('devel.configs_list'));
}
catch (\Exception $e) {
drupal_set_message($e->getMessage(), 'error');
$this->logger('devel')->error('Error saving configuration variable %variable : %error.', array('%variable' => $values['name'], '%error' => $e->getMessage()));
}
}
/**
* Builds the cancel link url for the form.
*
* @return Url
* Cancel url
*/
private function buildCancelLinkUrl() {
$query = $this->getRequest()->query;
if ($query->has('destination')) {
$options = UrlHelper::parse($query->get('destination'));
$url = Url::fromUri('internal:/' . $options['path'], $options);
}
else {
$url = Url::fromRoute('devel.configs_list');
}
return $url;
}
}
@@ -0,0 +1,85 @@
<?php
namespace Drupal\devel\Form;
use Drupal\Component\Utility\Html;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Form that displays all the config variables to edit them.
*/
class ConfigsList extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'devel_config_system_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $filter = '') {
$form['filter'] = array(
'#type' => 'details',
'#title' => t('Filter variables'),
'#attributes' => array('class' => array('container-inline')),
'#open' => isset($filter) && trim($filter) != '',
);
$form['filter']['name'] = array(
'#type' => 'textfield',
'#title' => $this->t('Variable name'),
'#title_display' => 'invisible',
'#default_value' => $filter,
);
$form['filter']['show'] = array(
'#type' => 'submit',
'#value' => $this->t('Filter'),
);
$header = array(
'name' => array('data' => $this->t('Name')),
'edit' => array('data' => $this->t('Operations')),
);
$rows = array();
$destination = $this->getDestinationArray();
// List all the variables filtered if any filter was provided.
$names = $this->configFactory()->listAll($filter);
foreach ($names as $config_name) {
$operations['edit'] = array(
'title' => $this->t('Edit'),
'url' => Url::fromRoute('devel.config_edit', array('config_name' => $config_name)),
'query' => $destination
);
$rows[] = array(
'name' => $config_name,
'operation' => array('data' => array('#type' => 'operations', '#links' => $operations)),
);
}
$form['variables'] = array(
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
'#empty' => $this->t('No variables found')
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$filter = $form_state->getValue('name');
$form_state->setRedirectUrl(Url::FromRoute('devel.configs_list', array('filter' => Html::escape($filter))));
}
}
@@ -0,0 +1,86 @@
<?php
namespace Drupal\devel\Form;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteBuilderInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides confirmation form for rebuilding the routes.
*/
class DevelRebuildMenus extends ConfirmFormBase {
/**
* The route builder service.
*
* @var \Drupal\Core\Routing\RouteBuilderInterface
*/
protected $routeBuilder;
/**
* Constructs a new DevelRebuildMenus object.
*
* @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
* The route builder service.
*/
public function __construct(RouteBuilderInterface $route_builder) {
$this->routeBuilder = $route_builder;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('router.builder')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'devel_menu_rebuild';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Are you sure you want to rebuild menus?');
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
return new Url('<front>');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->t('Rebuild menu based on hook_menu() and revert any custom changes. All menu items return to their default settings.');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Rebuild');
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->routeBuilder->rebuild();
drupal_set_message($this->t('The menu router has been rebuilt.'));
$form_state->setRedirect('<front>');
}
}
@@ -0,0 +1,154 @@
<?php
namespace Drupal\devel\Form;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Display a dropdown of installed modules with the option to reinstall them.
*/
class DevelReinstall extends FormBase {
/**
* The module installer.
*
* @var \Drupal\Core\Extension\ModuleInstallerInterface
*/
protected $moduleInstaller;
/**
* Constructs a new DevelReinstall form.
*
* @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer
* The module installer.
*/
public function __construct(ModuleInstallerInterface $module_installer) {
$this->moduleInstaller = $module_installer;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('module_installer')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'devel_reinstall_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// Get a list of all available modules.
$modules = system_rebuild_module_data();
$uninstallable = array_filter($modules, function ($module) use ($modules) {
return empty($modules[$module->getName()]->info['required']) && drupal_get_installed_schema_version($module->getName()) > SCHEMA_UNINSTALLED && $module->getName() !== 'devel';
});
$form['filters'] = array(
'#type' => 'container',
'#attributes' => array(
'class' => array('table-filter', 'js-show'),
),
);
$form['filters']['text'] = array(
'#type' => 'search',
'#title' => $this->t('Search'),
'#size' => 30,
'#placeholder' => $this->t('Enter module name'),
'#attributes' => array(
'class' => array('table-filter-text'),
'data-table' => '#devel-reinstall-form',
'autocomplete' => 'off',
'title' => $this->t('Enter a part of the module name or description to filter by.'),
),
);
// Only build the rest of the form if there are any modules available to
// uninstall;
if (empty($uninstallable)) {
return $form;
}
$header = array(
'name' => $this->t('Name'),
'description' => $this->t('Description'),
);
$rows = array();
foreach ($uninstallable as $module) {
$name = $module->info['name'] ? : $module->getName();
$rows[$module->getName()] = array(
'name' => array(
'data' => array(
'#type' => 'inline_template',
'#template' => '<label class="module-name table-filter-text-source">{{ module_name }}</label>',
'#context' => array('module_name' => $name),
)
),
'description' => array(
'data' => $module->info['description'],
'class' => array('description'),
),
);
}
$form['reinstall'] = array(
'#type' => 'tableselect',
'#header' => $header,
'#options' => $rows,
'#js_select' => FALSE,
'#empty' => $this->t('No modules are available to uninstall.'),
);
$form['#attached']['library'][] = 'system/drupal.system.modules';
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Reinstall'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
// Form submitted, but no modules selected.
if (!array_filter($form_state->getValue('reinstall'))) {
$form_state->setErrorByName('reinstall', $this->t('No modules selected.'));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
try {
$modules = $form_state->getValue('reinstall');
$reinstall = array_keys(array_filter($modules));
$this->moduleInstaller->uninstall($reinstall, FALSE);
$this->moduleInstaller->install($reinstall, FALSE);
drupal_set_message($this->t('Uninstalled and installed: %names.', array('%names' => implode(', ', $reinstall))));
}
catch (\Exception $e) {
drupal_set_message($this->t('Unable to reinstall modules. Error: %error.', array('%error' => $e->getMessage())), 'error');
}
}
}
@@ -0,0 +1,55 @@
<?php
namespace Drupal\devel\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Defines a form that allows privileged users to execute arbitrary PHP code.
*/
class ExecutePHP extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'devel_execute_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = array(
'#title' => $this->t('Execute PHP Code'),
'#description' => $this->t('Execute some PHP code'),
);
$form['execute']['code'] = array(
'#type' => 'textarea',
'#title' => t('PHP code to execute'),
'#description' => t('Enter some code. Do not use <code>&lt;?php ?&gt;</code> tags.'),
'#default_value' => (isset($_SESSION['devel_execute_code']) ? $_SESSION['devel_execute_code'] : ''),
'#rows' => 20,
);
$form['execute']['op'] = array('#type' => 'submit', '#value' => t('Execute'));
$form['#redirect'] = FALSE;
if (isset($_SESSION['devel_execute_code'])) {
unset($_SESSION['devel_execute_code']);
}
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
ob_start();
$code = $form_state->getValue('code');
print eval($code);
$_SESSION['devel_execute_code'] = $code;
dpm(ob_get_clean());
}
}
@@ -0,0 +1,192 @@
<?php
namespace Drupal\devel\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\devel\DevelDumperPluginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Defines a form that configures devel settings.
*/
class SettingsForm extends ConfigFormBase {
/**
* Devel Dumper Plugin Manager.
*
* @var \Drupal\devel\DevelDumperPluginManager
*/
protected $dumperManager;
/**
* Constructs a new SettingsForm object.
*
* @param \Drupal\devel\DevelDumperPluginManagerInterface $devel_dumper_manager
* Devel Dumper Plugin Manager.
*/
public function __construct(DevelDumperPluginManagerInterface $devel_dumper_manager) {
$this->dumperManager = $devel_dumper_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.devel_dumper')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'devel_admin_settings_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return [
'devel.settings',
];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, Request $request = NULL) {
$current_url = Url::createFromRequest($request);
$devel_config = $this->config('devel.settings');
$form['page_alter'] = array('#type' => 'checkbox',
'#title' => t('Display $page array'),
'#default_value' => $devel_config->get('page_alter'),
'#description' => t('Display $page array from <a href="http://api.drupal.org/api/function/hook_page_alter/7">hook_page_alter()</a> in the messages area of each page.'),
);
$form['raw_names'] = array('#type' => 'checkbox',
'#title' => t('Display machine names of permissions and modules'),
'#default_value' => $devel_config->get('raw_names'),
'#description' => t('Display the language-independent machine names of the permissions in mouse-over hints on the <a href=":permissions_url">Permissions</a> page and the module base file names on the Permissions and <a href=":modules_url">Modules</a> pages.', array(':permissions_url' => Url::fromRoute('user.admin_permissions')->toString(), ':modules_url' => Url::fromRoute('system.modules_list')->toString())),
);
$error_handlers = devel_get_handlers();
$form['error_handlers'] = array(
'#type' => 'select',
'#title' => t('Error handlers'),
'#options' => array(
DEVEL_ERROR_HANDLER_NONE => t('None'),
DEVEL_ERROR_HANDLER_STANDARD => t('Standard Drupal'),
DEVEL_ERROR_HANDLER_BACKTRACE_DPM => t('Kint backtrace in the message area'),
DEVEL_ERROR_HANDLER_BACKTRACE_KINT => t('Kint backtrace above the rendered page'),
),
'#multiple' => TRUE,
'#default_value' => empty($error_handlers) ? DEVEL_ERROR_HANDLER_NONE : $error_handlers,
'#description' => [
[
'#markup' => $this->t('Select the error handler(s) to use, in case you <a href=":choose">choose to show errors on screen</a>.', [':choose' => $this->url('system.logging_settings')])
],
[
'#theme' => 'item_list',
'#items' => [
$this->t('<em>None</em> is a good option when stepping through the site in your debugger.'),
$this->t('<em>Standard Drupal</em> does not display all the information that is often needed to resolve an issue.'),
$this->t('<em>Kint backtrace</em> displays nice debug information when any type of error is noticed, but only to users with the %perm permission.', ['%perm' => t('Access developer information')]),
],
],
[
'#markup' => $this->t('Depending on the situation, the theme, the size of the call stack and the arguments, etc., some handlers may not display their messages, or display them on the subsequent page. Select <em>Standard Drupal</em> <strong>and</strong> <em>Kint backtrace above the rendered page</em> to maximize your chances of not missing any messages.') . '<br />' .
$this->t('Demonstrate the current error handler(s):') . ' ' .
$this->l('notice', $current_url->setOption('query', ['demo' => 'notice'])) . ', ' .
$this->l('notice+warning', $current_url->setOption('query', ['demo' => 'warning'])). ', ' .
$this->l('notice+warning+error', $current_url->setOption('query', ['demo' => 'error'])) . ' (' .
$this->t('The presentation of the @error is determined by PHP.', ['@error' => 'error']) . ')'
],
],
);
$form['error_handlers']['#size'] = count($form['error_handlers']['#options']);
if ($request->query->has('demo')) {
if ($request->getMethod() == 'GET') {
$this->demonstrateErrorHandlers($request->query->get('demo'));
}
$request->query->remove('demo');
}
$form['rebuild_theme'] = array(
'#type' => 'checkbox',
'#title' => t('Rebuild the theme information like the registry'),
'#description' => t('While creating new templates, change the $theme.info.yml and theme_ overrides the theme information needs to be rebuilt.'),
'#default_value' => $devel_config->get('rebuild_theme'),
);
$dumper = $devel_config->get('devel_dumper');
$default = $this->dumperManager->isPluginSupported($dumper) ? $dumper : $this->dumperManager->getFallbackPluginId(NULL);
$form['dumper'] = array(
'#type' => 'radios',
'#title' => $this->t('Variables Dumper'),
'#options' => [],
'#default_value' => $default,
'#description' => $this->t('Select the debugging tool used for formatting and displaying the variables inspected through the debug functions of Devel. You can enable the <a href=":kint_install">Kint module</a> (shipped with Devel) and select the Kint debugging tool for an improved debugging experience. <strong>NOTE</strong>: Some of these plugins require external libraries for to be enabled. Learn how install external libraries with <a href=":url">Composer</a>.', [':url' => 'https://www.drupal.org/node/2404989', ':kint_install' => Url::fromRoute('system.modules_list')->toString()]),
);
foreach ($this->dumperManager->getDefinitions() as $id => $definition) {
$form['dumper']['#options'][$id] = $definition['label'];
$supported = $this->dumperManager->isPluginSupported($id);
$form['dumper'][$id]['#disabled'] = !$supported;
$form['dumper'][$id]['#description'] = [
'#type' => 'inline_template',
'#template' => '{{ description }}{% if not supported %}<div><small>{% trans %}<strong>Not available</strong>. You may need to install external dependencies for use this plugin.{% endtrans %}</small></div>{% endif %}',
'#context' => [
'description' => $definition['description'],
'supported' => $supported,
]
];
}
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$values = $form_state->getValues();
$this->config('devel.settings')
->set('page_alter', $values['page_alter'])
->set('raw_names', $values['raw_names'])
->set('error_handlers', $values['error_handlers'])
->set('rebuild_theme', $values['rebuild_theme'])
->set('devel_dumper', $values['dumper'])
->save();
parent::submitForm($form, $form_state);
}
/**
* @param string $severity
*/
protected function demonstrateErrorHandlers($severity) {
switch ($severity) {
case 'notice':
$undefined = $undefined;
break;
case 'warning':
$undefined = $undefined;
1/0;
break;
case 'error':
$undefined = $undefined;
1/0;
devel_undefined_function();
break;
}
}
}
@@ -0,0 +1,108 @@
<?php
namespace Drupal\devel\Form;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\user\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a form that allows privileged users to generate entities.
*/
class SwitchUserForm extends FormBase {
/**
* The csrf token generator.
*
* @var \Drupal\Core\Access\CsrfTokenGenerator
*/
protected $csrfToken;
/**
* Constructs a new SwitchUserForm object.
*
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token_generator
*/
public function __construct(CsrfTokenGenerator $csrf_token_generator) {
$this->csrfToken = $csrf_token_generator;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('csrf_token')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'devel_switchuser_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['autocomplete'] = [
'#type' => 'container',
'#attributes' => [
'class' => ['container-inline'],
],
];
$form['autocomplete']['userid'] = [
'#type' => 'entity_autocomplete',
'#title' => $this->t('Username'),
'#placeholder' => $this->t('Enter username'),
'#target_type' => 'user',
'#selection_settings' => [
'include_anonymous' => FALSE
],
'#process_default_value' => FALSE,
'#maxlength' => USERNAME_MAX_LENGTH,
'#title_display' => 'invisible',
'#required' => TRUE,
'#size' => '28',
];
$form['autocomplete']['actions'] = ['#type' => 'actions'];
$form['autocomplete']['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Switch'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
if (!$account = User::load($form_state->getValue('userid'))) {
$form_state->setErrorByName('userid', $this->t('Username not found'));
}
else {
$form_state->setValue('username', $account->getAccountName());
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// We cannot rely on automatic token creation, since the csrf seed changes
// after the redirect and the generated token is not more valid.
// TODO find another way to do this.
$url = Url::fromRoute('devel.switch', ['name' => $form_state->getValue('username')]);
$url->setOption('query', ['token' => $this->csrfToken->get($url->getInternalPath())]);
$form_state->setRedirectUrl($url);
}
}
@@ -0,0 +1,189 @@
<?php
namespace Drupal\devel\Form;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\State\StateInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form API form to edit a state.
*/
class SystemStateEdit extends FormBase {
/**
* The state store.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* Constructs a new SystemStateEdit object.
*
* @param \Drupal\Core\State\StateInterface $state
* The state service.
*/
public function __construct(StateInterface $state) {
$this->state = $state;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('state')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'devel_state_system_edit_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $state_name = '') {
// Get the old value
$old_value = $this->state->get($state_name);
if (!isset($old_value)) {
drupal_set_message(t('State @name does not exist in the system.', array('@name' => $state_name)), 'warning');
return;
}
// Only simple structures are allowed to be edited.
$disabled = !$this->checkObject($old_value);
if ($disabled) {
drupal_set_message(t('Only simple structures are allowed to be edited. State @name contains objects.', array('@name' => $state_name)), 'warning');
}
// First we will show the user the content of the variable about to be edited.
$form['value'] = array(
'#type' => 'item',
'#title' => $this->t('Current value for %name', array('%name' => $state_name)),
'#markup' => kprint_r($old_value, TRUE),
);
$transport = 'plain';
if (!$disabled && is_array($old_value)) {
try {
$old_value = Yaml::encode($old_value);
$transport = 'yaml';
}
catch (InvalidDataTypeException $e) {
drupal_set_message(t('Invalid data detected for @name : %error', array('@name' => $state_name, '%error' => $e->getMessage())), 'error');
return;
}
}
// Store in the form the name of the state variable
$form['state_name'] = array(
'#type' => 'value',
'#value' => $state_name,
);
// Set the transport format for the new value. Values:
// - plain
// - yaml
$form['transport'] = array(
'#type' => 'value',
'#value' => $transport,
);
$form['new_value'] = array(
'#type' => 'textarea',
'#title' => $this->t('New value'),
'#default_value' => $disabled ? '' : $old_value,
'#disabled' => $disabled,
'#rows' => 15,
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save'),
'#disabled' => $disabled,
);
$form['actions']['cancel'] = array(
'#type' => 'link',
'#title' => $this->t('Cancel'),
'#url' => Url::fromRoute('devel.state_system_page')
);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$values = $form_state->getValues();
if ($values['transport'] == 'yaml') {
// try to parse the new provided value
try {
$parsed_value = Yaml::decode($values['new_value']);
$form_state->setValue('parsed_value', $parsed_value);
}
catch (InvalidDataTypeException $e) {
$form_state->setErrorByName('new_value', $this->t('Invalid input: %error', array('%error' => $e->getMessage())));
}
}
else {
$form_state->setValue('parsed_value', $values['new_value']);
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Save the state
$values = $form_state->getValues();
$this->state->set($values['state_name'], $values['parsed_value']);
$form_state->setRedirectUrl(Url::fromRoute('devel.state_system_page'));
drupal_set_message($this->t('Variable %variable was successfully edited.', array('%variable' => $values['state_name'])));
$this->logger('devel')->info('Variable %variable was successfully edited.', array('%variable' => $values['state_name']));
}
/**
* Helper function to determine if a variable is or contains an object.
*
* @param $data
* Input data to check
*
* @return bool
* TRUE if the variable is not an object and does not contain one.
*/
protected function checkObject($data) {
if (is_object($data)) {
return FALSE;
}
if (is_array($data)) {
// If the current object is an array, then check recursively.
foreach ($data as $value) {
// If there is an object the whole container is "contaminated"
if (!$this->checkObject($value)) {
return FALSE;
}
}
}
// All checks pass
return TRUE;
}
}
@@ -0,0 +1,33 @@
<?php
namespace Drupal\devel\Plugin\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Session\AccountInterface;
/**
* Provides a block for executing PHP code.
*
* @Block(
* id = "devel_execute_php",
* admin_label = @Translation("Execute PHP")
* )
*/
class DevelExecutePHP extends BlockBase {
/**
* {@inheritdoc}
*/
protected function blockAccess(AccountInterface $account) {
return AccessResult::allowedIfHasPermission($account, 'execute php code');
}
/**
* {@inheritdoc}
*/
public function build() {
return \Drupal::formBuilder()->getForm('Drupal\devel\Form\ExecutePHP');
}
}
@@ -0,0 +1,286 @@
<?php
namespace Drupal\devel\Plugin\Block;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Routing\RedirectDestinationTrait;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\Url;
use Drupal\user\Entity\Role;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a block for switching users.
*
* @Block(
* id = "devel_switch_user",
* admin_label = @Translation("Switch user"),
* category = @Translation("Forms")
* )
*/
class SwitchUserBlock extends BlockBase implements ContainerFactoryPluginInterface {
use RedirectDestinationTrait;
/**
* The FormBuilder object.
*
* @var \Drupal\Core\Form\FormBuilderInterface
*/
protected $formBuilder;
/**
* The Current User object.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The user storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $userStorage;
/**
* Constructs a new SwitchUserBlock object.
*
* @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\Session\AccountInterface $current_user
* Current user.
* @param \Drupal\Core\Entity\EntityStorageInterface $user_storage
* The user storage.
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
* The form builder service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, AccountInterface $current_user, EntityStorageInterface $user_storage, FormBuilderInterface $form_builder) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->formBuilder = $form_builder;
$this->currentUser = $current_user;
$this->userStorage = $user_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('current_user'),
$container->get('entity.manager')->getStorage('user'),
$container->get('form_builder')
);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'list_size' => 12,
'include_anon' => FALSE,
'show_form' => TRUE,
];
}
/**
* {@inheritdoc}
*/
public function blockAccess(AccountInterface $account) {
return AccessResult::allowedIfHasPermission($account, 'switch users');
}
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state) {
$anononymous = new AnonymousUserSession();
$form['list_size'] = [
'#type' => 'number',
'#title' => $this->t('Number of users to display in the list'),
'#default_value' => $this->configuration['list_size'],
'#min' => 1,
'#max' => 50,
];
$form['include_anon'] = [
'#type' => 'checkbox',
'#title' => $this->t('Include %anonymous', ['%anonymous' => $anononymous->getAccountName()]),
'#default_value' => $this->configuration['include_anon'],
];
$form['show_form'] = [
'#type' => 'checkbox',
'#title' => $this->t('Allow entering any user name'),
'#default_value' => $this->configuration['show_form'],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
$this->configuration['list_size'] = $form_state->getValue('list_size');
$this->configuration['include_anon'] = $form_state->getValue('include_anon');
$this->configuration['show_form'] = $form_state->getValue('show_form');
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return 0;
}
/**
* {@inheritdoc}
*/
public function build() {
$build = [];
if ($accounts = $this->getUsers()) {
$build['devel_links'] = $this->buildUserList($accounts);
if ($this->configuration['show_form']) {
$build['devel_form'] = $this->formBuilder->getForm('\Drupal\devel\Form\SwitchUserForm');
}
}
return $build;
}
/**
* Provides the list of accounts that can be used for the user switch.
*
* Inactive users are omitted from all of the following db selects. Users
* with 'switch users' permission and anonymous user if include_anon property
* is set to TRUE, are prioritized.
*
* @return \Drupal\core\Session\AccountInterface[]
* List of accounts to be used for the switch.
*/
protected function getUsers() {
$list_size = $this->configuration['list_size'];
$include_anonymous = $this->configuration['include_anon'];
$list_size = $include_anonymous ? $list_size - 1 : $list_size;
// Users with 'switch users' permission are prioritized so
// we try to load first users with this permission.
$query = $this->userStorage->getQuery()
->condition('uid', 0, '>')
->condition('status', 0, '>')
->sort('access', 'DESC')
->range(0, $list_size);
$roles = user_roles(TRUE, 'switch users');
if (!empty($roles) && !isset($roles[Role::AUTHENTICATED_ID])) {
$query->condition('roles', array_keys($roles), 'IN');
}
$user_ids = $query->execute();
// If we don't have enough users with 'switch users' permission, add
// uids until we hit $list_size.
if (count($user_ids) < $list_size) {
$query = $this->userStorage->getQuery()
->condition('uid', 0, '>')
->condition('status', 0, '>')
->sort('access', 'DESC')
->range(0, $list_size);
// Excludes the prioritized user ids only if the previous query return
// some records.
if (!empty($user_ids)) {
$query->condition('uid', array_keys($user_ids), 'NOT IN');
$query->range(0, $list_size - count($user_ids));
}
$user_ids += $query->execute();
}
$accounts = $this->userStorage->loadMultiple($user_ids);
if ($include_anonymous) {
$anonymous = new AnonymousUserSession();
$accounts[$anonymous->id()] = $anonymous;
}
uasort($accounts, 'static::sortUserList');
return $accounts;
}
/**
* Builds the user listing as renderable array.
*
* @param \Drupal\core\Session\AccountInterface[] $accounts
* The accounts to be rendered in the list.
*
* @return array
* A renderable array.
*/
protected function buildUserList(array $accounts) {
$links = [];
foreach ($accounts as $account) {
$links[$account->id()] = [
'title' => $account->getDisplayName(),
'url' => Url::fromRoute('devel.switch', ['name' => $account->getAccountName()]),
'query' => $this->getDestinationArray(),
'attributes' => [
'title' => $account->hasPermission('switch users') ? $this->t('This user can switch back.') : $this->t('Caution: this user will be unable to switch back.'),
],
];
if ($account->isAnonymous()) {
$links[$account->id()]['url'] = Url::fromRoute('user.logout');
}
if ($this->currentUser->id() === $account->id()) {
$links[$account->id()]['title'] = new FormattableMarkup('<strong>%user</strong>', ['%user' => $account->getDisplayName()]);
}
}
return [
'#theme' => 'links',
'#links' => $links,
'#attached' => ['library' => ['devel/devel']],
];
}
/**
* Helper callback for uasort() to sort accounts by last access.
*/
public static function sortUserList(AccountInterface $a, AccountInterface $b) {
$a_access = (int) $a->getLastAccessedTime();
$b_access = (int) $b->getLastAccessedTime();
if ($a_access === $b_access) {
return 0;
}
// User never access to site.
if ($a_access === 0) {
return 1;
}
return ($a_access > $b_access) ? -1 : 1;
}
}
@@ -0,0 +1,99 @@
<?php
namespace Drupal\devel\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
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.
*
* @see \Drupal\devel\Controller\EntityDebugController
* @see \Drupal\devel\Routing\RouteSubscriber
*/
class DevelLocalTask extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
/**
* The entity manager
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Creates an DevelLocalTask object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity manager.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The translation manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) {
$this->entityTypeManager = $entity_type_manager;
$this->stringTranslation = $string_translation;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity_type.manager'),
$container->get('string_translation')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$this->derivatives = array();
foreach ($this->entityTypeManager->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;
}
}
@@ -0,0 +1,40 @@
<?php
namespace Drupal\devel\Plugin\Devel\Dumper;
use Drupal\devel\DevelDumperBase;
/**
* Provides a ChromePhp dumper plugin.
*
* @DevelDumper(
* id = "chromephp",
* label = @Translation("ChromePhp"),
* description = @Translation("Wrapper for <a href='https://craig.is/writing/chrome-logger'>ChromePhp</a> debugging tool.")
* )
*/
class ChromePhp extends DevelDumperBase {
/**
* {@inheritdoc}
*/
public function dump($input, $name = NULL) {
\ChromePhp::log($input);
}
/**
* {@inheritdoc}
*/
public function export($input, $name = NULL) {
$this->dump($input);
return $this->t('Dump was redirected to the console.');
}
/**
* {@inheritdoc}
*/
public static function checkRequirements() {
return class_exists('ChromePhp', TRUE);
}
}
@@ -0,0 +1,74 @@
<?php
namespace Drupal\devel\Plugin\Devel\Dumper;
use Doctrine\Common\Util\Debug;
use Drupal\devel\DevelDumperBase;
/**
* Provides a DoctrineDebug dumper plugin.
*
* @DevelDumper(
* id = "default",
* label = @Translation("Default"),
* description = @Translation("Wrapper for <a href='http://www.doctrine-project.org/api/common/2.3/class-Doctrine.Common.Util.Debug.html'>Doctrine</a> debugging tool.")
* )
*/
class DoctrineDebug extends DevelDumperBase {
/**
* {@inheritdoc}
*/
public function dump($input, $name = NULL) {
if ($name) {
echo $name . ' => ';
}
Debug::dump($input);
}
/**
* {@inheritdoc}
*/
public function export($input, $name = NULL) {
$name = $name ? $name . ' => ' : '';
$variable = Debug::export($input, 6);
ob_start();
print_r($variable);
$dump = ob_get_contents();
ob_end_clean();
$dump = '<pre>' . $name . $dump . '</pre>';
return $this->setSafeMarkup($dump);
}
/**
* {@inheritdoc}
*/
public function exportAsRenderable($input, $name = NULL) {
$output['container'] = [
'#type' => 'details',
'#title' => $name ? : $this->t('Variable'),
'#attached' => [
'library' => ['devel/devel']
],
'#attributes' => [
'class' => ['container-inline', 'devel-dumper', 'devel-selectable'],
],
'export' => [
'#markup' => $this->export($input),
],
];
return $output;
}
/**
* {@inheritdoc}
*/
public static function checkRequirements() {
return TRUE;
}
}
@@ -0,0 +1,65 @@
<?php
namespace Drupal\devel\Plugin\Devel\Dumper;
use Drupal\Component\Utility\Variable;
use Drupal\devel\DevelDumperBase;
/**
* Provides a DrupalVariable dumper plugin.
*
* @DevelDumper(
* id = "drupal_variable",
* label = @Translation("Drupal variable."),
* description = @Translation("Wrapper for <a href='https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Component%21Utility%21Variable.php/class/Variable/8'>Drupal Variable</a> class.")
* )
*/
class DrupalVariable extends DevelDumperBase {
/**
* {@inheritdoc}
*/
public function dump($input, $name = NULL) {
$name = $name ? $name . ' => ' : '';
$output = Variable::export($input);
echo '<pre>' . $name . print_r($output) . '</pre>';
}
/**
* {@inheritdoc}
*/
public function export($input, $name = NULL) {
$name = $name ? $name . ' => ' : '';
$dump = '<pre>' . $name . Variable::export($input) . '</pre>';
return $this->setSafeMarkup($dump);
}
/**
* {@inheritdoc}
*/
public function exportAsRenderable($input, $name = NULL) {
$output['container'] = [
'#type' => 'details',
'#title' => $name ? : $this->t('Variable'),
'#attached' => [
'library' => ['devel/devel']
],
'#attributes' => [
'class' => ['container-inline', 'devel-dumper', 'devel-selectable'],
],
'export' => [
'#markup' => $this->export($input),
],
];
return $output;
}
/**
* {@inheritdoc}
*/
public static function checkRequirements() {
return TRUE;
}
}
@@ -0,0 +1,41 @@
<?php
namespace Drupal\devel\Plugin\Devel\Dumper;
use Drupal\devel\DevelDumperBase;
/**
* Provides a FirePhp dumper plugin.
*
* @DevelDumper(
* id = "firephp",
* label = @Translation("FirePhp"),
* description = @Translation("Wrapper for <a href='http://www.firephp.org'>FirePhp</a> debugging tool.")
* )
*/
class FirePhp extends DevelDumperBase {
/**
* {@inheritdoc}
*/
public function dump($input, $name = NULL) {
$fb = new \FB();
$fb->dump($name, $input);
}
/**
* {@inheritdoc}
*/
public function export($input, $name = NULL) {
$this->dump($input);
return $this->t('Dump was redirected to the console.');
}
/**
* {@inheritdoc}
*/
public static function checkRequirements() {
return class_exists('FirePHP', TRUE);
}
}
@@ -0,0 +1,50 @@
<?php
namespace Drupal\devel\Plugin\Devel\Dumper;
use Drupal\devel\DevelDumperBase;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
/**
* Provides a Symfony VarDumper dumper plugin.
*
* @DevelDumper(
* id = "var_dumper",
* label = @Translation("Symfony var-dumper"),
* description = @Translation("Wrapper for <a href='https://github.com/symfony/var-dumper'>Symfony var-dumper</a> debugging tool."),
* )
*
*/
class VarDumper extends DevelDumperBase {
/**
* {@inheritdoc}
*/
public function dump($input, $name = NULL) {
echo (string) $this->export($input, $name);
}
/**
* {@inheritdoc}
*/
public function export($input, $name = NULL) {
$cloner = new VarCloner();
$dumper = 'cli' === PHP_SAPI ? new CliDumper() : new HtmlDumper();
$output = fopen('php://memory', 'r+b');
$dumper->dump($cloner->cloneVar($input), $output);
$output = stream_get_contents($output, -1, 0);
return $this->setSafeMarkup($output);
}
/**
* {@inheritdoc}
*/
public static function checkRequirements() {
return class_exists('Symfony\Component\VarDumper\Cloner\VarCloner', TRUE);
}
}
@@ -0,0 +1,98 @@
<?php
namespace Drupal\devel\Plugin\Mail;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Mail\Plugin\Mail\PhpMail;
use Drupal\Core\Site\Settings;
use Exception;
/**
* Defines a mail backend that saves emails as temporary files.
*
* To enable, save a variable in settings.php (or otherwise) whose value
* can be as simple as:
* @code
* $config['system.mail']['interface']['default'] = 'devel_mail_log';
* @endcode
*
* By default the mails are saved in 'temporary://devel-mails'. This setting
* can be changed using 'debug_mail_directory' config setting. For example,
* @code
* $config['devel.settings']['debug_mail_directory'] = 'temporary://my-directory';
* @endcode
*
* The default filename pattern used is '%to-%subject-%datetime.mail.txt'. This
* setting can be changed using 'debug_mail_directory' config setting. For example,
* @code
* $config['devel.settings']['debug_mail_file_format'] = 'devel-mail-%to-%subject-%datetime.mail.txt';
* @endcode
*
* The following placeholders can be used in the filename pattern:
* - %to: the email recipient.
* - %subject: the email subject.
* - %datetime: the current datetime in 'y-m-d_his' format.
*
* @Mail(
* id = "devel_mail_log",
* label = @Translation("Devel Logging Mailer"),
* description = @Translation("Outputs the message as a file in the temporary directory.")
* )
*/
class DevelMailLog extends PhpMail {
public function composeMessage($message) {
$mimeheaders = array();
$message['headers']['To'] = $message['to'];
foreach ($message['headers'] as $name => $value) {
$mimeheaders[] = $name . ': ' . Unicode::mimeHeaderEncode($value);
}
$line_endings = Settings::get('mail_line_endings', PHP_EOL);
$output = join($line_endings, $mimeheaders) . $line_endings;
// 'Subject:' is a mail header and should not be translated.
$output .= 'Subject: ' . $message['subject'] . $line_endings;
// Blank line to separate headers from body.
$output .= $line_endings;
$output .= preg_replace('@\r?\n@', $line_endings, $message['body']);
return $output;
}
public function getFileName($message) {
$output_directory = $this->getOutputDirectory();
$this->makeOutputDirectory($output_directory);
$output_file_format = \Drupal::config('devel.settings')->get('debug_mail_file_format');
$tokens = array(
'%to' => $message['to'],
'%subject' => $message['subject'],
'%datetime' => date('y-m-d_his'),
);
return $output_directory . '/' . $this->dirify(str_replace(array_keys($tokens), array_values($tokens), $output_file_format));
}
private function dirify($string) {
return preg_replace('/[^a-zA-Z0-9_\-\.@]/', '_', $string);
}
/**
* {@inheritdoc}
*/
public function mail(array $message) {
$output = $this->composeMessage($message);
$output_file = $this->getFileName($message);
return file_put_contents($output_file, $output);
}
protected function makeOutputDirectory($output_directory) {
if (!file_prepare_directory($output_directory, FILE_CREATE_DIRECTORY)) {
throw new Exception("Unable to continue sending mail, $output_directory is not writable");
}
}
public function getOutputDirectory() {
return \Drupal::config('devel.settings')->get('debug_mail_directory');
}
}
@@ -0,0 +1,32 @@
<?php
namespace Drupal\devel\Plugin\Menu;
use Drupal\Core\Menu\MenuLinkDefault;
use Drupal\Core\Url;
/**
* Modifies the menu link to add destination.
*/
class DestinationMenuLink extends MenuLinkDefault {
/**
* {@inheritdoc}
*/
public function getOptions() {
$options = parent::getOptions();
// Append the current path as destination to the query string.
$options['query']['destination'] = Url::fromRoute('<current>')->toString();
return $options;
}
/**
* {@inheritdoc}
*
* @todo Make cacheable once https://www.drupal.org/node/2582797 lands.
*/
public function getCacheMaxAge() {
return 0;
}
}
@@ -0,0 +1,29 @@
<?php
namespace Drupal\devel\Plugin\Menu;
use Drupal\Core\Menu\MenuLinkDefault;
use Drupal\Core\Url;
/**
* Modifies the menu link to add current route path.
*/
class MenuItemMenuLink extends MenuLinkDefault {
/**
* {@inheritdoc}
*/
public function getOptions() {
$options = parent::getOptions();
$options['query']['path'] = '/' . Url::fromRoute('<current>')->getInternalPath();
return $options;
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return 0;
}
}
@@ -0,0 +1,122 @@
<?php
namespace Drupal\devel\Routing;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Subscriber for Devel routes.
*
* @see \Drupal\devel\Controller\EntityDebugController
* @see \Drupal\devel\Plugin\Derivative\DevelLocalTask
*/
class RouteSubscriber extends RouteSubscriberBase {
/**
* The entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new RouteSubscriber object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_manager) {
$this->entityTypeManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
if ($route = $this->getEntityLoadRoute($entity_type)) {
$collection->add("entity.$entity_type_id.devel_load", $route);
}
if ($route = $this->getEntityRenderRoute($entity_type)) {
$collection->add("entity.$entity_type_id.devel_render", $route);
}
}
}
/**
* Gets the devel load route.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
* @return \Symfony\Component\Routing\Route|null
* The generated route, if available.
*/
protected function getEntityLoadRoute(EntityTypeInterface $entity_type) {
if ($devel_load = $entity_type->getLinkTemplate('devel-load')) {
$entity_type_id = $entity_type->id();
$route = new Route($devel_load);
$route
->addDefaults([
'_controller' => '\Drupal\devel\Controller\EntityDebugController::entityLoad',
'_title' => 'Devel Load',
])
->addRequirements([
'_permission' => 'access devel information',
])
->setOption('_admin_route', TRUE)
->setOption('_devel_entity_type_id', $entity_type_id)
->setOption('parameters', [
$entity_type_id => ['type' => 'entity:' . $entity_type_id],
]);
return $route;
}
}
/**
* Gets the devel render route.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
* @return \Symfony\Component\Routing\Route|null
* The generated route, if available.
*/
protected function getEntityRenderRoute(EntityTypeInterface $entity_type) {
if ($devel_render = $entity_type->getLinkTemplate('devel-render')) {
$entity_type_id = $entity_type->id();
$route = new Route($devel_render);
$route
->addDefaults([
'_controller' => '\Drupal\devel\Controller\EntityDebugController::entityRender',
'_title' => 'Devel Render',
])
->addRequirements([
'_permission' => 'access devel information'
])
->setOption('_admin_route', TRUE)
->setOption('_devel_entity_type_id', $entity_type_id)
->setOption('parameters', [
$entity_type_id => ['type' => 'entity:' . $entity_type_id],
]);
return $route;
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events = parent::getSubscribedEvents();
$events[RoutingEvents::ALTER] = ['onAlterRoutes', 100];
return $events;
}
}
@@ -0,0 +1,109 @@
<?php
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_entity_test', 'block');
/**
* {@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();
$this->drupalPlaceBlock('local_tasks_block');
$web_user = $this->drupalCreateUser(array(
'view test entity',
'administer entity_test content',
'access devel information',
));
$this->drupalLogin($web_user);
}
function testRouteGeneration() {
// 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('devel/entity_test/' . $this->entity->id());
$this->assertResponse(200);
$this->assertText('Load', 'Devel load tab is present');
$this->assertText('Render', 'Devel load tab is present');
$this->assertLinkByHref('devel/entity_test/' . $this->entity->id() . '/render');
$this->drupalGet('devel/entity_test/' . $this->entity->id() . '/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');
//TODO this fail since assertNoLinkByHref search by partial value.
//$this->assertNoLinkByHref('devel/devel_entity_test_canonical/' . $this->entity_canonical->id());
$this->assertLinkByHref('devel/devel_entity_test_canonical/' . $this->entity_canonical->id() . '/render');
$this->drupalGet('devel/devel_entity_test_canonical/' . $this->entity_canonical->id());
$this->assertResponse(404);
$this->drupalGet('devel/devel_entity_test_canonical/' . $this->entity_canonical->id() . '/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/devel_entity_test_edit/' . $this->entity_edit->id());
$this->assertNoLinkByHref('devel/devel_entity_test_edit/' . $this->entity_edit->id() . '/render');
$this->drupalGet('devel/devel_entity_test_edit/' . $this->entity_edit->id());
$this->assertResponse(200);
$this->drupalGet('devel/devel_entity_test_edit/' . $this->entity_edit->id() . '/render');
$this->assertResponse(404);
// Test Devel load and render routes for entities with no route
// definitions.
$this->drupalGet('devel_entity_test_no_links/' . $this->entity_edit->id());
$this->assertNoText('Devel', 'Devel tab is not present');
$this->assertNoLinkByHref('devel/devel_entity_test_no_links/' . $this->entity_no_links->id());
$this->assertNoLinkByHref('devel/devel_entity_test_no_links/' . $this->entity_no_links->id() . '/render');
$this->drupalGet('devel/devel_entity_test_no_links/' . $this->entity_no_links->id());
$this->assertResponse(404);
$this->drupalGet('devel/devel_entity_test_no_links/' . $this->entity_no_links->id() . '/render');
$this->assertResponse(404);
}
}
@@ -0,0 +1,159 @@
<?php
namespace Drupal\devel\Tests;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\simpletest\WebTestBase;
/**
* Tests pluggable dumper feature.
*
* @group devel
*/
class DevelDumperTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['devel', 'devel_dumper_test'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$admin_user = $this->drupalCreateUser(['administer site configuration', 'access devel information']);
$this->drupalLogin($admin_user);
}
/**
* Test dumpers configuration page.
*/
public function testDumpersConfiguration() {
$this->drupalGet('admin/config/development/devel');
// Ensures that the dumper input is present on the config page.
$this->assertFieldByName('dumper');
// Ensures that the 'default' dumper is enabled by default.
$this->assertFieldChecked('edit-dumper-default');
// Ensures that all dumpers declared by devel are present on the config page
// and that only the available dumpers are selectable.
$dumpers = ['default', 'drupal_variable', 'firephp', 'chromephp', 'var_dumper'];
$available_dumpers = ['default', 'drupal_variable'];
foreach ($dumpers as $dumper) {
$this->assertFieldByXPath('//input[@type="radio" and @name="dumper"]', $dumper, new FormattableMarkup('Radio button for @dumper found.', ['@dumper' => $dumper]));
if (in_array($dumper, $available_dumpers)) {
$this->assertFieldByXPath('//input[@name="dumper" and not(@disabled="disabled")]', $dumper, new FormattableMarkup('Dumper @dumper is available.', ['@dumper' => $dumper]));
}
else {
$this->assertFieldByXPath('//input[@name="dumper" and @disabled="disabled"]', $dumper, new FormattableMarkup('Dumper @dumper is disabled.', ['@dumper' => $dumper]));
}
}
// Ensures that dumper plugins declared by other modules are present on the
// config page and that only the available dumpers are selectable.
$this->assertFieldByName('dumper', 'available_test_dumper');
$this->assertText('Available test dumper.', 'Available dumper label is present');
$this->assertText('Drupal dumper for testing purposes (available).', 'Available dumper description is present');
$this->assertFieldByXPath('//input[@name="dumper" and not(@disabled="disabled")]', 'available_test_dumper', 'Available dumper input not is disabled.');
$this->assertFieldByName('dumper', 'not_available_test_dumper');
$this->assertText('Not available test dumper.', 'Non available dumper label is present');
$this->assertText('Drupal dumper for testing purposes (not available).Not available. You may need to install external dependencies for use this plugin.', 'Non available dumper description is present');
$this->assertFieldByXPath('//input[@name="dumper" and @disabled="disabled"]', 'not_available_test_dumper', 'Non available dumper input is disabled.');
// Ensures that saving of the dumpers configuration works as expected.
$edit = [
'dumper' => 'drupal_variable',
];
$this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
$this->assertText(t('The configuration options have been saved.'));
$config = \Drupal::config('devel.settings')->get('devel_dumper');
$this->assertEqual('drupal_variable', $config, 'The configuration options have been properly saved');
// Ensure that if the chosen dumper is not available (e.g. the module that
// provide it is uninstalled) the 'default' dumper appears selected in the
// config page.
\Drupal::service('module_installer')->install(['kint']);
$this->drupalGet('admin/config/development/devel');
$this->assertFieldByName('dumper', 'kint');
$edit = [
'dumper' => 'kint',
];
$this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
$this->assertText(t('The configuration options have been saved.'));
$config = \Drupal::config('devel.settings')->get('devel_dumper');
$this->assertEqual('kint', $config, 'The configuration options have been properly saved');
\Drupal::service('module_installer')->uninstall(['kint']);
$this->drupalGet('admin/config/development/devel');
$this->assertNoFieldByName('dumper', 'kint');
$this->assertFieldChecked('edit-dumper-default');
}
/**
* Test variable is dumped in page.
*/
function testDumpersOutput() {
$edit = [
'dumper' => 'available_test_dumper',
];
$this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
$this->assertText(t('The configuration options have been saved.'));
$this->drupalGet('devel_dumper_test/dump');
$elements = $this->xpath('//body/pre[contains(text(), :message)]', [':message' => 'AvailableTestDumper::dump() Test output']);
$this->assertTrue(!empty($elements), 'Dumped message is present.');
$this->drupalGet('devel_dumper_test/message');
$elements = $this->xpath('//div[contains(@class, "messages")]/pre[contains(text(), :message)]', [':message' => 'AvailableTestDumper::export() Test output']);
$this->assertTrue(!empty($elements), 'Dumped message is present.');
$this->drupalGet('devel_dumper_test/export');
$elements = $this->xpath('//div[@class="layout-content"]//pre[contains(text(), :message)]', [':message' => 'AvailableTestDumper::export() Test output']);
$this->assertTrue(!empty($elements), 'Dumped message is present.');
$this->drupalGet('devel_dumper_test/export_renderable');
$elements = $this->xpath('//div[@class="layout-content"]//pre[contains(text(), :message)]', [':message' => 'AvailableTestDumper::exportAsRenderable() Test output']);
$this->assertTrue(!empty($elements), 'Dumped message is present.');
// Ensures that plugins can add libraries to the page when the
// ::exportAsRenderable() method is used.
$this->assertRaw('devel_dumper_test/css/devel_dumper_test.css');
$this->assertRaw('devel_dumper_test/js/devel_dumper_test.js');
$debug_filename = file_directory_temp() . '/drupal_debug.txt';
$this->drupalGet('devel_dumper_test/debug');
$file_content = file_get_contents($debug_filename);
$expected = <<<EOF
<pre>AvailableTestDumper::export() Test output</pre>
EOF;
$this->assertEqual($file_content, $expected, 'Dumped message is present.');
// Ensures that the DevelDumperManager::debug() is not access checked and
// that the dump is written in the debug file even if the user has not the
// 'access devel information' permission.
file_put_contents($debug_filename, '');
$this->drupalLogout();
$this->drupalGet('devel_dumper_test/debug');
$file_content = file_get_contents($debug_filename);
$expected = <<<EOF
<pre>AvailableTestDumper::export() Test output</pre>
EOF;
$this->assertEqual($file_content, $expected, 'Dumped message is present.');
}
}
@@ -0,0 +1,213 @@
<?php
namespace Drupal\devel\Tests;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\simpletest\WebTestBase;
/**
* Tests devel error handler.
*
* @group devel
*/
class DevelErrorHandlerTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['devel'];
/**
* Tests devel error handler.
*/
public function testErrorHandler() {
$error_notice = [
'%type' => 'Notice',
'@message' => 'Undefined variable: undefined',
'%function' => 'Drupal\devel\Form\SettingsForm->demonstrateErrorHandlers()',
];
$error_warning = [
'%type' => 'Warning',
'@message' => 'Division by zero',
'%function' => 'Drupal\devel\Form\SettingsForm->demonstrateErrorHandlers()',
];
$config = $this->config('system.logging');
$config->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
$admin_user = $this->drupalCreateUser(['administer site configuration', 'access devel information']);
$this->drupalLogin($admin_user);
// Ensures that the error handler config is present on the config page and
// by default the standard error handler is selected.
$error_handlers = \Drupal::config('devel.settings')->get('error_handlers');
$this->assertEqual($error_handlers, [DEVEL_ERROR_HANDLER_STANDARD => DEVEL_ERROR_HANDLER_STANDARD]);
$this->drupalGet('admin/config/development/devel');
$this->assertOptionSelected('edit-error-handlers', DEVEL_ERROR_HANDLER_STANDARD);
// Ensures that selecting the DEVEL_ERROR_HANDLER_NONE option no error
// (raw or message) is shown on the site in case of php errors.
$edit = [
'error_handlers[]' => DEVEL_ERROR_HANDLER_NONE,
];
$this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
$this->assertText(t('The configuration options have been saved.'));
$error_handlers = \Drupal::config('devel.settings')->get('error_handlers');
$this->assertEqual($error_handlers, [DEVEL_ERROR_HANDLER_NONE => DEVEL_ERROR_HANDLER_NONE]);
$this->assertOptionSelected('edit-error-handlers', DEVEL_ERROR_HANDLER_NONE);
$this->clickLink('notice+warning');
$this->assertResponse(200, 'Received expected HTTP status code.');
$this->assertNoRawErrorMessage($error_notice);
$this->assertNoRawErrorMessage($error_warning);
$this->assertNoErrorMessage($error_notice);
$this->assertNoErrorMessage($error_warning);
// Ensures that selecting the DEVEL_ERROR_HANDLER_BACKTRACE_KINT option a
// backtrace above the rendered page is shown on the site in case of php
// errors.
$edit = [
'error_handlers[]' => DEVEL_ERROR_HANDLER_BACKTRACE_KINT,
];
$this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
$this->assertText(t('The configuration options have been saved.'));
$error_handlers = \Drupal::config('devel.settings')->get('error_handlers');
$this->assertEqual($error_handlers, [DEVEL_ERROR_HANDLER_BACKTRACE_KINT => DEVEL_ERROR_HANDLER_BACKTRACE_KINT]);
$this->assertOptionSelected('edit-error-handlers', DEVEL_ERROR_HANDLER_BACKTRACE_KINT);
$this->clickLink('notice+warning');
$this->assertResponse(200, 'Received expected HTTP status code.');
$this->assertRawErrorMessage($error_notice);
$this->assertRawErrorMessage($error_warning);
// Ensures that selecting the DEVEL_ERROR_HANDLER_BACKTRACE_DPM option a
// backtrace in the message area is shown on the site in case of php errors.
$edit = [
'error_handlers[]' => DEVEL_ERROR_HANDLER_BACKTRACE_DPM,
];
$this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
$this->assertText(t('The configuration options have been saved.'));
$error_handlers = \Drupal::config('devel.settings')->get('error_handlers');
$this->assertEqual($error_handlers, [DEVEL_ERROR_HANDLER_BACKTRACE_DPM => DEVEL_ERROR_HANDLER_BACKTRACE_DPM]);
$this->assertOptionSelected('edit-error-handlers', DEVEL_ERROR_HANDLER_BACKTRACE_DPM);
$this->clickLink('notice+warning');
$this->assertResponse(200, 'Received expected HTTP status code.');
$this->assertErrorMessage($error_notice);
$this->assertErrorMessage($error_warning);
// Ensures that when multiple handlers are selected, the output produced by
// every handler is shown on the site in case of php errors.
$edit = [
'error_handlers[]' => [
DEVEL_ERROR_HANDLER_BACKTRACE_DPM => DEVEL_ERROR_HANDLER_BACKTRACE_DPM,
DEVEL_ERROR_HANDLER_BACKTRACE_KINT => DEVEL_ERROR_HANDLER_BACKTRACE_KINT,
]
];
$this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
$this->assertText(t('The configuration options have been saved.'));
$error_handlers = \Drupal::config('devel.settings')->get('error_handlers');
$this->assertEqual($error_handlers, [
DEVEL_ERROR_HANDLER_BACKTRACE_DPM => DEVEL_ERROR_HANDLER_BACKTRACE_DPM,
DEVEL_ERROR_HANDLER_BACKTRACE_KINT => DEVEL_ERROR_HANDLER_BACKTRACE_KINT,
]);
$this->assertOptionSelected('edit-error-handlers', DEVEL_ERROR_HANDLER_BACKTRACE_DPM);
$this->assertOptionSelected('edit-error-handlers', DEVEL_ERROR_HANDLER_BACKTRACE_KINT);
$this->clickLink('notice+warning');
$this->assertResponse(200, 'Received expected HTTP status code.');
$this->assertRawErrorMessage($error_notice);
$this->assertRawErrorMessage($error_warning);
$this->assertErrorMessage($error_notice);
$this->assertErrorMessage($error_warning);
// Ensures that setting the error reporting to all the output produced by
// handlers is shown on the site in case of php errors.
$config->set('error_level', ERROR_REPORTING_DISPLAY_ALL)->save();
$this->clickLink('notice+warning');
$this->assertResponse(200, 'Received expected HTTP status code.');
$this->assertRawErrorMessage($error_notice);
$this->assertRawErrorMessage($error_warning);
$this->assertErrorMessage($error_notice);
$this->assertErrorMessage($error_warning);
// Ensures that setting the error reporting to some the output produced by
// handlers is shown on the site in case of php errors.
$config->set('error_level', ERROR_REPORTING_DISPLAY_SOME)->save();
$this->assertResponse(200, 'Received expected HTTP status code.');
$this->clickLink('notice+warning');
$this->assertRawErrorMessage($error_notice);
$this->assertRawErrorMessage($error_warning);
$this->assertErrorMessage($error_notice);
$this->assertErrorMessage($error_warning);
// Ensures that setting the error reporting to none the output produced by
// handlers is not shown on the site in case of php errors.
$config->set('error_level', ERROR_REPORTING_HIDE)->save();
$this->clickLink('notice+warning');
$this->assertResponse(200, 'Received expected HTTP status code.');
$this->assertNoRawErrorMessage($error_notice);
$this->assertNoRawErrorMessage($error_warning);
$this->assertNoErrorMessage($error_notice);
$this->assertNoErrorMessage($error_warning);
// The errors are expected. Do not interpret them as a test failure.
// Not using File API; a potential error must trigger a PHP warning.
unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
}
/**
* Helper function: assert that the error message is found.
*
* @param array $error
* The error to check.
*/
protected function assertRawErrorMessage(array $error) {
$message = new FormattableMarkup('%type: @message in %function (line ', $error);
$this->assertRaw($message, new FormattableMarkup('Found raw error message: @message.', ['@message' => $message]));
}
/**
* Helper function: assert that the error message is not found.
*
*
* @param array $error
* The error to check.
*/
protected function assertNoRawErrorMessage(array $error) {
$message = new FormattableMarkup('%type: @message in %function (line ', $error);
$this->assertNoRaw($message, new FormattableMarkup('Did not find raw error message: @message.', ['@message' => $message]));
}
/**
* Helper function: assert that the error message is found.
*
* @param array $error
* The error to check.
*/
protected function assertErrorMessage(array $error) {
$pattern = '//div[contains(@class, "messages--warning")]//pre[contains(., :content)]';
$message = new FormattableMarkup('%type: @message in %function (line ', $error);
$message = html_entity_decode(strip_tags((string) $message));
$xpath = $this->xpath($pattern, [':content' => $message]);
$this->assertTrue(!empty($xpath), new FormattableMarkup('Found error message: @message.', ['@message' => $message]));
}
/**
* Helper function: assert that the error message is not found.
*
* @param array $error
* The error to check.
*/
protected function assertNoErrorMessage(array $error) {
$pattern = '//div[contains(@class, "messages--warning")]//pre[contains(., :content)]';
$message = new FormattableMarkup('%type: @message in %function (line ', $error);
$message = html_entity_decode(strip_tags((string) $message));
$xpath = $this->xpath($pattern, [':content' => $message]);
$this->assertTrue(empty($xpath), new FormattableMarkup('Found error message: @message.', ['@message' => $message]));
}
}
@@ -0,0 +1,48 @@
<?php
namespace Drupal\devel\Tests;
use Drupal\devel\Plugin\Mail\DevelMailLog;
use Drupal\simpletest\WebTestBase;
/**
* Tests sending mails with debug interface.
*
* @group devel
*/
class DevelMailTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('devel');
protected $profile = 'testing';
/**
* Test mail logging functionality.
*/
public function testDevelMail() {
$message = array();
$message['to'] = 'drupal@example.com';
$message['subject'] = 'Test mail';
$message['headers'] = array(
'From' => 'postmaster@example.com',
'X-stupid' => 'dumb',
);
$message['body'] = "I am the body of this message";
$d = new DevelMailLog();
$filename = $d->getFileName($message);
$content = $d->composeMessage($message);
$expected_filename = $d->getOutputDirectory() . '/drupal@example.com-Test_mail-' . date('y-m-d_his') . '.mail.txt';
$this->assertEqual($filename, $expected_filename);
$content = str_replace("\r", '', $content);
$this->assertEqual($content, 'From: postmaster@example.com
X-stupid: dumb
To: drupal@example.com
Subject: Test mail
I am the body of this message');
}
}
@@ -0,0 +1,147 @@
<?php
namespace Drupal\devel\Tests;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
/**
* Tests devel menu links.
*
* @group devel
*/
class DevelMenuLinksTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['devel', 'block', 'devel_test'];
/**
* The user for tests.
*
* @var \Drupal\user\UserInterface
*/
protected $develUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Devel links currently appears only in the devel menu.
// Place the devel menu block so we can ensure that these link works
// properly.
$this->drupalPlaceBlock('system_menu_block:devel');
$this->drupalPlaceBlock('page_title_block');
$this->develUser = $this->drupalCreateUser(['access devel information', 'administer site configuration']);
$this->drupalLogin($this->develUser);
}
/**
* Tests CSFR protected links.
*/
public function testCsrfProtectedLinks() {
// Ensure CSRF link are not accessible directly.
$this->drupalGet('devel/run-cron');
$this->assertResponse(403);
$this->drupalGet('devel/cache/clear');
$this->assertResponse(403);
// Ensure clear cache link works properly.
$this->assertLink('Cache clear');
$this->clickLink('Cache clear');
$this->assertText('Cache cleared.');
// Ensure run cron link works properly.
$this->assertLink('Run cron');
$this->clickLink('Run cron');
$this->assertText('Cron ran successfully.');
// Ensure CSRF protected links work properly after change session.
$this->drupalLogout();
$this->drupalLogin($this->develUser);
$this->assertLink('Cache clear');
$this->clickLink('Cache clear');
$this->assertText('Cache cleared.');
$this->assertLink('Run cron');
$this->clickLink('Run cron');
$this->assertText('Cron ran successfully.');
}
/**
* Tests redirect destination links.
*/
public function testRedirectDestinationLinks() {
// By default, in the testing profile, front page is the user canonical URI.
// For better testing do not use the default frontpage.
$url = Url::fromRoute('devel.simple_page');
$destination = Url::fromRoute('devel.simple_page', [], ['absolute' => FALSE]);
$this->drupalGet($url);
$this->assertLink(t('Reinstall Modules'));
$this->clickLink(t('Reinstall Modules'));
$this->assertUrl('devel/reinstall', ['query' => ['destination' => $destination->toString()]]);
$this->drupalGet($url);
$this->assertLink(t('Rebuild Menu'));
$this->clickLink(t('Rebuild Menu'));
$this->assertUrl('devel/menu/reset', ['query' => ['destination' => $destination->toString()]]);
$this->drupalGet($url);
$this->assertLink(t('Cache clear'));
$this->clickLink(t('Cache clear'));
$this->assertText('Cache cleared.');
$this->assertUrl($url);
$this->drupalGet($url);
$this->assertLink(t('Run cron'));
$this->clickLink(t('Run cron'));
$this->assertText(t('Cron ran successfully.'));
$this->assertUrl($url);
}
/**
* Tests menu item link.
*/
public function testMenuItemLink() {
// Ensures that devel menu item works properly.
$url = $this->develUser->toUrl();
$path = '/' . $url->getInternalPath();
$this->drupalGet($url);
$this->clickLink(t('Menu Item'));
$this->assertResponse(200);
$this->assertText('Menu item');
$this->assertUrl('devel/menu/item', ['query' => ['path' => $path]]);
// Ensures that devel menu item works properly even when dynamic cache is
// enabled.
$url = Url::fromRoute('devel.simple_page');
$path = '/' . $url->getInternalPath();
$this->drupalGet($url);
$this->clickLink(t('Menu Item'));
$this->assertResponse(200);
$this->assertText('Menu item');
$this->assertUrl('devel/menu/item', ['query' => ['path' => $path]]);
// Ensures that if no 'path' query string is passed devel menu item does
// not return errors.
$this->drupalGet('devel/menu/item');
$this->assertResponse(200);
$this->assertText('Menu item');
// Ensures that devel menu item is accessible ony to users with the
// adequate permissions.
$this->drupalLogout();
$this->drupalGet('devel/menu/item');
$this->assertResponse(403);
}
}
@@ -0,0 +1,41 @@
<?php
namespace Drupal\devel\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests routes rebuild.
*
* @group devel
*/
class DevelRebuildMenusTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('devel');
/**
* Set up test.
*/
protected function setUp() {
parent::setUp();
$web_user = $this->drupalCreateUser(array('administer site configuration'));
$this->drupalLogin($web_user);
}
/**
* Test routes rebuild.
*/
public function testDevelRebuildMenus() {
$this->drupalGet('devel/menu/reset');
$this->assertResponse(200);
$this->drupalPostForm('devel/menu/reset', array(), t('Rebuild'));
$this->assertText(t('The menu router has been rebuilt.'));
}
}
@@ -0,0 +1,60 @@
<?php
namespace Drupal\devel\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests reinstall modules.
*
* @group devel
*/
class DevelReinstallTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('devel');
/**
* The profile to install as a basis for testing.
*
* @var string
*/
protected $profile = 'minimal';
/**
* Set up test.
*/
protected function setUp() {
parent::setUp();
$web_user = $this->drupalCreateUser(array('administer site configuration'));
$this->drupalLogin($web_user);
}
/**
* Reinstall modules.
*/
public function testDevelReinstallModules() {
// Minimal profile enables only dblog, block and node.
$modules = array('dblog', 'block');
// Needed for compare correctly the message.
sort($modules);
$this->drupalGet('devel/reinstall');
// Prepare field data in an associative array
$edit = array();
foreach ($modules as $module) {
$edit["reinstall[$module]"] = TRUE;
}
$this->drupalPostForm('devel/reinstall', $edit, t('Reinstall'));
$this->assertText(t('Uninstalled and installed: @names.', array('@names' => implode(', ', $modules))));
}
}
@@ -0,0 +1,210 @@
<?php
namespace Drupal\devel\Tests;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\simpletest\WebTestBase;
/**
* Tests devel state editor.
*
* @group devel
*/
class DevelStateEditorTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['devel'];
/**
* The state store.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* The user for tests.
*
* @var \Drupal\user\UserInterface
*/
protected $develUser;
/**
* The user for tests.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->state = $this->container->get('state');
$this->develUser = $this->drupalCreateUser(['access devel information']);
$this->adminUser = $this->drupalCreateUser(['access devel information', 'administer site configuration']);
}
/**
* Tests state listing.
*/
public function testStateListing() {
// Ensure that state listing page is accessible only by users with the
// adequate permissions.
$this->drupalGet('devel/state');
$this->assertResponse(403);
$this->drupalLogin($this->develUser);
$this->drupalGet('devel/state');
$this->assertResponse(200);
$this->assertText(t('State editor'));
// Ensure that the state variables table is visible.
$table = $this->xpath('//table[contains(@class, "devel-state-list")]');
$this->assertTrue($table, 'State list table found.');
// Ensure that all state variables are listed in the table.
$states = \Drupal::keyValue('state')->getAll();
$rows = $this->xpath('//table[contains(@class, "devel-state-list")]//tbody//tr');
$this->assertEqual(count($rows), count($states), 'All states are listed in the table.');
// Ensure that the added state variables are listed in the table.
$this->state->set('devel.simple', 'Hello!');
$this->drupalGet('devel/state');
$this->assertFieldByXpath('//table[contains(@class, "devel-state-list")]//tbody//td', 'devel.simple', 'Label found for the added state.');
$thead_xpath = '//table[contains(@class, "devel-state-list")]/thead/tr/th';
$action_xpath = '//table[contains(@class, "devel-state-list")]//ul[@class="dropbutton"]/li/a';
// Ensure that the operations column and the actions buttons are not
// available for user without 'administer site configuration' permission.
$elements = $this->xpath($thead_xpath);
$this->assertEqual(count($elements), 2, 'Correct number of table header cells found.');
$expected_items = ['Name', 'Value'];
foreach ($elements as $key => $element) {
$this->assertIdentical((string) $element[0], $expected_items[$key]);
}
$this->assertFalse($this->xpath($action_xpath), 'Action buttons are not visible.');
// Ensure that the operations column and the actions buttons are
// available for user with 'administer site configuration' permission.
$this->drupalLogin($this->adminUser);
$this->drupalGet('devel/state');
$elements = $this->xpath($thead_xpath);
$this->assertEqual(count($elements), 3, 'Correct number of table header cells found.');
$expected_items = ['Name', 'Value', 'Operations'];
foreach ($elements as $key => $element) {
$this->assertIdentical((string) $element[0], $expected_items[$key]);
}
$this->assertTrue($this->xpath($action_xpath), 'Action buttons are visible.');
// Test that the edit button works properly.
$this->clickLink(t('Edit'));
$this->assertResponse(200);
}
/**
* Tests state edit.
*/
public function testStateEdit() {
// Create some state variables for the test.
$this->state->set('devel.simple', 0);
$this->state->set('devel.array', ['devel' => 'value']);
$this->state->set('devel.object', $this->randomObject());
// Ensure that state edit form is accessible only by users with the
// adequate permissions.
$this->drupalLogin($this->develUser);
$this->drupalGet('devel/state/edit/devel.simple');
$this->assertResponse(403);
$this->drupalLogin($this->adminUser);
// Ensure that accessing an un-existent state variable cause a warning
// message.
$this->drupalGet('devel/state/edit/devel.unknown');
$this->assertText(t('State @name does not exist in the system.', ['@name' => 'devel.unknown']));
// Ensure that state variables that contain simple type can be edited and
// saved.
$this->drupalGet('devel/state/edit/devel.simple');
$this->assertResponse(200);
$this->assertText(t('Edit state variable: @name', ['@name' => 'devel.simple']));
$this->assertInputNotDisabledById('edit-new-value');
$this->assertInputNotDisabledById('edit-submit');
$edit = ['new_value' => 1];
$this->drupalPostForm('devel/state/edit/devel.simple', $edit, t('Save'));
$this->assertText(t('Variable @name was successfully edited.', ['@name' => 'devel.simple']));
$this->assertEqual(1, $this->state->get('devel.simple'));
// Ensure that state variables that contain array can be edited and saved
// and the new value is properly validated.
$this->drupalGet('devel/state/edit/devel.array');
$this->assertResponse(200);
$this->assertText(t('Edit state variable: @name', ['@name' => 'devel.array']));
$this->assertInputNotDisabledById('edit-new-value');
$this->assertInputNotDisabledById('edit-submit');
// Try to save an invalid yaml input.
$edit = ['new_value' => 'devel: \'value updated'];
$this->drupalPostForm('devel/state/edit/devel.array', $edit, t('Save'));
$this->assertText(t('Invalid input:'));
$edit = ['new_value' => 'devel: \'value updated\''];
$this->drupalPostForm('devel/state/edit/devel.array', $edit, t('Save'));
$this->assertText(t('Variable @name was successfully edited.', ['@name' => 'devel.array']));
$this->assertEqual(['devel' => 'value updated'], $this->state->get('devel.array'));
// Ensure that state variables that contain objects cannot be edited.
$this->drupalGet('devel/state/edit/devel.object');
$this->assertResponse(200);
$this->assertText(t('Edit state variable: @name', ['@name' => 'devel.object']));
$this->assertText(t('Only simple structures are allowed to be edited. State @name contains objects.', ['@name' => 'devel.object']));
$this->assertInputDisabledById('edit-new-value');
$this->assertInputDisabledById('edit-submit');
// Ensure that the cancel link works as expected.
$this->clickLink(t('Cancel'));
$this->assertUrl('devel/state');
}
/**
* Helper function for check if an input is disabled.
*
* @param string $id
* The ID of the input.
*/
protected function assertInputDisabledById($id) {
$message = new FormattableMarkup('The input %id is disabled.', ['%id' => $id]);
$xpath = '//textarea[@id=:id and @disabled="disabled"]|//input[@id=:id and @disabled="disabled"]|//select[@id=:id and @disabled="disabled"]';
$query = $this->buildXPathQuery($xpath, [':id' => $id]);
$this->assertFieldByXPath($query, NULL, $message);
}
/**
* Helper function for check if an input is not disabled.
*
* @param string $id
* The ID of the input.
*/
protected function assertInputNotDisabledById($id) {
$message = new FormattableMarkup('The input %id is not disabled.', ['%id' => $id]);
$xpath = '//textarea[@id=:id and not(@disabled="disabled")]|//input[@id=:id and not(@disabled="disabled")]|//select[@id=:id and not(@disabled="disabled")]';
$query = $this->buildXPathQuery($xpath, [':id' => $id]);
$this->assertFieldByXPath($query, NULL, $message);
}
}
@@ -0,0 +1,313 @@
<?php
namespace Drupal\devel\Tests;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\simpletest\WebTestBase;
/**
* Tests switch user.
*
* @group devel
*/
class DevelSwitchUserTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['devel', 'block'];
/**
* The block used by this test.
*
* @var \Drupal\block\BlockInterface
*/
protected $block;
/**
* The devel user.
*
* @var \Drupal\user\Entity\User
*/
protected $develUser;
/**
* The switch user.
*
* @var \Drupal\user\Entity\User
*/
protected $switchUser;
/**
* The web user.
*
* @var \Drupal\user\Entity\User
*/
protected $webUser;
/**
* Set up test.
*/
protected function setUp() {
parent::setUp();
$this->block = $this->drupalPlaceBlock('devel_switch_user', ['id' => 'switch-user']);
$this->develUser = $this->drupalCreateUser(['access devel information', 'switch users']);
$this->switchUser = $this->drupalCreateUser(['switch users']);
$this->webUser = $this->drupalCreateUser();
}
/**
* Tests switch user.
*/
public function testSwitchUser() {
$this->drupalLogin($this->webUser);
$this->drupalGet('');
$this->assertNoText($this->block->label(), 'Block title was not found.');
// Ensure that a token is required to switch user.
$this->drupalGet('/devel/switch/' . $this->webUser->getUsername());
$this->assertResponse(403);
$this->drupalLogin($this->develUser);
$this->drupalGet('');
$this->assertText($this->block->label(), 'Block title was found.');
// Ensure that if name in not passed the controller returns access denied.
$this->drupalGet('/devel/switch');
$this->assertResponse(403);
// Ensure that a token is required to switch user.
$this->drupalGet('/devel/switch/' . $this->switchUser->getUsername());
$this->assertResponse(403);
// Switch to another user account.
$this->drupalGet('/user/' . $this->switchUser->id());
$this->clickLink($this->switchUser->getUsername());
$this->assertSessionByUid($this->switchUser->id());
$this->assertNoSessionByUid($this->develUser->id());
// Switch back to initial account.
$this->clickLink($this->develUser->getUsername());
$this->assertNoSessionByUid($this->switchUser->id());
$this->assertSessionByUid($this->develUser->id());
// Use the search form to switch to another account.
$edit = ['userid' => $this->switchUser->getUsername()];
$this->drupalPostForm(NULL, $edit, t('Switch'));
$this->assertSessionByUid($this->switchUser->id());
$this->assertNoSessionByUid($this->develUser->id());
}
/**
* Tests the switch user block configuration.
*/
public function testSwitchUserBlockConfiguration() {
$anonymous = \Drupal::config('user.settings')->get('anonymous');
// Create some users for the test.
for ($i = 0; $i < 12; $i++) {
$this->drupalCreateUser();
}
$this->drupalLogin($this->develUser);
$this->drupalGet('');
$this->assertText($this->block->label(), 'Block title was found.');
// Ensure that block default configuration is effectively used. The block
// default configuration is the following:
// - list_size : 12
// - include_anon : FALSE
// - show_form : TRUE
$this->assertSwitchUserSearchForm();
$this->assertSwitchUserListCount(12);
$this->assertSwitchUserListNoContainsUser($anonymous);
// Ensure that changing the list_size configuration property the number of
// user displayed in the list change.
$this->setBlockConfiguration('list_size', 4);
$this->drupalGet('');
$this->assertSwitchUserListCount(4);
// Ensure that changing the include_anon configuration property the
// anonymous user is displayed in the list.
$this->setBlockConfiguration('include_anon', TRUE);
$this->drupalGet('');
$this->assertSwitchUserListContainsUser($anonymous);
// Ensure that changing the show_form configuration property the
// form is not displayed.
$this->setBlockConfiguration('show_form', FALSE);
$this->drupalGet('');
$this->assertSwitchUserNoSearchForm();
}
/**
* Test the user list items.
*/
public function testSwitchUserListItems() {
$anonymous = \Drupal::config('user.settings')->get('anonymous');
$this->setBlockConfiguration('list_size', 2);
// Login as web user so we are sure that this account is prioritized
// in the list if not enougth user with 'switch users' permission are
// present.
$this->drupalLogin($this->webUser);
$this->drupalLogin($this->develUser);
$this->drupalGet('');
// Ensure that user with 'switch users' permission are prioritized.
$this->assertSwitchUserListCount(2);
$this->assertSwitchUserListContainsUser($this->develUser->getUsername());
$this->assertSwitchUserListContainsUser($this->switchUser->getUsername());
// Ensure that blocked users are not shown in the list.
$this->switchUser->set('status', 0)->save();
$this->drupalGet('');
$this->assertSwitchUserListCount(2);
$this->assertSwitchUserListContainsUser($this->develUser->getUsername());
$this->assertSwitchUserListContainsUser($this->webUser->getUsername());
$this->assertSwitchUserListNoContainsUser($this->switchUser->getUsername());
// Ensure that anonymous user are prioritized if include_anon is set to true.
$this->setBlockConfiguration('include_anon', TRUE);
$this->drupalGet('');
$this->assertSwitchUserListCount(2);
$this->assertSwitchUserListContainsUser($this->develUser->getUsername());
$this->assertSwitchUserListContainsUser($anonymous);
// Ensure that the switch user block works properly even if no prioritized
// users are found (special handling for user 1).
$this->drupalLogout();
$this->develUser->delete();
$this->drupalLogin($this->rootUser);
$this->drupalGet('');
$this->assertSwitchUserListCount(2);
$this->assertSwitchUserListContainsUser($this->rootUser->getUsername());
$this->assertSwitchUserListContainsUser($anonymous);
// Ensure that the switch user block works properly even if no roles have
// the 'switch users' permission associated (special handling for user 1).
$roles = user_roles(TRUE, 'switch users');
\Drupal::entityTypeManager()->getStorage('user_role')->delete($roles);
$this->drupalGet('');
$this->assertSwitchUserListCount(2);
$this->assertSwitchUserListContainsUser($this->rootUser->getUsername());
$this->assertSwitchUserListContainsUser($anonymous);
}
/**
* Helper function for verify the number of items shown in the user list.
*
* @param int $number
* The expected numer of items.
*/
public function assertSwitchUserListCount($number) {
$result = $this->xpath('//div[@id=:block]//ul/li/a', [':block' => 'block-switch-user']);
$this->assert(count($result) == $number, 'The number of users shown in switch user is correct.');
}
/**
* Helper function for verify if the user list contains a username.
*
* @param string $username
* The username to check.
*/
public function assertSwitchUserListContainsUser($username) {
$result = $this->xpath('//div[@id=:block]//ul/li/a[normalize-space()=:user]', [':block' => 'block-switch-user', ':user' => $username]);
$this->assert(count($result) > 0, new FormattableMarkup('User "%user" is included in the switch user list.', ['%user' => $username]));
}
/**
* Helper function for verify if the user list not contains a username.
*
* @param string $username
* The username to check.
*/
public function assertSwitchUserListNoContainsUser($username) {
$result = $this->xpath('//div[@id=:block]//ul/li/a[normalize-space()=:user]', [':block' => 'block-switch-user', ':user' => $username]);
$this->assert(count($result) == 0, new FormattableMarkup('User "%user" is not included in the switch user list.', ['%user' => $username]));
}
/**
* Helper function for verify if the search form is shown.
*/
public function assertSwitchUserSearchForm() {
$result = $this->xpath('//div[@id=:block]//form[contains(@class, :form)]', [':block' => 'block-switch-user', ':form' => 'devel-switchuser-form']);
$this->assert(count($result) > 0, 'The search form is shown.');
}
/**
* Helper function for verify if the search form is not shown.
*/
public function assertSwitchUserNoSearchForm() {
$result = $this->xpath('//div[@id=:block]//form[contains(@class, :form)]', [':block' => 'block-switch-user', ':form' => 'devel-switchuser-form']);
$this->assert(count($result) == 0, 'The search form is not shown.');
}
/**
* Protected helper method to set the test block's configuration.
*/
protected function setBlockConfiguration($key, $value) {
$block = $this->block->getPlugin();
$block->setConfigurationValue($key, $value);
$this->block->save();
}
/**
* Asserts that there is a session for a given user ID.
*
* Based off masquarade module.
*
* @param int $uid
* The user ID for which to find a session record.
*
* TODO find a cleaner way to do this check.
*/
protected function assertSessionByUid($uid) {
$query = \Drupal::database()->select('sessions');
$query->fields('sessions', array('uid'));
$query->condition('uid', $uid);
$result = $query->execute()->fetchAll();
if (empty($result)) {
$this->fail(new FormattableMarkup('No session found for uid @uid', array('@uid' => $uid)));
}
elseif (count($result) > 1) {
// If there is more than one session, then that must be unexpected.
$this->fail("Found more than 1 session for uid $uid.");
}
else {
$this->pass("Found session for uid $uid.");
}
}
/**
* Asserts that no session exists for a given uid.
*
* Based off masquarade module.
*
* @param int $uid
* The user ID to assert.
*
* TODO find a cleaner way to do this check.
*/
protected function assertNoSessionByUid($uid) {
$query = \Drupal::database()->select('sessions');
$query->fields('sessions', array('uid'));
$query->condition('uid', $uid);
$result = $query->execute()->fetchAll();
$this->assert(empty($result), "No session for uid $uid found.");
}
}
@@ -0,0 +1,201 @@
<?php
namespace Drupal\devel\Twig\Extension;
use Drupal\devel\DevelDumperManagerInterface;
/**
* Provides the Devel debugging function within Twig templates.
*
* NOTE: This extension doesn't do anything unless twig_debug is enabled.
* The twig_debug setting is read from the Twig environment, not Drupal
* Settings, so a container rebuild is necessary when toggling twig_debug on
* and off.
*/
class Debug extends \Twig_Extension {
/**
* The devel dumper service.
*
* @var \Drupal\devel\DevelDumperManagerInterface
*/
protected $dumper;
/**
* Constructs a Debug object.
*
* @param \Drupal\devel\DevelDumperManagerInterface $dumper
* The devel dumper service.
*/
public function __construct(DevelDumperManagerInterface $dumper) {
$this->dumper = $dumper;
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'devel_debug';
}
/**
* {@inheritdoc}
*/
public function getFunctions() {
$functions = [];
foreach (['devel_dump', 'kpr'] as $function) {
$functions[] = new \Twig_SimpleFunction($function, [$this, 'dump'], [
'is_safe' => ['html'],
'needs_environment' => TRUE,
'needs_context' => TRUE,
'is_variadic' => TRUE,
]);
}
foreach (['devel_message', 'dpm', 'dsm'] as $function) {
$functions[] = new \Twig_SimpleFunction($function, [$this, 'message'], [
'is_safe' => ['html'],
'needs_environment' => TRUE,
'needs_context' => TRUE,
'is_variadic' => TRUE,
]);
}
foreach (['devel_breakpoint'] as $function) {
$functions[] = new \Twig_SimpleFunction($function, [$this, 'breakpoint'], [
'needs_environment' => TRUE,
'needs_context' => TRUE,
'is_variadic' => TRUE,
]);
}
return $functions;
}
/**
* Provides debug function to Twig templates.
*
* Handles 0, 1, or multiple arguments.
*
* @param \Twig_Environment $env
* The twig environment instance.
* @param array $context
* An array of parameters passed to the template.
* @param array $args
* An array of parameters passed the function.
*
* @return string
* String representation of the input variables.
*
* @see \Drupal\devel\DevelDumperManager::dump()
*/
public function dump(\Twig_Environment $env, array $context, array $args = []) {
if (!$env->isDebug()) {
return;
}
ob_start();
// No arguments passed, display full Twig context.
if (empty($args)) {
$context_variables = $this->getContextVariables($context);
$this->dumper->dump($context_variables, 'Twig context');
}
else {
foreach ($args as $variable) {
$this->dumper->dump($variable);
}
}
return ob_get_clean();
}
/**
* Provides debug function to Twig templates.
*
* Handles 0, 1, or multiple arguments.
*
* @param \Twig_Environment $env
* The twig environment instance.
* @param array $context
* An array of parameters passed to the template.
* @param array $args
* An array of parameters passed the function.
*
* @return void
*
* @see \Drupal\devel\DevelDumperManager::message()
*/
public function message(\Twig_Environment $env, array $context, array $args = []) {
if (!$env->isDebug()) {
return;
}
// No arguments passed, display full Twig context.
if (empty($args)) {
$context_variables = $this->getContextVariables($context);
$this->dumper->message($context_variables, 'Twig context');
}
else {
foreach ($args as $variable) {
$this->dumper->message($variable);
}
}
}
/**
* Provides XDebug integration for Twig templates.
*
* To use this features simply put the following statement in the template
* of interest:
*
* @code
* {{ devel_breakpoint() }}
* @endcode
*
* When the template is evaluated is made a call to a dedicated method in
* devel twig debug extension in which is used xdebug_break(), that emits a
* breakpoint to the debug client (the debugger break on the specific line as
* if a normal file/line breakpoint was set on this line).
* In this way you'll be able to inspect any variables available in the
* template (environment, context, specific variables etc..) in your IDE.
*
* @param \Twig_Environment $env
* The twig environment instance.
* @param array $context
* An array of parameters passed to the template.
* @param array $args
* An array of parameters passed the function.
*/
public function breakpoint(\Twig_Environment $env, array $context, array $args = []) {
if (!$env->isDebug()) {
return;
}
if (function_exists('xdebug_break')) {
xdebug_break();
}
}
/**
* Filters the Twig context variable.
*
* @param array $context
* The Twig context.
*
* @return array
* An array Twig context variables.
*/
protected function getContextVariables(array $context) {
$context_variables = [];
foreach ($context as $key => $value) {
if (!$value instanceof \Twig_Template) {
$context_variables[$key] = $value;
}
}
return $context_variables;
}
}