htools-8.x-1.x-dev/src/Plugin/views/display/LayoutBuilder.php
src/Plugin/views/display/LayoutBuilder.php
<?php
namespace Drupal\htools\Plugin\views\display;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\Url;
use Drupal\path_alias\AliasManagerInterface;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\display\DisplayRouterInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\RouteCollection;
/**
* The plugin that handles an embed display.
*
* @ingroup views_display_plugins
*
* @todo: Wait until annotations/plugins support access methods.
* no_ui => !\Drupal::config('views.settings')->get('ui.show.display_embed'),
*
* @ViewsDisplay(
* id = "layout_builder",
* title = @Translation("Layout Builder"),
* help = @Translation("Provide a display which can be embedded using the
* views api."), theme = "views_view", uses_hook_block = FALSE,
* uses_menu_links = FALSE
* )
*/
class LayoutBuilder extends DisplayPluginBase implements DisplayRouterInterface {
/**
* Current path.
*
* @var \Drupal\Core\Path\CurrentPathStack
*/
protected $currentPath;
/**
* PrismaEmbed::aliasManager.
*
* @var \Drupal\Core\Path\AliasManagerInterface
*/
protected $aliasManager;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, CurrentPathStack $currentPath, AliasManagerInterface $aliasManager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->currentPath = $currentPath;
$this->aliasManager = $aliasManager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('path.current'),
$container->get('path_alias.manager')
);
}
/**
* {@inheritdoc}
*/
public function optionsSummary(&$categories, &$options) {
parent::optionsSummary($categories, $options);
$categories['page'] = [
'title' => $this->t('Page settings'),
'column' => 'second',
'build' => [
'#weight' => -10,
],
];
if ($this->getOption('path') === TRUE) {
$path = '';
}
else {
$path = strip_tags($this->getOption('path'));
}
if (empty($path)) {
$path = $this->t('No path is set (Current url will be use)');
}
else {
$path = '/' . $path;
}
$options['path'] = [
'category' => 'page',
'title' => $this->t('Path'),
'value' => views_ui_truncate($path, 50),
];
$regions = [];
foreach ($this->getOption('regions') as $reg => $v) {
if ($v) {
$regions[] = $reg;
}
}
$options['regions'] = [
'category' => 'page',
'title' => $this->t('Regions'),
'value' => !empty($regions) ? implode(', ', $regions) : 'configure',
];
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
switch ($form_state->get('section')) {
case 'path':
$url = Url::fromRoute('<none>', [], ['absolute' => TRUE]);
$form['#title'] .= $this->t('The menu path or URL of this view');
$form['path'] = [
'#type' => 'textfield',
'#title' => $this->t('Path'),
'#description' => $this->t('This view will be displayed by visiting this path on your site. You may use "%" in your URL to represent values that will be used for contextual filters: For example, "node/%/feed". If needed you can even specify named route parameters like taxonomy/term/%taxonomy_term'),
'#default_value' => $this->getOption('path'),
'#field_prefix' => '<span dir="ltr">' . $url->toString(),
'#field_suffix' => '</span>‎',
'#attributes' => ['dir' => LanguageInterface::DIRECTION_LTR],
// Account for the leading backslash.
'#maxlength' => 254,
];
break;
case 'regions':
$form['#title'] .= $this->t('The regions of view to want to show or hide');
$regions = static::getRegions();
$default = array_filter($this->getOption('regions'));
$form['regions'] = [
'#type' => 'checkboxes',
'#title' => $this->t('View Regions'),
'#default_value' => array_keys($default),
'#options' => $regions,
];
break;
}
}
public static function getRegions() {
return [
'header' => t('Header'),
'footer' => t('Footer'),
'empty' => t('Empty'),
'exposed' => t('Exposed form'),
'more' => t('More link'),
'feed_icons' => t('Feed Icons'),
'rows' => t('Rows of content'),
'pager' => t('Pager'),
'attachment_before' => t('Attachment before'),
'attachment_after' => t('Attachment after'),
];
}
/**
* {@inheritdoc}
*/
public function validateOptionsForm(&$form, FormStateInterface $form_state) {
parent::validateOptionsForm($form, $form_state);
if ($form_state->get('section') == 'path') {
$errors = $this->validatePath($form_state->getValue('path'));
foreach ($errors as $error) {
$form_state->setError($form['path'], $error);
}
// Automatically remove '/' and trailing whitespace from path.
$form_state->setValue('path', trim($form_state->getValue('path'), '/ '));
}
}
/**
* Validates the path of the display.
*
* @param string $path
* The path to validate.
*
* @return array
* A list of error strings.
*/
protected function validatePath($path) {
$errors = [];
if (strpos($path, '%') === 0) {
$errors[] = $this->t('"%" may not be used for the first segment of a path.');
}
$parsed_url = UrlHelper::parse($path);
if (!empty($parsed_url['query'])) {
$errors[] = $this->t('No query allowed.');
}
if (!parse_url('internal:/' . $path)) {
$errors[] = $this->t('Invalid path. Valid characters are alphanumerics as well as "-", ".", "_" and "~".');
}
$path_sections = explode('/', $path);
// Symfony routing does not allow to use numeric placeholders.
// @see \Symfony\Component\Routing\RouteCompiler
$numeric_placeholders = array_filter($path_sections, function ($section) {
return (preg_match('/^%(.*)/', $section, $matches)
&& is_numeric($matches[1]));
});
if (!empty($numeric_placeholders)) {
$errors[] = $this->t("Numeric placeholders may not be used. Please use plain placeholders (%).");
}
return $errors;
}
/**
* {@inheritdoc}
*/
public function submitOptionsForm(&$form, FormStateInterface $form_state) {
parent::submitOptionsForm($form, $form_state);
if ($form_state->get('section') === 'path') {
$this->setOption('path', $form_state->getValue('path'));
}
if ($form_state->get('section') === 'regions') {
$this->setOption('regions', $form_state->getValue('regions'));
}
}
/**
* {@inheritdoc}
*/
public function buildRenderable(array $args = [], $cache = TRUE) {
$build = parent::buildRenderable($args, $cache);
$build['#embed'] = TRUE;
return $build;
}
/**
* {@inheritdoc}
*/
public function hasPath() {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function elementPreRender(array $element) {
$element = parent::elementPreRender($element);
$regions = $this->getOption('regions');
foreach ($regions as $reg => $v) {
if (!$v) {
unset($element['#' . $reg]);
}
}
if (!empty($element['#exposed']) && empty($element['#exposed']['#build_id'])) {
foreach ($element['#exposed'] as $key => $region) {
$element['#exposed_region_' . $key] = $region;
}
unset($element['#exposed']);
}
return $element;
}
/**
* {@inheritdoc}
*/
public function execute() {
// Prior to this being called, the $view should already be set to this
// display, and arguments should be set on the view.
$this->view->build();
if (!empty($this->view->build_info['fail'])) {
throw new NotFoundHttpException();
}
if (!empty($this->view->build_info['denied'])) {
throw new AccessDeniedHttpException();
}
// And now render the view.
$render = $this->view->render();
return $render;
}
/**
* {@inheritdoc}
*/
public function collectRoutes(RouteCollection $collection) {
return [];
}
/**
* {@inheritdoc}
*/
public function alterRoutes(RouteCollection $collection) {
$view_route_names = [];
return $view_route_names;
}
/**
* {@inheritdoc}
*/
public function getUrlInfo() {
$url = Url::fromUserInput($this->getPath());
return $url;
}
/**
* {@inheritdoc}
*/
public function getPath() {
$path = $this->getOption('path');
if (empty($path)) {
$useAjax = $this->usesAJAX();
if ($useAjax) {
$request = $this->view->getRequest();
$params = $request->request->all();
// @TODO Read POST parameters from request object.
if (!empty($params['ajax_page_state']) &&
!empty($_POST['view_base_path'])) {
$path = $_POST['view_base_path'];
}
}
}
if (empty($path)) {
$current_path = $this->currentPath->getPath();
$path = $this->aliasManager->getAliasByPath($current_path);
}
return $path;
}
/**
* {@inheritdoc}
*/
public function getRouteName() {
// return empty routename for fix exposed form action url.
return '';
$url = Url::fromUserInput($this->getPath());
return $url->getRouteName();
}
/**
* {@inheritdoc}
*/
public function getAlteredRouteNames() {
return [];
}
/**
* {@inheritdoc}
*/
public function render() {
$build = parent::render();
$exposed_form_plugin_id = $this->view->display_handler->getPlugin('exposed_form')->getPluginId();
if ($exposed_form_plugin_id === 'exposed_form_region') {
$build['#theme'] = 'views_view__layout_builder__efr';
}
return $build;
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['path'] = ['default' => ''];
$options['regions'] = [
'default' => [
'header' => TRUE,
'footer' => TRUE,
'empty' => TRUE,
'exposed' => TRUE,
'more' => TRUE,
'feed_icons' => TRUE,
'rows' => TRUE,
'pager' => TRUE,
'attachment_before' => TRUE,
'attachment_after' => TRUE,
],
];
return $options;
}
}
