gridstack-8.x-2.5/src/GridStackManager.php
src/GridStackManager.php
<?php
namespace Drupal\gridstack;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Render\Element;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\blazy\Blazy;
use Drupal\blazy\BlazyManagerBase;
use Drupal\gridstack\Entity\GridStack;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Implements GridStackManagerInterface.
*/
class GridStackManager extends BlazyManagerBase implements GridStackManagerInterface, TrustedCallbackInterface {
/**
* The GridStack optionset.
*
* @var \Drupal\gridstack\Entity\GridStack
*/
protected $gridStackOptionset;
/**
* If should ungridstack, no js/css-driven layouts, just re-use templates.
*
* @var bool
*/
protected $unGridStack = FALSE;
/**
* The gridstack skin manager service.
*
* @var \Drupal\gridstack\GridStackSkinManagerInterface
*/
protected $skinManager;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$instance = parent::create($container);
$instance->setSkinManager($container->get('gridstack.skin_manager'));
return $instance;
}
/**
* Returns gridstack skin manager service.
*/
public function skinManager() {
return $this->skinManager;
}
/**
* Sets gridstack skin manager service.
*/
public function setSkinManager(GridStackSkinManagerInterface $skin_manager) {
$this->skinManager = $skin_manager;
return $this;
}
/**
* {@inheritdoc}
*/
public static function trustedCallbacks() {
return ['preRenderGridStack'];
}
/**
* {@inheritdoc}
*/
public function attach(array $attach = []) {
$load = parent::attach($attach);
$this->skinManager->attach($load, $attach);
$this->skinManager->attachSkin($load, $attach);
$this->moduleHandler->alter('gridstack_attach', $load, $attach);
return $load;
}
/**
* {@inheritdoc}
*/
public function boxAttributes(array &$settings, $current = 'grids') {
// Allows extenders to provide own grid attributes.
if (!empty($settings['ungridstack'])) {
return [];
}
return empty($settings['use_js']) ? GridStackAttribute::cssBox($this->gridStackOptionset, $settings, $current) : GridStackAttribute::jsBox($this->gridStackOptionset, $settings, $current);
}
/**
* {@inheritdoc}
*/
public function buildItems(array $build, array $regions = []) {
$settings = array_filter($build['settings']);
$grids = $this->gridStackOptionset->getEndBreakpointGrids();
$items = [];
// Cleans up container setting wrapper to not leak to children.
unset($settings['wrapper']);
foreach ($build['items'] as $delta => $item) {
$box = isset($item['box']) ? $item['box'] : [];
$attributes = isset($item['attributes']) ? $item['attributes'] : [];
$settings = isset($item['settings']) ? array_merge($settings, $item['settings']) : $settings;
$rid = GridStackDefault::regionId($delta);
$content = [
'box' => $box,
'caption' => isset($item['caption']) ? $item['caption'] : [],
];
$settings['delta'] = $settings['root_delta'] = $delta;
$attributes = NestedArray::mergeDeep($attributes, $this->boxAttributes($settings, 'grids'));
$content_attributes = [];
if ($grids && empty($settings['ungridstack'])) {
if (!empty($settings['use_framework'])) {
$content_attributes = GridStackAttribute::regionAttributes($settings, 'grids');
}
// Skips if more than we can chew, otherwise broken grid anyway.
// The grids is a fixed layout blueprint. The items is dynamic contents.
// Dynamic contents must obey the blueprint to stay harmonious.
if (!isset($grids[$delta])) {
continue;
}
// Layout Builder or Panels IPE only output for granted users.
if (!empty($settings['_access_ipe']) && $regions && isset($regions[$rid])) {
GridStackBuilder::adminAttributes($content['box'], $attributes, $content_attributes, $settings, $regions, $rid);
}
// Node contains the main grids/boxes.
$nested_grids = $this->gridStackOptionset->getNestedGridsByDelta($delta);
$is_nested = array_filter($nested_grids);
// Overrides wrapper via UI, if so configured.
if (isset($settings['regions'], $settings['regions'][$rid])) {
$settings = array_merge($settings, $settings['regions'][$rid]);
}
// Nested grids with preserved indices even if empty so to layout.
// Only Bootstrap/ Foundation has nested grids, not js-driven layouts.
if ($is_nested && !empty($box) && isset($box[0]['box'])) {
$attributes['class'][] = 'box--nester';
$settings['nested'] = TRUE;
$settings['root'] = FALSE;
$settings['use_inner'] = FALSE;
$content['box'] = $this->buildNestedItems($item, $delta, $nested_grids, $settings, $regions);
}
else {
$settings['nested'] = FALSE;
$settings['use_inner'] = TRUE;
}
}
$items[] = $this->buildItem($content, $delta, $attributes, $content_attributes, $settings);
unset($content);
}
return $items;
}
/**
* Provides nested items if so configured.
*/
protected function buildNestedItems($item, $delta, $grids, $settings, $regions = []) {
$items = [];
$nested = $item['box'];
// The nested elements.
foreach (array_keys($grids) as $gid) {
$rid = GridStackDefault::regionId($delta . '_' . $gid);
$settings['nested_delta'] = $gid;
$nested_box = $nested[$gid]['box'];
$nested_settings = isset($nested[$gid]['settings']) ? array_merge($settings, $nested[$gid]['settings']) : $settings;
$nested_attributes = isset($nested[$gid]['attributes']) ? $nested[$gid]['attributes'] : [];
$content_attributes = [];
if (!empty($settings['use_framework'])) {
$content_attributes = GridStackAttribute::regionAttributes($settings, 'nested');
}
$nested_settings['nested_id'] = ($delta + 1) . '-' . ($gid + 1);
$nested_settings['use_inner'] = TRUE;
if (isset($nested_settings['regions'], $nested_settings['regions'][$rid])) {
$nested_settings = array_merge($nested_settings, $nested_settings['regions'][$rid]);
}
// Layout Builder or Panels IPE integration only output for granted users.
if (!empty($settings['_access_ipe']) && $regions && isset($regions[$rid])) {
GridStackBuilder::adminAttributes($nested_box, $nested_attributes, $content_attributes, $nested_settings, $regions, $rid);
}
$nested_attributes = NestedArray::mergeDeep($nested_attributes, $this->boxAttributes($nested_settings, 'nested'));
$nested_attributes['class'][] = 'box--nested';
$items[] = $this->buildItem(['box' => $nested_box], $gid, $nested_attributes, $content_attributes, $nested_settings);
}
// Provides nested gridstack, gridstack within gridstack, if so configured.
$box = [];
$attributes = isset($item['wrapper_attributes']) ? $item['wrapper_attributes'] : [];
$attributes['class'][] = 'gridstack--nested';
$box['content'] = [
'#theme' => 'gridstack',
'#items' => $items,
'#optionset' => $this->gridStackOptionset,
'#settings' => $settings,
'#attributes' => $attributes,
];
// Update box with nested boxes.
return $box;
}
/**
* {@inheritdoc}
*/
public function buildItem($item, $delta, array $attributes, array $content_attributes = [], array $settings = []) {
return [
'#theme' => 'gridstack_box',
'#item' => $item,
'#delta' => $delta,
'#attributes' => $attributes,
'#content_attributes' => $content_attributes,
'#settings' => $settings,
];
}
/**
* Provides multi-breakpoint image styles.
*
* Overrides fallback breakpoint image_style with grid image_style.
* This tells theme_blazy() to respect different image style per item.
*/
public function buildImageStyleMultiple(array &$settings, $delta = 0) {
foreach ($settings['breakpoints'] as $key => &$breakpoint) {
if (isset($breakpoint['image_style']) && !empty($breakpoint['grids'][$delta]) && !empty($breakpoint['grids'][$delta]['image_style'])) {
$breakpoint['image_style'] = $breakpoint['grids'][$delta]['image_style'];
}
// Overrides image style to use a defined image style per grid item.
// This allows each individual box to have different image styles.
if ($key == 'xl' && !empty($breakpoint['grids'][$delta]['image_style'])) {
$settings['_dimensions'] = FALSE;
$settings['image_style'] = $breakpoint['grids'][$delta]['image_style'];
}
}
}
/**
* {@inheritdoc}
*/
public function build(array $build = []) {
foreach (['attached', 'layout'] + GridStackDefault::themeProperties() as $key) {
$build[$key] = isset($build[$key]) ? $build[$key] : [];
}
$gridstack = [
'#theme' => 'gridstack',
'#build' => $build,
'#pre_render' => [[$this, 'preRenderGridStack']],
'items' => [],
// Required by Drupal\layout_builder\QuickEditIntegration since 8.7.3+.
'#layout' => $build['layout'],
];
$this->moduleHandler->alter('gridstack_build', $gridstack, $build['settings']);
return empty($build['items']) ? [] : $gridstack;
}
/**
* Return the wrapper attributes.
*/
public function prepareAttributes(array &$build) {
$settings = &$build['settings'];
return GridStackAttribute::prepare($this->gridStackOptionset, $settings);
}
/**
* Returns the common settings inherited down to each item.
*/
public function getGridStackSettings() {
return array_intersect_key($this->configLoad('', 'gridstack.settings'), GridStackDefault::uiSettings());
}
/**
* Build the HTML settings.
*/
public function prepareSettings(array &$settings) {
$settings += $this->getGridStackSettings() + GridStackDefault::htmlSettings();
// Use static grid framework if so configured.
$settings['ungridstack'] = empty($settings['ungridstack']) ? $this->unGridStack : $settings['ungridstack'];
$settings['use_framework'] = !empty($settings['framework']) && $this->gridStackOptionset->getOption('use_framework');
$settings['id'] = Blazy::getHtmlId('gridstack-' . $settings['optionset'], $settings['id']);
$settings['library'] = $settings['_access_ipe'] ? $settings['library'] : '';
$settings['column'] = $this->gridStackOptionset->getSetting('column') ?: 12;
// Disable background and JS if using a CSS framework.
if ($settings['use_framework']) {
$settings['background'] = $settings['use_js'] = FALSE;
// Admin UI always uses gridstack JS to build layouts regardless of this.
$settings['use_framework'] = empty($settings['_admin']);
}
else {
$settings['use_js'] = !empty($settings['root']) || !empty($settings['_admin']) || !empty($settings['gridnative']);
}
$settings['gridnative'] = empty($settings['use_framework']) && !empty($settings['gridnative']);
if (empty($settings['breakpoints'])) {
$this->gridStackOptionset->gridsJsonToArray($settings);
}
$keys = empty($settings['breakpoints']) ? [] : array_keys($settings['breakpoints']);
$settings['_last'] = $keys ? end($keys) : 'xl';
}
/**
* Returns the dummy box to measure cell height to fix aspect ratio.
*/
protected function buildDummyItem() {
$dummy['class'][] = 'gridstack__sizer sizer is-nixbox';
$dummy['data-gs-height'] = 1;
$dummy['data-gs-width'] = 1;
$settings['use_inner'] = TRUE;
return ['dummy' => $this->buildItem(NULL, 100, $dummy, [], $settings)];
}
/**
* {@inheritdoc}
*/
public function preRenderGridStack($element) {
$build = $element['#build'];
unset($element['#build']);
// Build gridstack elements.
$settings = &$build['settings'];
$settings['_ipe'] = !empty($settings['_layouts']) || !empty($settings['_panels']);
$is_ipe_page = isset($element['#attributes'], $element['#attributes']['data-layout-delta']) || (isset($element['#prefix']) && strpos($element['#prefix'], 'panels-ipe-content') !== FALSE);
$settings['_access_ipe'] = $is_ipe_page && $settings['_ipe'];
// Supports Blazy multi-breakpoint images if provided.
if (!empty($settings['check_blazy']) && !empty($build['items'][0])) {
$this->isBlazy($settings, $build['items'][0]);
}
// Prepare the settings.
$this->gridStackOptionset = $build['optionset'] ?: GridStack::loadWithFallback($settings['optionset']);
$this->prepareSettings($settings);
// Adds regions for Layout Builder and Panels IPE integration.
$regions = $settings['_access_ipe'] ? GridStackBuilder::adminRegions($build, $element, $settings) : [];
$attachments = $this->attach($settings);
$attributes = $this->prepareAttributes($build);
// Adds the required elements for the template.
$element['#attributes'] = empty($element['#attributes']) ? $attributes : NestedArray::mergeDeep($element['#attributes'], $attributes);
$element['#optionset'] = $build['optionset'] = $this->gridStackOptionset;
$element['#settings'] = $build['settings'] = $settings;
$element['#attached'] = empty($build['attached']) ? $attachments : NestedArray::mergeDeep($build['attached'], $attachments);
$element['#items'] = $this->buildItems($build, $regions);
$element['#cache'] = $this->getCacheMetadata($build);
$element['#postscript'] = $settings['use_js'] && empty($settings['ungridstack']) ? $this->buildDummyItem() : [];
// Panels IPE, Layout Builder are happy, safe to free up wasted children.
foreach (Element::children($element) as $child) {
unset($element[$child]);
}
unset($build);
return $element;
}
}
