Move into nested docroot

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

View file

@ -0,0 +1,18 @@
<?php
namespace Drupal\system\Plugin\Archiver;
use Drupal\Core\Archiver\Tar as BaseTar;
/**
* Defines an archiver implementation for .tar files.
*
* @Archiver(
* id = "Tar",
* title = @Translation("Tar"),
* description = @Translation("Handles .tar files."),
* extensions = {"tar", "tgz", "tar.gz", "tar.bz2"}
* )
*/
class Tar extends BaseTar {
}

View file

@ -0,0 +1,20 @@
<?php
namespace Drupal\system\Plugin\Archiver;
use Drupal\Core\Archiver\Zip as BaseZip;
/**
* Defines an archiver implementation for .zip files.
*
* @link http://php.net/zip
*
* @Archiver(
* id = "Zip",
* title = @Translation("Zip"),
* description = @Translation("Handles zip files."),
* extensions = {"zip"}
* )
*/
class Zip extends BaseZip {
}

View file

@ -0,0 +1,186 @@
<?php
namespace Drupal\system\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a block to display 'Site branding' elements.
*
* @Block(
* id = "system_branding_block",
* admin_label = @Translation("Site branding")
* )
*/
class SystemBrandingBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* Stores the configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* Creates a SystemBrandingBlock instance.
*
* @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\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, ConfigFactoryInterface $config_factory) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->configFactory = $config_factory;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('config.factory')
);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return array(
'use_site_logo' => TRUE,
'use_site_name' => TRUE,
'use_site_slogan' => TRUE,
'label_display' => FALSE,
);
}
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state) {
// Get the theme.
$theme = $form_state->get('block_theme');
// Get permissions.
$url_system_theme_settings = new Url('system.theme_settings');
$url_system_theme_settings_theme = new Url('system.theme_settings_theme', array('theme' => $theme));
if ($url_system_theme_settings->access() && $url_system_theme_settings_theme->access()) {
// Provide links to the Appearance Settings and Theme Settings pages
// if the user has access to administer themes.
$site_logo_description = $this->t('Defined on the <a href=":appearance">Appearance Settings</a> or <a href=":theme">Theme Settings</a> page.', array(
':appearance' => $url_system_theme_settings->toString(),
':theme' => $url_system_theme_settings_theme->toString(),
));
}
else {
// Explain that the user does not have access to the Appearance and Theme
// Settings pages.
$site_logo_description = $this->t('Defined on the Appearance or Theme Settings page. You do not have the appropriate permissions to change the site logo.');
}
$url_system_site_information_settings = new Url('system.site_information_settings');
if ($url_system_site_information_settings->access()) {
// Get paths to settings pages.
$site_information_url = $url_system_site_information_settings->toString();
// Provide link to Site Information page if the user has access to
// administer site configuration.
$site_name_description = $this->t('Defined on the <a href=":information">Site Information</a> page.', array(':information' => $site_information_url));
$site_slogan_description = $this->t('Defined on the <a href=":information">Site Information</a> page.', array(':information' => $site_information_url));
}
else {
// Explain that the user does not have access to the Site Information
// page.
$site_name_description = $this->t('Defined on the Site Information page. You do not have the appropriate permissions to change the site logo.');
$site_slogan_description = $this->t('Defined on the Site Information page. You do not have the appropriate permissions to change the site logo.');
}
$form['block_branding'] = array(
'#type' => 'fieldset',
'#title' => $this->t('Toggle branding elements'),
'#description' => $this->t('Choose which branding elements you want to show in this block instance.'),
);
$form['block_branding']['use_site_logo'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Site logo'),
'#description' => $site_logo_description,
'#default_value' => $this->configuration['use_site_logo'],
);
$form['block_branding']['use_site_name'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Site name'),
'#description' => $site_name_description,
'#default_value' => $this->configuration['use_site_name'],
);
$form['block_branding']['use_site_slogan'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Site slogan'),
'#description' => $site_slogan_description,
'#default_value' => $this->configuration['use_site_slogan'],
);
return $form;
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
$block_branding = $form_state->getValue('block_branding');
$this->configuration['use_site_logo'] = $block_branding['use_site_logo'];
$this->configuration['use_site_name'] = $block_branding['use_site_name'];
$this->configuration['use_site_slogan'] = $block_branding['use_site_slogan'];
}
/**
* {@inheritdoc}
*/
public function build() {
$build = array();
$site_config = $this->configFactory->get('system.site');
$build['site_logo'] = array(
'#theme' => 'image',
'#uri' => theme_get_setting('logo.url'),
'#alt' => $this->t('Home'),
'#access' => $this->configuration['use_site_logo'],
);
$build['site_name'] = array(
'#markup' => $site_config->get('name'),
'#access' => $this->configuration['use_site_name'],
);
$build['site_slogan'] = array(
'#markup' => $site_config->get('slogan'),
'#access' => $this->configuration['use_site_slogan'],
);
return $build;
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return Cache::mergeTags(
parent::getCacheTags(),
$this->configFactory->get('system.site')->getCacheTags()
);
}
}

View file

@ -0,0 +1,75 @@
<?php
namespace Drupal\system\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a block to display the breadcrumbs.
*
* @Block(
* id = "system_breadcrumb_block",
* admin_label = @Translation("Breadcrumbs")
* )
*/
class SystemBreadcrumbBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The breadcrumb manager.
*
* @var \Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface
*/
protected $breadcrumbManager;
/**
* The current route match.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* Constructs a new SystemBreadcrumbBlock 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\Breadcrumb\BreadcrumbBuilderInterface $breadcrumb_manager
* The breadcrumb manager.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route match.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, BreadcrumbBuilderInterface $breadcrumb_manager, RouteMatchInterface $route_match) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->breadcrumbManager = $breadcrumb_manager;
$this->routeMatch = $route_match;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('breadcrumb'),
$container->get('current_route_match')
);
}
/**
* {@inheritdoc}
*/
public function build() {
return $this->breadcrumbManager->build($this->routeMatch)->toRenderable();
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace Drupal\system\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Block\MainContentBlockPluginInterface;
/**
* Provides a 'Main page content' block.
*
* @Block(
* id = "system_main_block",
* admin_label = @Translation("Main page content")
* )
*/
class SystemMainBlock extends BlockBase implements MainContentBlockPluginInterface {
/**
* The render array representing the main page content.
*
* @var array
*/
protected $mainContent;
/**
* {@inheritdoc}
*/
public function setMainContent(array $main_content) {
$this->mainContent = $main_content;
}
/**
* {@inheritdoc}
*/
public function build() {
return $this->mainContent;
}
}

View file

@ -0,0 +1,196 @@
<?php
namespace Drupal\system\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Menu\MenuActiveTrailInterface;
use Drupal\Core\Menu\MenuLinkTreeInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a generic Menu block.
*
* @Block(
* id = "system_menu_block",
* admin_label = @Translation("Menu"),
* category = @Translation("Menus"),
* deriver = "Drupal\system\Plugin\Derivative\SystemMenuBlock"
* )
*/
class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The menu link tree service.
*
* @var \Drupal\Core\Menu\MenuLinkTreeInterface
*/
protected $menuTree;
/**
* The active menu trail service.
*
* @var \Drupal\Core\Menu\MenuActiveTrailInterface
*/
protected $menuActiveTrail;
/**
* Constructs a new SystemMenuBlock.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param array $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
* The menu tree service.
* @param \Drupal\Core\Menu\MenuActiveTrailInterface $menu_active_trail
* The active menu trail service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuLinkTreeInterface $menu_tree, MenuActiveTrailInterface $menu_active_trail) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->menuTree = $menu_tree;
$this->menuActiveTrail = $menu_active_trail;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('menu.link_tree'),
$container->get('menu.active_trail')
);
}
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state) {
$config = $this->configuration;
$defaults = $this->defaultConfiguration();
$form['menu_levels'] = array(
'#type' => 'details',
'#title' => $this->t('Menu levels'),
// Open if not set to defaults.
'#open' => $defaults['level'] !== $config['level'] || $defaults['depth'] !== $config['depth'],
'#process' => [[get_class(), 'processMenuLevelParents']],
);
$options = range(0, $this->menuTree->maxDepth());
unset($options[0]);
$form['menu_levels']['level'] = array(
'#type' => 'select',
'#title' => $this->t('Initial menu level'),
'#default_value' => $config['level'],
'#options' => $options,
'#description' => $this->t('The menu will only be visible if the menu item for the current page is at or below the selected starting level. Select level 1 to always keep this menu visible.'),
'#required' => TRUE,
);
$options[0] = $this->t('Unlimited');
$form['menu_levels']['depth'] = array(
'#type' => 'select',
'#title' => $this->t('Maximum number of menu levels to display'),
'#default_value' => $config['depth'],
'#options' => $options,
'#description' => $this->t('The maximum number of menu levels to show, starting from the initial menu level. For example: with an initial level 2 and a maximum number of 3, menu levels 2, 3 and 4 can be displayed.'),
'#required' => TRUE,
);
return $form;
}
/**
* Form API callback: Processes the menu_levels field element.
*
* Adjusts the #parents of menu_levels to save its children at the top level.
*/
public static function processMenuLevelParents(&$element, FormStateInterface $form_state, &$complete_form) {
array_pop($element['#parents']);
return $element;
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
$this->configuration['level'] = $form_state->getValue('level');
$this->configuration['depth'] = $form_state->getValue('depth');
}
/**
* {@inheritdoc}
*/
public function build() {
$menu_name = $this->getDerivativeId();
$parameters = $this->menuTree->getCurrentRouteMenuTreeParameters($menu_name);
// Adjust the menu tree parameters based on the block's configuration.
$level = $this->configuration['level'];
$depth = $this->configuration['depth'];
$parameters->setMinDepth($level);
// When the depth is configured to zero, there is no depth limit. When depth
// is non-zero, it indicates the number of levels that must be displayed.
// Hence this is a relative depth that we must convert to an actual
// (absolute) depth, that may never exceed the maximum depth.
if ($depth > 0) {
$parameters->setMaxDepth(min($level + $depth - 1, $this->menuTree->maxDepth()));
}
$tree = $this->menuTree->load($menu_name, $parameters);
$manipulators = array(
array('callable' => 'menu.default_tree_manipulators:checkAccess'),
array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
);
$tree = $this->menuTree->transform($tree, $manipulators);
return $this->menuTree->build($tree);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'level' => 1,
'depth' => 0,
];
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
// Even when the menu block renders to the empty string for a user, we want
// the cache tag for this menu to be set: whenever the menu is changed, this
// menu block must also be re-rendered for that user, because maybe a menu
// link that is accessible for that user has been added.
$cache_tags = parent::getCacheTags();
$cache_tags[] = 'config:system.menu.' . $this->getDerivativeId();
return $cache_tags;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
// ::build() uses MenuLinkTreeInterface::getCurrentRouteMenuTreeParameters()
// to generate menu tree parameters, and those take the active menu trail
// into account. Therefore, we must vary the rendered menu by the active
// trail of the rendered menu.
// Additional cache contexts, e.g. those that determine link text or
// accessibility of a menu, will be bubbled automatically.
$menu_name = $this->getDerivativeId();
return Cache::mergeContexts(parent::getCacheContexts(), ['route.menu_active_trails:' . $menu_name]);
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace Drupal\system\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Block\MessagesBlockPluginInterface;
use Drupal\Core\Cache\Cache;
/**
* Provides a block to display the messages.
*
* @see drupal_set_message()
*
* @Block(
* id = "system_messages_block",
* admin_label = @Translation("Messages")
* )
*/
class SystemMessagesBlock extends BlockBase implements MessagesBlockPluginInterface {
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return array(
'label_display' => FALSE,
);
}
/**
* {@inheritdoc}
*/
public function build() {
return ['#type' => 'status_messages'];
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
// The messages are session-specific and hence aren't cacheable, but the
// block itself *is* cacheable because it uses a #lazy_builder callback and
// hence the block has a globally cacheable render array.
return Cache::PERMANENT;
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace Drupal\system\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Provides a 'Powered by Drupal' block.
*
* @Block(
* id = "system_powered_by_block",
* admin_label = @Translation("Powered by Drupal")
* )
*/
class SystemPoweredByBlock extends BlockBase {
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return ['label_display' => FALSE];
}
/**
* {@inheritdoc}
*/
public function build() {
return array('#markup' => '<span>' . $this->t('Powered by <a href=":poweredby">Drupal</a>', array(':poweredby' => 'https://www.drupal.org')) . '</span>');
}
}

View file

@ -0,0 +1,130 @@
<?php
namespace Drupal\system\Plugin\Condition;
use Drupal\Core\Condition\ConditionPluginBase;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Theme\ThemeManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a 'Current Theme' condition.
*
* @Condition(
* id = "current_theme",
* label = @Translation("Current Theme"),
* )
*/
class CurrentThemeCondition extends ConditionPluginBase implements ContainerFactoryPluginInterface {
/**
* The theme manager.
*
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
protected $themeManager;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* Constructs a CurrentThemeCondition condition plugin.
*
* @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\Theme\ThemeManagerInterface $theme_manager
* The theme manager.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, ThemeManagerInterface $theme_manager, ThemeHandlerInterface $theme_handler) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->themeManager = $theme_manager;
$this->themeHandler = $theme_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('theme.manager'),
$container->get('theme_handler')
);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return array('theme' => '') + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['theme'] = array(
'#type' => 'select',
'#title' => $this->t('Theme'),
'#default_value' => $this->configuration['theme'],
'#options' => array_map(function ($theme_info) {
return $theme_info->info['name'];
}, $this->themeHandler->listInfo()),
);
return parent::buildConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['theme'] = $form_state->getValue('theme');
parent::submitConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function evaluate() {
if (!$this->configuration['theme']) {
return TRUE;
}
return $this->themeManager->getActiveTheme()->getName() == $this->configuration['theme'];
}
/**
* {@inheritdoc}
*/
public function summary() {
if ($this->isNegated()) {
return $this->t('The current theme is not @theme', array('@theme' => $this->configuration['theme']));
}
return $this->t('The current theme is @theme', array('@theme' => $this->configuration['theme']));
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
$contexts = parent::getCacheContexts();
$contexts[] = 'theme';
return $contexts;
}
}

View file

@ -0,0 +1,166 @@
<?php
namespace Drupal\system\Plugin\Condition;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Condition\ConditionPluginBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Path\AliasManagerInterface;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\Path\PathMatcherInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Provides a 'Request Path' condition.
*
* @Condition(
* id = "request_path",
* label = @Translation("Request Path"),
* )
*/
class RequestPath extends ConditionPluginBase implements ContainerFactoryPluginInterface {
/**
* An alias manager to find the alias for the current system path.
*
* @var \Drupal\Core\Path\AliasManagerInterface
*/
protected $aliasManager;
/**
* The path matcher.
*
* @var \Drupal\Core\Path\PathMatcherInterface
*/
protected $pathMatcher;
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The current path.
*
* @var \Drupal\Core\Path\CurrentPathStack
*/
protected $currentPath;
/**
* Constructs a RequestPath condition plugin.
*
* @param \Drupal\Core\Path\AliasManagerInterface $alias_manager
* An alias manager to find the alias for the current system path.
* @param \Drupal\Core\Path\PathMatcherInterface $path_matcher
* The path matcher service.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Path\CurrentPathStack $current_path
* The current path.
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param array $plugin_definition
* The plugin implementation definition.
*/
public function __construct(AliasManagerInterface $alias_manager, PathMatcherInterface $path_matcher, RequestStack $request_stack, CurrentPathStack $current_path, array $configuration, $plugin_id, array $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->aliasManager = $alias_manager;
$this->pathMatcher = $path_matcher;
$this->requestStack = $request_stack;
$this->currentPath = $current_path;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$container->get('path.alias_manager'),
$container->get('path.matcher'),
$container->get('request_stack'),
$container->get('path.current'),
$configuration,
$plugin_id,
$plugin_definition);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return array('pages' => '') + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['pages'] = array(
'#type' => 'textarea',
'#title' => $this->t('Pages'),
'#default_value' => $this->configuration['pages'],
'#description' => $this->t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. An example path is %user-wildcard for every user page. %front is the front page.", array(
'%user-wildcard' => '/user/*',
'%front' => '<front>',
)),
);
return parent::buildConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['pages'] = $form_state->getValue('pages');
parent::submitConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function summary() {
$pages = array_map('trim', explode("\n", $this->configuration['pages']));
$pages = implode(', ', $pages);
if (!empty($this->configuration['negate'])) {
return $this->t('Do not return true on the following pages: @pages', array('@pages' => $pages));
}
return $this->t('Return true on the following pages: @pages', array('@pages' => $pages));
}
/**
* {@inheritdoc}
*/
public function evaluate() {
// Convert path to lowercase. This allows comparison of the same path
// with different case. Ex: /Page, /page, /PAGE.
$pages = Unicode::strtolower($this->configuration['pages']);
if (!$pages) {
return TRUE;
}
$request = $this->requestStack->getCurrentRequest();
// Compare the lowercase path alias (if any) and internal path.
$path = $this->currentPath->getPath($request);
// Do not trim a trailing slash if that is the complete path.
$path = $path === '/' ? $path : rtrim($path, '/');
$path_alias = Unicode::strtolower($this->aliasManager->getAliasByPath($path));
return $this->pathMatcher->matchPath($path_alias, $pages) || (($path != $path_alias) && $this->pathMatcher->matchPath($path, $pages));
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
$contexts = parent::getCacheContexts();
$contexts[] = 'url.path';
return $contexts;
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Drupal\system\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides block plugin definitions for custom menus.
*
* @see \Drupal\system\Plugin\Block\SystemMenuBlock
*/
class SystemMenuBlock extends DeriverBase implements ContainerDeriverInterface {
/**
* The menu storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $menuStorage;
/**
* Constructs new SystemMenuBlock.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $menu_storage
* The menu storage.
*/
public function __construct(EntityStorageInterface $menu_storage) {
$this->menuStorage = $menu_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity.manager')->getStorage('menu')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
foreach ($this->menuStorage->loadMultiple() as $menu => $entity) {
$this->derivatives[$menu] = $base_plugin_definition;
$this->derivatives[$menu]['admin_label'] = $entity->label();
$this->derivatives[$menu]['config_dependencies']['config'] = array($entity->getConfigDependencyName());
}
return $this->derivatives;
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Drupal\system\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides dynamic tabs based on active themes.
*/
class ThemeLocalTask extends DeriverBase implements ContainerDeriverInterface {
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* Constructs a new ThemeLocalTask instance.
*
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
*/
public function __construct(ThemeHandlerInterface $theme_handler) {
$this->themeHandler = $theme_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('theme_handler')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
foreach ($this->themeHandler->listInfo() as $theme_name => $theme) {
if ($this->themeHandler->hasUi($theme_name)) {
$this->derivatives[$theme_name] = $base_plugin_definition;
$this->derivatives[$theme_name]['title'] = $theme->info['name'];
$this->derivatives[$theme_name]['route_parameters'] = array('theme' => $theme_name);
}
}
return $this->derivatives;
}
}

View file

@ -0,0 +1,446 @@
<?php
namespace Drupal\system\Plugin\ImageToolkit;
use Drupal\Component\Utility\Color;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\ImageToolkit\ImageToolkitBase;
use Drupal\Core\ImageToolkit\ImageToolkitOperationManagerInterface;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the GD2 toolkit for image manipulation within Drupal.
*
* @ImageToolkit(
* id = "gd",
* title = @Translation("GD2 image manipulation toolkit")
* )
*/
class GDToolkit extends ImageToolkitBase {
/**
* A GD image resource.
*
* @var resource|null
*/
protected $resource = NULL;
/**
* Image type represented by a PHP IMAGETYPE_* constant (e.g. IMAGETYPE_JPEG).
*
* @var int
*/
protected $type;
/**
* Image information from a file, available prior to loading the GD resource.
*
* This contains a copy of the array returned by executing getimagesize()
* on the image file when the image object is instantiated. It gets reset
* to NULL as soon as the GD resource is loaded.
*
* @var array|null
*
* @see \Drupal\system\Plugin\ImageToolkit\GDToolkit::parseFile()
* @see \Drupal\system\Plugin\ImageToolkit\GDToolkit::setResource()
* @see http://php.net/manual/function.getimagesize.php
*/
protected $preLoadInfo = NULL;
/**
* The StreamWrapper manager.
*
* @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
*/
protected $streamWrapperManager;
/**
* Constructs a GDToolkit 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 array $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\ImageToolkit\ImageToolkitOperationManagerInterface $operation_manager
* The toolkit operation manager.
* @param \Psr\Log\LoggerInterface $logger
* A logger instance.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager
* The StreamWrapper manager.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, ImageToolkitOperationManagerInterface $operation_manager, LoggerInterface $logger, ConfigFactoryInterface $config_factory, StreamWrapperManagerInterface $stream_wrapper_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $operation_manager, $logger, $config_factory);
$this->streamWrapperManager = $stream_wrapper_manager;
}
/**
* Destructs a GDToolkit object.
*
* Frees memory associated with a GD image resource.
*/
public function __destruct() {
if (is_resource($this->resource)) {
imagedestroy($this->resource);
}
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('image.toolkit.operation.manager'),
$container->get('logger.channel.image'),
$container->get('config.factory'),
$container->get('stream_wrapper_manager')
);
}
/**
* Sets the GD image resource.
*
* @param resource $resource
* The GD image resource.
*
* @return \Drupal\system\Plugin\ImageToolkit\GDToolkit
* An instance of the current toolkit object.
*/
public function setResource($resource) {
if (!is_resource($resource) || get_resource_type($resource) != 'gd') {
throw new \InvalidArgumentException('Invalid resource argument');
}
$this->preLoadInfo = NULL;
$this->resource = $resource;
return $this;
}
/**
* Retrieves the GD image resource.
*
* @return resource|null
* The GD image resource, or NULL if not available.
*/
public function getResource() {
if (!is_resource($this->resource)) {
$this->load();
}
return $this->resource;
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['image_jpeg_quality'] = array(
'#type' => 'number',
'#title' => t('JPEG quality'),
'#description' => t('Define the image quality for JPEG manipulations. Ranges from 0 to 100. Higher values mean better image quality but bigger files.'),
'#min' => 0,
'#max' => 100,
'#default_value' => $this->configFactory->getEditable('system.image.gd')->get('jpeg_quality', FALSE),
'#field_suffix' => t('%'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configFactory->getEditable('system.image.gd')
->set('jpeg_quality', $form_state->getValue(array('gd', 'image_jpeg_quality')))
->save();
}
/**
* Loads a GD resource from a file.
*
* @return bool
* TRUE or FALSE, based on success.
*/
protected function load() {
// Return immediately if the image file is not valid.
if (!$this->isValid()) {
return FALSE;
}
$function = 'imagecreatefrom' . image_type_to_extension($this->getType(), FALSE);
if (function_exists($function) && $resource = $function($this->getSource())) {
$this->setResource($resource);
if (imageistruecolor($resource)) {
return TRUE;
}
else {
// Convert indexed images to truecolor, copying the image to a new
// truecolor resource, so that filters work correctly and don't result
// in unnecessary dither.
$data = array(
'width' => imagesx($resource),
'height' => imagesy($resource),
'extension' => image_type_to_extension($this->getType(), FALSE),
'transparent_color' => $this->getTransparentColor(),
'is_temp' => TRUE,
);
if ($this->apply('create_new', $data)) {
imagecopy($this->getResource(), $resource, 0, 0, 0, 0, imagesx($resource), imagesy($resource));
imagedestroy($resource);
}
}
return (bool) $this->getResource();
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function isValid() {
return ((bool) $this->preLoadInfo || (bool) $this->resource);
}
/**
* {@inheritdoc}
*/
public function save($destination) {
$scheme = file_uri_scheme($destination);
// Work around lack of stream wrapper support in imagejpeg() and imagepng().
if ($scheme && file_stream_wrapper_valid_scheme($scheme)) {
// If destination is not local, save image to temporary local file.
$local_wrappers = $this->streamWrapperManager->getWrappers(StreamWrapperInterface::LOCAL);
if (!isset($local_wrappers[$scheme])) {
$permanent_destination = $destination;
$destination = drupal_tempnam('temporary://', 'gd_');
}
// Convert stream wrapper URI to normal path.
$destination = drupal_realpath($destination);
}
$function = 'image' . image_type_to_extension($this->getType(), FALSE);
if (!function_exists($function)) {
return FALSE;
}
if ($this->getType() == IMAGETYPE_JPEG) {
$success = $function($this->getResource(), $destination, $this->configFactory->get('system.image.gd')->get('jpeg_quality'));
}
else {
// Always save PNG images with full transparency.
if ($this->getType() == IMAGETYPE_PNG) {
imagealphablending($this->getResource(), FALSE);
imagesavealpha($this->getResource(), TRUE);
}
$success = $function($this->getResource(), $destination);
}
// Move temporary local file to remote destination.
if (isset($permanent_destination) && $success) {
return (bool) file_unmanaged_move($destination, $permanent_destination, FILE_EXISTS_REPLACE);
}
return $success;
}
/**
* {@inheritdoc}
*/
public function parseFile() {
$data = @getimagesize($this->getSource());
if ($data && in_array($data[2], static::supportedTypes())) {
$this->setType($data[2]);
$this->preLoadInfo = $data;
return TRUE;
}
return FALSE;
}
/**
* Gets the color set for transparency in GIF images.
*
* @return string|null
* A color string like '#rrggbb', or NULL if not set or not relevant.
*/
public function getTransparentColor() {
if (!$this->getResource() || $this->getType() != IMAGETYPE_GIF) {
return NULL;
}
// Find out if a transparent color is set, will return -1 if no
// transparent color has been defined in the image.
$transparent = imagecolortransparent($this->getResource());
if ($transparent >= 0) {
// Find out the number of colors in the image palette. It will be 0 for
// truecolor images.
$palette_size = imagecolorstotal($this->getResource());
if ($palette_size == 0 || $transparent < $palette_size) {
// Return the transparent color, either if it is a truecolor image
// or if the transparent color is part of the palette.
// Since the index of the transparent color is a property of the
// image rather than of the palette, it is possible that an image
// could be created with this index set outside the palette size.
// (see http://stackoverflow.com/a/3898007).
$rgb = imagecolorsforindex($this->getResource(), $transparent);
unset($rgb['alpha']);
return Color::rgbToHex($rgb);
}
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function getWidth() {
if ($this->preLoadInfo) {
return $this->preLoadInfo[0];
}
elseif ($res = $this->getResource()) {
return imagesx($res);
}
else {
return NULL;
}
}
/**
* {@inheritdoc}
*/
public function getHeight() {
if ($this->preLoadInfo) {
return $this->preLoadInfo[1];
}
elseif ($res = $this->getResource()) {
return imagesy($res);
}
else {
return NULL;
}
}
/**
* Gets the PHP type of the image.
*
* @return int
* The image type represented by a PHP IMAGETYPE_* constant (e.g.
* IMAGETYPE_JPEG).
*/
public function getType() {
return $this->type;
}
/**
* Sets the PHP type of the image.
*
* @param int $type
* The image type represented by a PHP IMAGETYPE_* constant (e.g.
* IMAGETYPE_JPEG).
*
* @return $this
*/
public function setType($type) {
if (in_array($type, static::supportedTypes())) {
$this->type = $type;
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getMimeType() {
return $this->getType() ? image_type_to_mime_type($this->getType()) : '';
}
/**
* {@inheritdoc}
*/
public function getRequirements() {
$requirements = array();
$info = gd_info();
$requirements['version'] = array(
'title' => t('GD library'),
'value' => $info['GD Version'],
);
// Check for filter and rotate support.
if (!function_exists('imagefilter') || !function_exists('imagerotate')) {
$requirements['version']['severity'] = REQUIREMENT_WARNING;
$requirements['version']['description'] = t('The GD Library for PHP is enabled, but was compiled without support for functions used by the rotate and desaturate effects. It was probably compiled using the official GD libraries from http://www.libgd.org instead of the GD library bundled with PHP. You should recompile PHP --with-gd using the bundled GD library. See <a href="http://php.net/manual/book.image.php">the PHP manual</a>.');
}
return $requirements;
}
/**
* {@inheritdoc}
*/
public static function isAvailable() {
// GD2 support is available.
return function_exists('imagegd2');
}
/**
* {@inheritdoc}
*/
public static function getSupportedExtensions() {
$extensions = array();
foreach (static::supportedTypes() as $image_type) {
// @todo Automatically fetch possible extensions for each mime type.
// @see https://www.drupal.org/node/2311679
$extension = Unicode::strtolower(image_type_to_extension($image_type, FALSE));
$extensions[] = $extension;
// Add some known similar extensions.
if ($extension === 'jpeg') {
$extensions[] = 'jpg';
$extensions[] = 'jpe';
}
}
return $extensions;
}
/**
* Returns the IMAGETYPE_xxx constant for the given extension.
*
* This is the reverse of the image_type_to_extension() function.
*
* @param string $extension
* The extension to get the IMAGETYPE_xxx constant for.
*
* @return int
* The IMAGETYPE_xxx constant for the given extension, or IMAGETYPE_UNKNOWN
* for unsupported extensions.
*
* @see image_type_to_extension()
*/
public function extensionToImageType($extension) {
if (in_array($extension, ['jpe', 'jpg'])) {
$extension = 'jpeg';
}
foreach ($this->supportedTypes() as $type) {
if (image_type_to_extension($type, FALSE) === $extension) {
return $type;
}
}
return IMAGETYPE_UNKNOWN;
}
/**
* Returns a list of image types supported by the toolkit.
*
* @return array
* An array of available image types. An image type is represented by a PHP
* IMAGETYPE_* constant (e.g. IMAGETYPE_JPEG, IMAGETYPE_PNG, etc.).
*/
protected static function supportedTypes() {
return array(IMAGETYPE_PNG, IMAGETYPE_JPEG, IMAGETYPE_GIF);
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace Drupal\system\Plugin\ImageToolkit\Operation\gd;
/**
* Defines GD2 convert operation.
*
* @ImageToolkitOperation(
* id = "gd_convert",
* toolkit = "gd",
* operation = "convert",
* label = @Translation("Convert"),
* description = @Translation("Instructs the toolkit to save the image with a specified extension.")
* )
*/
class Convert extends GDImageToolkitOperationBase {
/**
* {@inheritdoc}
*/
protected function arguments() {
return array(
'extension' => array(
'description' => 'The new extension of the converted image',
),
);
}
/**
* {@inheritdoc}
*/
protected function validateArguments(array $arguments) {
if (!in_array($arguments['extension'], $this->getToolkit()->getSupportedExtensions())) {
throw new \InvalidArgumentException("Invalid extension ({$arguments['extension']}) specified for the image 'convert' operation");
}
return $arguments;
}
/**
* {@inheritdoc}
*/
protected function execute(array $arguments) {
// Create a new resource of the required dimensions and format, and copy
// the original resource on it with resampling. Destroy the original
// resource upon success.
$width = $this->getToolkit()->getWidth();
$height = $this->getToolkit()->getHeight();
$original_resource = $this->getToolkit()->getResource();
$original_type = $this->getToolkit()->getType();
$data = array(
'width' => $width,
'height' => $height,
'extension' => $arguments['extension'],
'transparent_color' => $this->getToolkit()->getTransparentColor(),
'is_temp' => TRUE,
);
if ($this->getToolkit()->apply('create_new', $data)) {
if (imagecopyresampled($this->getToolkit()->getResource(), $original_resource, 0, 0, 0, 0, $width, $height, $width, $height)) {
imagedestroy($original_resource);
return TRUE;
}
// In case of error, reset resource and type to as it was.
$this->getToolkit()->setResource($original_resource);
$this->getToolkit()->setType($original_type);
}
return FALSE;
}
}

View file

@ -0,0 +1,134 @@
<?php
namespace Drupal\system\Plugin\ImageToolkit\Operation\gd;
use Drupal\Component\Utility\Color;
/**
* Defines GD2 create_new image operation.
*
* @ImageToolkitOperation(
* id = "gd_create_new",
* toolkit = "gd",
* operation = "create_new",
* label = @Translation("Set a new image"),
* description = @Translation("Creates a new transparent resource and sets it for the image.")
* )
*/
class CreateNew extends GDImageToolkitOperationBase {
/**
* {@inheritdoc}
*/
protected function arguments() {
return array(
'width' => array(
'description' => 'The width of the image, in pixels',
),
'height' => array(
'description' => 'The height of the image, in pixels',
),
'extension' => array(
'description' => 'The extension of the image file (e.g. png, gif, etc.)',
'required' => FALSE,
'default' => 'png',
),
'transparent_color' => array(
'description' => 'The RGB hex color for GIF transparency',
'required' => FALSE,
'default' => '#ffffff',
),
'is_temp' => array(
'description' => 'If TRUE, this operation is being used to create a temporary image by another GD operation. After performing its function, the caller is responsible for destroying the original GD resource.',
'required' => FALSE,
'default' => FALSE,
),
);
}
/**
* {@inheritdoc}
*/
protected function validateArguments(array $arguments) {
// Assure extension is supported.
if (!in_array($arguments['extension'], $this->getToolkit()->getSupportedExtensions())) {
throw new \InvalidArgumentException("Invalid extension ('{$arguments['extension']}') specified for the image 'convert' operation");
}
// Assure integers for width and height.
$arguments['width'] = (int) round($arguments['width']);
$arguments['height'] = (int) round($arguments['height']);
// Fail when width or height are 0 or negative.
if ($arguments['width'] <= 0) {
throw new \InvalidArgumentException("Invalid width ('{$arguments['width']}') specified for the image 'create_new' operation");
}
if ($arguments['height'] <= 0) {
throw new \InvalidArgumentException("Invalid height ({$arguments['height']}) specified for the image 'create_new' operation");
}
// Assure transparent color is a valid hex string.
if ($arguments['transparent_color'] && !Color::validateHex($arguments['transparent_color'])) {
throw new \InvalidArgumentException("Invalid transparent color ({$arguments['transparent_color']}) specified for the image 'create_new' operation");
}
return $arguments;
}
/**
* {@inheritdoc}
*/
protected function execute(array $arguments) {
// Get the image type.
$type = $this->getToolkit()->extensionToImageType($arguments['extension']);
// Store the original GD resource.
$original_res = $this->getToolkit()->getResource();
// Create the resource.
if (!$res = imagecreatetruecolor($arguments['width'], $arguments['height'])) {
return FALSE;
}
// Fill the resource with transparency as possible.
switch ($type) {
case IMAGETYPE_PNG:
imagealphablending($res, FALSE);
$transparency = imagecolorallocatealpha($res, 0, 0, 0, 127);
imagefill($res, 0, 0, $transparency);
imagealphablending($res, TRUE);
imagesavealpha($res, TRUE);
break;
case IMAGETYPE_GIF:
if (empty($arguments['transparent_color'])) {
// No transparency color specified, fill white transparent.
$fill_color = imagecolorallocatealpha($res, 255, 255, 255, 127);
}
else {
$fill_rgb = Color::hexToRgb($arguments['transparent_color']);
$fill_color = imagecolorallocatealpha($res, $fill_rgb['red'], $fill_rgb['green'], $fill_rgb['blue'], 127);
imagecolortransparent($res, $fill_color);
}
imagefill($res, 0, 0, $fill_color);
break;
case IMAGETYPE_JPEG:
imagefill($res, 0, 0, imagecolorallocate($res, 255, 255, 255));
break;
}
// Update the toolkit properties.
$this->getToolkit()->setType($type);
$this->getToolkit()->setResource($res);
// Destroy the original resource if it is not needed by other operations.
if (!$arguments['is_temp'] && is_resource($original_res)) {
imagedestroy($original_res);
}
return TRUE;
}
}

View file

@ -0,0 +1,102 @@
<?php
namespace Drupal\system\Plugin\ImageToolkit\Operation\gd;
/**
* Defines GD2 Crop operation.
*
* @ImageToolkitOperation(
* id = "gd_crop",
* toolkit = "gd",
* operation = "crop",
* label = @Translation("Crop"),
* description = @Translation("Crops an image to a rectangle specified by the given dimensions.")
* )
*/
class Crop extends GDImageToolkitOperationBase {
/**
* {@inheritdoc}
*/
protected function arguments() {
return array(
'x' => array(
'description' => 'The starting x offset at which to start the crop, in pixels',
),
'y' => array(
'description' => 'The starting y offset at which to start the crop, in pixels',
),
'width' => array(
'description' => 'The width of the cropped area, in pixels',
'required' => FALSE,
'default' => NULL,
),
'height' => array(
'description' => 'The height of the cropped area, in pixels',
'required' => FALSE,
'default' => NULL,
),
);
}
/**
* {@inheritdoc}
*/
protected function validateArguments(array $arguments) {
// Assure at least one dimension.
if (empty($arguments['width']) && empty($arguments['height'])) {
throw new \InvalidArgumentException("At least one dimension ('width' or 'height') must be provided to the image 'crop' operation");
}
// Preserve aspect.
$aspect = $this->getToolkit()->getHeight() / $this->getToolkit()->getWidth();
$arguments['height'] = empty($arguments['height']) ? $arguments['width'] * $aspect : $arguments['height'];
$arguments['width'] = empty($arguments['width']) ? $arguments['height'] / $aspect : $arguments['width'];
// Assure integers for all arguments.
foreach (array('x', 'y', 'width', 'height') as $key) {
$arguments[$key] = (int) round($arguments[$key]);
}
// Fail when width or height are 0 or negative.
if ($arguments['width'] <= 0) {
throw new \InvalidArgumentException("Invalid width ('{$arguments['width']}') specified for the image 'crop' operation");
}
if ($arguments['height'] <= 0) {
throw new \InvalidArgumentException("Invalid height ('{$arguments['height']}') specified for the image 'crop' operation");
}
return $arguments;
}
/**
* {@inheritdoc}
*/
protected function execute(array $arguments) {
// Create a new resource of the required dimensions, and copy and resize
// the original resource on it with resampling. Destroy the original
// resource upon success.
$original_resource = $this->getToolkit()->getResource();
$data = array(
'width' => $arguments['width'],
'height' => $arguments['height'],
'extension' => image_type_to_extension($this->getToolkit()->getType(), FALSE),
'transparent_color' => $this->getToolkit()->getTransparentColor(),
'is_temp' => TRUE,
);
if ($this->getToolkit()->apply('create_new', $data)) {
if (imagecopyresampled($this->getToolkit()->getResource(), $original_resource, 0, 0, $arguments['x'], $arguments['y'], $arguments['width'], $arguments['height'], $arguments['width'], $arguments['height'])) {
imagedestroy($original_resource);
return TRUE;
}
else {
// In case of failure, destroy the temporary resource and restore
// the original one.
imagedestroy($this->getToolkit()->getResource());
$this->getToolkit()->setResource($original_resource);
}
}
return FALSE;
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace Drupal\system\Plugin\ImageToolkit\Operation\gd;
/**
* Defines GD2 Desaturate operation.
*
* @ImageToolkitOperation(
* id = "gd_desaturate",
* toolkit = "gd",
* operation = "desaturate",
* label = @Translation("Desaturate"),
* description = @Translation("Converts an image to grayscale.")
* )
*/
class Desaturate extends GDImageToolkitOperationBase {
/**
* {@inheritdoc}
*/
protected function arguments() {
// This operation does not use any parameters.
return array();
}
/**
* {@inheritdoc}
*/
protected function execute(array $arguments) {
// PHP installations using non-bundled GD do not have imagefilter.
if (!function_exists('imagefilter')) {
$this->logger->notice("The image '@file' could not be desaturated because the imagefilter() function is not available in this PHP installation.", array('@file' => $this->getToolkit()->getSource()));
return FALSE;
}
return imagefilter($this->getToolkit()->getResource(), IMG_FILTER_GRAYSCALE);
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace Drupal\system\Plugin\ImageToolkit\Operation\gd;
use Drupal\Core\ImageToolkit\ImageToolkitOperationBase;
abstract class GDImageToolkitOperationBase extends ImageToolkitOperationBase {
/**
* The correctly typed image toolkit for GD operations.
*
* @return \Drupal\system\Plugin\ImageToolkit\GDToolkit
*/
protected function getToolkit() {
return parent::getToolkit();
}
}

View file

@ -0,0 +1,81 @@
<?php
namespace Drupal\system\Plugin\ImageToolkit\Operation\gd;
/**
* Defines GD2 resize operation.
*
* @ImageToolkitOperation(
* id = "gd_resize",
* toolkit = "gd",
* operation = "resize",
* label = @Translation("Resize"),
* description = @Translation("Resizes an image to the given dimensions (ignoring aspect ratio).")
* )
*/
class Resize extends GDImageToolkitOperationBase {
/**
* {@inheritdoc}
*/
protected function arguments() {
return array(
'width' => array(
'description' => 'The new width of the resized image, in pixels',
),
'height' => array(
'description' => 'The new height of the resized image, in pixels',
),
);
}
/**
* {@inheritdoc}
*/
protected function validateArguments(array $arguments) {
// Assure integers for all arguments.
$arguments['width'] = (int) round($arguments['width']);
$arguments['height'] = (int) round($arguments['height']);
// Fail when width or height are 0 or negative.
if ($arguments['width'] <= 0) {
throw new \InvalidArgumentException("Invalid width ('{$arguments['width']}') specified for the image 'resize' operation");
}
if ($arguments['height'] <= 0) {
throw new \InvalidArgumentException("Invalid height ('{$arguments['height']}') specified for the image 'resize' operation");
}
return $arguments;
}
/**
* {@inheritdoc}
*/
protected function execute(array $arguments = array()) {
// Create a new resource of the required dimensions, and copy and resize
// the original resource on it with resampling. Destroy the original
// resource upon success.
$original_resource = $this->getToolkit()->getResource();
$data = array(
'width' => $arguments['width'],
'height' => $arguments['height'],
'extension' => image_type_to_extension($this->getToolkit()->getType(), FALSE),
'transparent_color' => $this->getToolkit()->getTransparentColor(),
'is_temp' => TRUE,
);
if ($this->getToolkit()->apply('create_new', $data)) {
if (imagecopyresampled($this->getToolkit()->getResource(), $original_resource, 0, 0, 0, 0, $arguments['width'], $arguments['height'], imagesx($original_resource), imagesy($original_resource))) {
imagedestroy($original_resource);
return TRUE;
}
else {
// In case of failure, destroy the temporary resource and restore
// the original one.
imagedestroy($this->getToolkit()->getResource());
$this->getToolkit()->setResource($original_resource);
}
}
return FALSE;
}
}

View file

@ -0,0 +1,117 @@
<?php
namespace Drupal\system\Plugin\ImageToolkit\Operation\gd;
use Drupal\Component\Utility\Color;
/**
* Defines GD2 rotate operation.
*
* @ImageToolkitOperation(
* id = "gd_rotate",
* toolkit = "gd",
* operation = "rotate",
* label = @Translation("Rotate"),
* description = @Translation("Rotates an image by the given number of degrees.")
* )
*/
class Rotate extends GDImageToolkitOperationBase {
/**
* {@inheritdoc}
*/
protected function arguments() {
return array(
'degrees' => array(
'description' => 'The number of (clockwise) degrees to rotate the image',
),
'background' => array(
'description' => "A string specifying the hexadecimal color code to use as background for the uncovered area of the image after the rotation. E.g. '#000000' for black, '#ff00ff' for magenta, and '#ffffff' for white. For images that support transparency, this will default to transparent white",
'required' => FALSE,
'default' => NULL,
),
);
}
/**
* {@inheritdoc}
*/
protected function validateArguments(array $arguments) {
// PHP 5.5 GD bug: https://bugs.php.net/bug.php?id=65148: To prevent buggy
// behavior on negative multiples of 90 degrees we convert any negative
// angle to a positive one between 0 and 360 degrees.
$arguments['degrees'] -= floor($arguments['degrees'] / 360) * 360;
// Validate or set background color argument.
if (!empty($arguments['background'])) {
// Validate the background color: Color::hexToRgb does so for us.
$background = Color::hexToRgb($arguments['background']) + array( 'alpha' => 0 );
}
else {
// Background color is not specified: use transparent white as background.
$background = array('red' => 255, 'green' => 255, 'blue' => 255, 'alpha' => 127);
}
// Store the color index for the background as that is what GD uses.
$arguments['background_idx'] = imagecolorallocatealpha($this->getToolkit()->getResource(), $background['red'], $background['green'], $background['blue'], $background['alpha']);
if ($this->getToolkit()->getType() === IMAGETYPE_GIF) {
// GIF does not work with a transparency channel, but can define 1 color
// in its palette to act as transparent.
// Get the current transparent color, if any.
$gif_transparent_id = imagecolortransparent($this->getToolkit()->getResource());
if ($gif_transparent_id !== -1) {
// The gif already has a transparent color set: remember it to set it on
// the rotated image as well.
$arguments['gif_transparent_color'] = imagecolorsforindex($this->getToolkit()->getResource(), $gif_transparent_id);
if ($background['alpha'] >= 127) {
// We want a transparent background: use the color already set to act
// as transparent, as background.
$arguments['background_idx'] = $gif_transparent_id;
}
}
else {
// The gif does not currently have a transparent color set.
if ($background['alpha'] >= 127) {
// But as the background is transparent, it should get one.
$arguments['gif_transparent_color'] = $background;
}
}
}
return $arguments;
}
/**
* {@inheritdoc}
*/
protected function execute(array $arguments) {
// PHP installations using non-bundled GD do not have imagerotate.
if (!function_exists('imagerotate')) {
$this->logger->notice('The image %file could not be rotated because the imagerotate() function is not available in this PHP installation.', array('%file' => $this->getToolkit()->getSource()));
return FALSE;
}
// Stores the original GD resource.
$original_res = $this->getToolkit()->getResource();
if ($new_res = imagerotate($this->getToolkit()->getResource(), 360 - $arguments['degrees'], $arguments['background_idx'])) {
$this->getToolkit()->setResource($new_res);
imagedestroy($original_res);
// GIFs need to reassign the transparent color after performing the
// rotate, but only do so, if the image already had transparency of its
// own, or the rotate added a transparent background.
if (!empty($arguments['gif_transparent_color'])) {
$transparent_idx = imagecolorexactalpha($this->getToolkit()->getResource(), $arguments['gif_transparent_color']['red'], $arguments['gif_transparent_color']['green'], $arguments['gif_transparent_color']['blue'], $arguments['gif_transparent_color']['alpha']);
imagecolortransparent($this->getToolkit()->getResource(), $transparent_idx);
}
return TRUE;
}
return FALSE;
}
}

View file

@ -0,0 +1,92 @@
<?php
namespace Drupal\system\Plugin\ImageToolkit\Operation\gd;
/**
* Defines GD2 Scale operation.
*
* @ImageToolkitOperation(
* id = "gd_scale",
* toolkit = "gd",
* operation = "scale",
* label = @Translation("Scale"),
* description = @Translation("Scales an image while maintaining aspect ratio. The resulting image can be smaller for one or both target dimensions.")
* )
*/
class Scale extends Resize {
/**
* {@inheritdoc}
*/
protected function arguments() {
return array(
'width' => array(
'description' => 'The target width, in pixels. This value is omitted then the scaling will based only on the height value',
'required' => FALSE,
'default' => NULL,
),
'height' => array(
'description' => 'The target height, in pixels. This value is omitted then the scaling will based only on the width value',
'required' => FALSE,
'default' => NULL,
),
'upscale' => array(
'description' => 'Boolean indicating that files smaller than the dimensions will be scaled up. This generally results in a low quality image',
'required' => FALSE,
'default' => FALSE,
),
);
}
/**
* {@inheritdoc}
*/
protected function validateArguments(array $arguments) {
// Assure at least one dimension.
if (empty($arguments['width']) && empty($arguments['height'])) {
throw new \InvalidArgumentException("At least one dimension ('width' or 'height') must be provided to the image 'scale' operation");
}
// Calculate one of the dimensions from the other target dimension,
// ensuring the same aspect ratio as the source dimensions. If one of the
// target dimensions is missing, that is the one that is calculated. If both
// are specified then the dimension calculated is the one that would not be
// calculated to be bigger than its target.
$aspect = $this->getToolkit()->getHeight() / $this->getToolkit()->getWidth();
if (($arguments['width'] && !$arguments['height']) || ($arguments['width'] && $arguments['height'] && $aspect < $arguments['height'] / $arguments['width'])) {
$arguments['height'] = (int) round($arguments['width'] * $aspect);
}
else {
$arguments['width'] = (int) round($arguments['height'] / $aspect);
}
// Assure integers for all arguments.
$arguments['width'] = (int) round($arguments['width']);
$arguments['height'] = (int) round($arguments['height']);
// Fail when width or height are 0 or negative.
if ($arguments['width'] <= 0) {
throw new \InvalidArgumentException("Invalid width ('{$arguments['width']}') specified for the image 'scale' operation");
}
if ($arguments['height'] <= 0) {
throw new \InvalidArgumentException("Invalid height ('{$arguments['height']}') specified for the image 'scale' operation");
}
return $arguments;
}
/**
* {@inheritdoc}
*/
protected function execute(array $arguments = array()) {
// Don't scale if we don't change the dimensions at all.
if ($arguments['width'] !== $this->getToolkit()->getWidth() || $arguments['height'] !== $this->getToolkit()->getHeight()) {
// Don't upscale if the option isn't enabled.
if ($arguments['upscale'] || ($arguments['width'] <= $this->getToolkit()->getWidth() && $arguments['height'] <= $this->getToolkit()->getHeight())) {
return parent::execute($arguments);
}
}
return TRUE;
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace Drupal\system\Plugin\ImageToolkit\Operation\gd;
/**
* Defines GD2 Scale and crop operation.
*
* @ImageToolkitOperation(
* id = "gd_scale_and_crop",
* toolkit = "gd",
* operation = "scale_and_crop",
* label = @Translation("Scale and crop"),
* description = @Translation("Scales an image to the exact width and height given. This plugin achieves the target aspect ratio by cropping the original image equally on both sides, or equally on the top and bottom. This function is useful to create uniform sized avatars from larger images.")
* )
*/
class ScaleAndCrop extends GDImageToolkitOperationBase {
/**
* {@inheritdoc}
*/
protected function arguments() {
return array(
'width' => array(
'description' => 'The target width, in pixels',
),
'height' => array(
'description' => 'The target height, in pixels',
),
);
}
/**
* {@inheritdoc}
*/
protected function validateArguments(array $arguments) {
$actualWidth = $this->getToolkit()->getWidth();
$actualHeight = $this->getToolkit()->getHeight();
$scaleFactor = max($arguments['width'] / $actualWidth, $arguments['height'] / $actualHeight);
$arguments['x'] = (int) round(($actualWidth * $scaleFactor - $arguments['width']) / 2);
$arguments['y'] = (int) round(($actualHeight * $scaleFactor - $arguments['height']) / 2);
$arguments['resize'] = array(
'width' => (int) round($actualWidth * $scaleFactor),
'height' => (int) round($actualHeight * $scaleFactor),
);
// Fail when width or height are 0 or negative.
if ($arguments['width'] <= 0) {
throw new \InvalidArgumentException("Invalid width ('{$arguments['width']}') specified for the image 'scale_and_crop' operation");
}
if ($arguments['height'] <= 0) {
throw new \InvalidArgumentException("Invalid height ('{$arguments['height']}') specified for the image 'scale_and_crop' operation");
}
return $arguments;
}
/**
* {@inheritdoc}
*/
protected function execute(array $arguments = array()) {
return $this->getToolkit()->apply('resize', $arguments['resize'])
&& $this->getToolkit()->apply('crop', $arguments);
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Drupal\system\Plugin\migrate\destination;
use Drupal\Core\Entity\EntityInterface;
use Drupal\migrate\Plugin\migrate\destination\EntityConfigBase;
/**
* @MigrateDestination(
* id = "entity:date_format"
* )
*/
class EntityDateFormat extends EntityConfigBase {
/**
* {@inheritdoc}
*
* @param \Drupal\Core\Datetime\DateFormatInterface $entity
* The date entity.
*/
protected function updateEntityProperty(EntityInterface $entity, array $parents, $value) {
if ($parents[0] == 'pattern') {
$entity->setPattern($value);
}
else {
parent::updateEntityProperty($entity, $parents, $value);
}
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\system\Plugin\migrate\process\d6;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Rename blog and forum permissions to be consistent with other content types.
*
* @MigrateProcessPlugin(
* id = "system_update_7000"
* )
*/
class SystemUpdate7000 extends ProcessPluginBase {
/**
* {@inheritdoc}
*
* Rename blog and forum permissions to be consistent with other content types.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$value = preg_replace('/(?<=^|,\ )create\ blog\ entries(?=,|$)/', 'create blog content', $value);
$value = preg_replace('/(?<=^|,\ )edit\ own\ blog\ entries(?=,|$)/', 'edit own blog content', $value);
$value = preg_replace('/(?<=^|,\ )edit\ any\ blog\ entry(?=,|$)/', 'edit any blog content', $value);
$value = preg_replace('/(?<=^|,\ )delete\ own\ blog\ entries(?=,|$)/', 'delete own blog content', $value);
$value = preg_replace('/(?<=^|,\ )delete\ any\ blog\ entry(?=,|$)/', 'delete any blog content', $value);
$value = preg_replace('/(?<=^|,\ )create\ forum\ topics(?=,|$)/', 'create forum content', $value);
$value = preg_replace('/(?<=^|,\ )delete\ any\ forum\ topic(?=,|$)/', 'delete any forum content', $value);
$value = preg_replace('/(?<=^|,\ )delete\ own\ forum\ topics(?=,|$)/', 'delete own forum content', $value);
$value = preg_replace('/(?<=^|,\ )edit\ any\ forum\ topic(?=,|$)/', 'edit any forum content', $value);
$value = preg_replace('/(?<=^|,\ )edit\ own\ forum\ topics(?=,|$)/', 'edit own forum content', $value);
return $value;
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace Drupal\system\Plugin\migrate\process\d6;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Process the D6 Timezone offset into a D8 compatible timezone name.
*
* @MigrateProcessPlugin(
* id = "timezone"
* )
*/
class TimeZone extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$offset = $value;
// Convert the integer value of the offset (which can be either
// negative or positive) to a timezone name.
// Note: Daylight saving time is not to be used.
$timezone_name = timezone_name_from_abbr('', intval($offset), 0);
if (!$timezone_name) {
$timezone_name = 'UTC';
}
return $timezone_name;
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Drupal\system\Plugin\migrate\source;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Menu source from database.
*
* @MigrateSource(
* id = "menu",
* source_provider = "menu"
* )
*/
class Menu extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
return $this->select('menu_custom', 'm')->fields('m');
}
/**
* {@inheritdoc}
*/
public function fields() {
return array(
'menu_name' => $this->t('The menu name. Primary key.'),
'title' => $this->t('The human-readable name of the menu.'),
'description' => $this->t('A description of the menu'),
);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['menu_name']['type'] = 'string';
return $ids;
}
}

View file

@ -0,0 +1,504 @@
<?php
namespace Drupal\system\Plugin\views\field;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Routing\RedirectDestinationTrait;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\views\Entity\Render\EntityTranslationRenderTrait;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\field\FieldPluginBase;
use Drupal\views\Plugin\views\field\UncacheableFieldHandlerTrait;
use Drupal\views\Plugin\views\style\Table;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a actions-based bulk operation form element.
*
* @ViewsField("bulk_form")
*/
class BulkForm extends FieldPluginBase implements CacheableDependencyInterface {
use RedirectDestinationTrait;
use UncacheableFieldHandlerTrait;
use EntityTranslationRenderTrait;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The action storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $actionStorage;
/**
* An array of actions that can be executed.
*
* @var \Drupal\system\ActionConfigEntityInterface[]
*/
protected $actions = array();
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a new BulkForm 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\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entity_manager;
$this->actionStorage = $entity_manager->getStorage('action');
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager'),
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
$entity_type = $this->getEntityType();
// Filter the actions to only include those for this entity type.
$this->actions = array_filter($this->actionStorage->loadMultiple(), function ($action) use ($entity_type) {
return $action->getType() == $entity_type;
});
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
// @todo Consider making the bulk operation form cacheable. See
// https://www.drupal.org/node/2503009.
return 0;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return $this->languageManager->isMultilingual() ? $this->getEntityTranslationRenderer()->getCacheContexts() : [];
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return [];
}
/**
* {@inheritdoc}
*/
public function getEntityTypeId() {
return $this->getEntityType();
}
/**
* {@inheritdoc}
*/
protected function getEntityManager() {
return $this->entityManager;
}
/**
* {@inheritdoc}
*/
protected function getLanguageManager() {
return $this->languageManager;
}
/**
* {@inheritdoc}
*/
protected function getView() {
return $this->view;
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['action_title'] = array('default' => $this->t('Action'));
$options['include_exclude'] = array(
'default' => 'exclude',
);
$options['selected_actions'] = array(
'default' => array(),
);
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$form['action_title'] = array(
'#type' => 'textfield',
'#title' => $this->t('Action title'),
'#default_value' => $this->options['action_title'],
'#description' => $this->t('The title shown above the actions dropdown.'),
);
$form['include_exclude'] = array(
'#type' => 'radios',
'#title' => $this->t('Available actions'),
'#options' => array(
'exclude' => $this->t('All actions, except selected'),
'include' => $this->t('Only selected actions'),
),
'#default_value' => $this->options['include_exclude'],
);
$form['selected_actions'] = array(
'#type' => 'checkboxes',
'#title' => $this->t('Selected actions'),
'#options' => $this->getBulkOptions(FALSE),
'#default_value' => $this->options['selected_actions'],
);
parent::buildOptionsForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function validateOptionsForm(&$form, FormStateInterface $form_state) {
parent::validateOptionsForm($form, $form_state);
$selected_actions = $form_state->getValue(array('options', 'selected_actions'));
$form_state->setValue(array('options', 'selected_actions'), array_values(array_filter($selected_actions)));
}
/**
* {@inheritdoc}
*/
public function preRender(&$values) {
parent::preRender($values);
// If the view is using a table style, provide a placeholder for a
// "select all" checkbox.
if (!empty($this->view->style_plugin) && $this->view->style_plugin instanceof Table) {
// Add the tableselect css classes.
$this->options['element_label_class'] .= 'select-all';
// Hide the actual label of the field on the table header.
$this->options['label'] = '';
}
}
/**
* {@inheritdoc}
*/
public function getValue(ResultRow $row, $field = NULL) {
return '<!--form-item-' . $this->options['id'] . '--' . $row->index . '-->';
}
/**
* Form constructor for the bulk form.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function viewsForm(&$form, FormStateInterface $form_state) {
// Make sure we do not accidentally cache this form.
// @todo Evaluate this again in https://www.drupal.org/node/2503009.
$form['#cache']['max-age'] = 0;
// Add the tableselect javascript.
$form['#attached']['library'][] = 'core/drupal.tableselect';
$use_revision = array_key_exists('revision', $this->view->getQuery()->getEntityTableInfo());
// Only add the bulk form options and buttons if there are results.
if (!empty($this->view->result)) {
// Render checkboxes for all rows.
$form[$this->options['id']]['#tree'] = TRUE;
foreach ($this->view->result as $row_index => $row) {
$entity = $this->getEntityTranslation($this->getEntity($row), $row);
$form[$this->options['id']][$row_index] = array(
'#type' => 'checkbox',
// We are not able to determine a main "title" for each row, so we can
// only output a generic label.
'#title' => $this->t('Update this item'),
'#title_display' => 'invisible',
'#default_value' => !empty($form_state->getValue($this->options['id'])[$row_index]) ? 1 : NULL,
'#return_value' => $this->calculateEntityBulkFormKey($entity, $use_revision),
);
}
// Replace the form submit button label.
$form['actions']['submit']['#value'] = $this->t('Apply to selected items');
// Ensure a consistent container for filters/operations in the view header.
$form['header'] = array(
'#type' => 'container',
'#weight' => -100,
);
// Build the bulk operations action widget for the header.
// Allow themes to apply .container-inline on this separate container.
$form['header'][$this->options['id']] = array(
'#type' => 'container',
);
$form['header'][$this->options['id']]['action'] = array(
'#type' => 'select',
'#title' => $this->options['action_title'],
'#options' => $this->getBulkOptions(),
);
// Duplicate the form actions into the action container in the header.
$form['header'][$this->options['id']]['actions'] = $form['actions'];
}
else {
// Remove the default actions build array.
unset($form['actions']);
}
}
/**
* Returns the available operations for this form.
*
* @param bool $filtered
* (optional) Whether to filter actions to selected actions.
* @return array
* An associative array of operations, suitable for a select element.
*/
protected function getBulkOptions($filtered = TRUE) {
$options = array();
// Filter the action list.
foreach ($this->actions as $id => $action) {
if ($filtered) {
$in_selected = in_array($id, $this->options['selected_actions']);
// If the field is configured to include only the selected actions,
// skip actions that were not selected.
if (($this->options['include_exclude'] == 'include') && !$in_selected) {
continue;
}
// Otherwise, if the field is configured to exclude the selected
// actions, skip actions that were selected.
elseif (($this->options['include_exclude'] == 'exclude') && $in_selected) {
continue;
}
}
$options[$id] = $action->label();
}
return $options;
}
/**
* Submit handler for the bulk form.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
* Thrown when the user tried to access an action without access to it.
*/
public function viewsFormSubmit(&$form, FormStateInterface $form_state) {
if ($form_state->get('step') == 'views_form_views_form') {
// Filter only selected checkboxes.
$selected = array_filter($form_state->getValue($this->options['id']));
$entities = array();
$action = $this->actions[$form_state->getValue('action')];
$count = 0;
foreach ($selected as $bulk_form_key) {
$entity = $this->loadEntityFromBulkFormKey($bulk_form_key);
// Skip execution if the user did not have access.
if (!$action->getPlugin()->access($entity, $this->view->getUser())) {
$this->drupalSetMessage($this->t('No access to execute %action on the @entity_type_label %entity_label.', [
'%action' => $action->label(),
'@entity_type_label' => $entity->getEntityType()->getLabel(),
'%entity_label' => $entity->label()
]), 'error');
continue;
}
$count++;
$entities[$bulk_form_key] = $entity;
}
$action->execute($entities);
$operation_definition = $action->getPluginDefinition();
if (!empty($operation_definition['confirm_form_route_name'])) {
$options = array(
'query' => $this->getDestinationArray(),
);
$form_state->setRedirect($operation_definition['confirm_form_route_name'], array(), $options);
}
else {
// Don't display the message unless there are some elements affected and
// there is no confirmation form.
if ($count) {
drupal_set_message($this->formatPlural($count, '%action was applied to @count item.', '%action was applied to @count items.', array(
'%action' => $action->label(),
)));
}
}
}
}
/**
* Returns the message to be displayed when there are no selected items.
*
* @return string
* Message displayed when no items are selected.
*/
protected function emptySelectedMessage() {
return $this->t('No items selected.');
}
/**
* {@inheritdoc}
*/
public function viewsFormValidate(&$form, FormStateInterface $form_state) {
$selected = array_filter($form_state->getValue($this->options['id']));
if (empty($selected)) {
$form_state->setErrorByName('', $this->emptySelectedMessage());
}
}
/**
* {@inheritdoc}
*/
public function query() {
if ($this->languageManager->isMultilingual()) {
$this->getEntityTranslationRenderer()->query($this->query, $this->relationship);
}
}
/**
* {@inheritdoc}
*/
public function clickSortable() {
return FALSE;
}
/**
* Wraps drupal_set_message().
*/
protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
drupal_set_message($message, $type, $repeat);
}
/**
* Calculates a bulk form key.
*
* This generates a key that is used as the checkbox return value when
* submitting a bulk form. This key allows the entity for the row to be loaded
* totally independently of the executed view row.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to calculate a bulk form key for.
* @param bool $use_revision
* Whether the revision id should be added to the bulk form key. This should
* be set to TRUE only if the view is listing entity revisions.
*
* @return string
* The bulk form key representing the entity's id, language and revision (if
* applicable) as one string.
*
* @see self::loadEntityFromBulkFormKey()
*/
protected function calculateEntityBulkFormKey(EntityInterface $entity, $use_revision) {
$key_parts = [$entity->language()->getId(), $entity->id()];
if ($entity instanceof RevisionableInterface && $use_revision) {
$key_parts[] = $entity->getRevisionId();
}
// An entity ID could be an arbitrary string (although they are typically
// numeric). JSON then Base64 encoding ensures the bulk_form_key is
// safe to use in HTML, and that the key parts can be retrieved.
$key = json_encode($key_parts);
return base64_encode($key);
}
/**
* Loads an entity based on a bulk form key.
*
* @param string $bulk_form_key
* The bulk form key representing the entity's id, language and revision (if
* applicable) as one string.
*
* @return \Drupal\Core\Entity\EntityInterface
* The entity loaded in the state (language, optionally revision) specified
* as part of the bulk form key.
*/
protected function loadEntityFromBulkFormKey($bulk_form_key) {
$key = base64_decode($bulk_form_key);
$key_parts = json_decode($key);
$revision_id = NULL;
// If there are 3 items, vid will be last.
if (count($key_parts) === 3) {
$revision_id = array_pop($key_parts);
}
// The first two items will always be langcode and ID.
$id = array_pop($key_parts);
$langcode = array_pop($key_parts);
// Load the entity or a specific revision depending on the given key.
$storage = $this->entityManager->getStorage($this->getEntityType());
$entity = $revision_id ? $storage->loadRevision($revision_id) : $storage->load($id);
if ($entity instanceof TranslatableInterface) {
$entity = $entity->getTranslation($langcode);
}
return $entity;
}
}