diff --git a/src/Command/CreateImageSizeItemsCommand.php b/src/Command/CreateImageSizeItemsCommand.php index ec41a870..8cf7c522 100644 --- a/src/Command/CreateImageSizeItemsCommand.php +++ b/src/Command/CreateImageSizeItemsCommand.php @@ -1,29 +1,26 @@ get('huh.utils.model')->findModelInstancesBy('tl_image_size', $columns, []))) { - $io->error('No image sizes found for the given ids.'); - - return false; - } - - /** @var ImageSizeModel $imageSize */ - foreach ($imageSizes as $imageSize) { - $existingItems = System::getContainer()->get('huh.utils.model')->findModelInstancesBy('tl_image_size_item', ['tl_image_size_item.pid=?'], [$imageSize->id]); - - if (null !== $existingItems) { - $io->warning('Skipping image size ID '.$imageSize->id.' because it already has existing image size items.'); - - continue; - } - - $j = 0; - - // first - $this->createItem($imageSize, $j++, $breakpoints[0], null, static::MODE_FIRST); - ++$creationCount; - - // intermediates - foreach ($breakpoints as $i => $breakpoint) { - if ($i === \count($breakpoints) - 1) { - continue; - } - - $this->createItem($imageSize, $j++, $breakpoint, $breakpoints[$i + 1], static::MODE_INTERMEDIATE); - - ++$creationCount; - } - - // last - $this->createItem($imageSize, $j++, $breakpoints[\count($breakpoints) - 1], null, static::MODE_LAST); - ++$creationCount; - } - - $io->success($creationCount.' image size items have been created.'); - - return true; + parent::__construct(); + $this->contaoFramework = $contaoFramework; + $this->utils = $utils; } /** @@ -96,17 +55,26 @@ public function createImageSizes(SymfonyStyle $io, array $breakpoints, array $im */ protected function configure() { - $this->setName('huh:utils:create-image-size-items')->setDescription('Creates image size items for a given image size entity. Image size entities with existing image size items will be skipped.'); - $this->addArgument('image-size-ids', InputArgument::OPTIONAL, 'The comma separated ids of the image size. Skip the parameter in order to create image size items for all image size entities.'); - $this->addArgument('breakpoints', InputArgument::OPTIONAL, 'The comma separated breakpoints as pixel amounts (defaults to "576,768,992,1200,1400").', implode(',', static::DEFAULT_BREAKPOINTS)); + $this + ->setDescription('Creates image size items for a given image size entity. Image size entities with existing image size items will be skipped.') + ->addArgument( + 'image-size-ids', + InputArgument::OPTIONAL, + 'The comma separated ids of the image size. Skip the parameter in order to create image size items for all image size entities.' + ) + ->addArgument( + 'breakpoints', + InputArgument::OPTIONAL, + 'The comma separated breakpoints as pixel amounts (defaults to "576,768,992,1200,1400").', implode(',', static::DEFAULT_BREAKPOINTS) + ); } /** * {@inheritdoc} */ - protected function executeLocked(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output) { - $this->framework->initialize(); + $this->contaoFramework->initialize(); $io = new SymfonyStyle($input, $output); @@ -158,4 +126,54 @@ protected function createItem(Model $imageSize, int $index, int $breakpoint, ?in $item->save(); } + + private function createImageSizes(SymfonyStyle $io, array $breakpoints, array $imageSizeIds = []) + { + sort($breakpoints); + + $creationCount = 0; + $columns = (empty($imageSizeIds) ? [] : ['tl_image_size.id IN ('.implode(',', $imageSizeIds).')']); + + if (null === ($imageSizes = $this->utils->model()->findModelInstancesBy('tl_image_size', $columns, []))) { + $io->error('No image sizes found for the given ids.'); + + return false; + } + + /** @var ImageSizeModel $imageSize */ + foreach ($imageSizes as $imageSize) { + $existingItems = $this->utils->model()->findModelInstancesBy('tl_image_size_item', ['tl_image_size_item.pid=?'], [$imageSize->id]); + + if (null !== $existingItems) { + $io->warning('Skipping image size ID '.$imageSize->id.' because it already has existing image size items.'); + + continue; + } + + $j = 0; + + // first + $this->createItem($imageSize, $j++, $breakpoints[0], null, static::MODE_FIRST); + ++$creationCount; + + // intermediates + foreach ($breakpoints as $i => $breakpoint) { + if ($i === \count($breakpoints) - 1) { + continue; + } + + $this->createItem($imageSize, $j++, $breakpoint, $breakpoints[$i + 1], static::MODE_INTERMEDIATE); + + ++$creationCount; + } + + // last + $this->createItem($imageSize, $j++, $breakpoints[\count($breakpoints) - 1], null, static::MODE_LAST); + ++$creationCount; + } + + $io->success($creationCount.' image size items have been created.'); + + return true; + } } diff --git a/src/Command/EntityFinderCommand.php b/src/Command/EntityFinderCommand.php new file mode 100644 index 00000000..2dc9326a --- /dev/null +++ b/src/Command/EntityFinderCommand.php @@ -0,0 +1,271 @@ +contaoFramework = $contaoFramework; + $this->eventDispatcher = $eventDispatcher; + } + + protected function configure() + { + $this + ->addArgument('table', InputArgument::REQUIRED, 'The database table') + ->addArgument('id', InputArgument::REQUIRED, 'The entity id or alias (id is better supported).') + ->setDescription('A command to find where an entity is included.') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->contaoFramework->initialize(); + $io = new SymfonyStyle($input, $output); + + $io->title('Find entity'); + + if ($input->hasArgument('table') && $input->getArgument('table')) { + $table = $input->getArgument('table'); + } + + if ($input->hasArgument('id') && $input->getArgument('id')) { + $id = $input->getArgument('id'); + } + + $result = $this->loop($table, $id); + $this->output($io, [$result]); + } + + private function loop(string $table, $id): array + { + $current = [ + 'table' => $table, + 'id' => $id, + 'parents' => [], + ]; + + $parents = []; + + $this->findEntity($table, $id, $parents); + $event = $this->runExtendEntityFinderEvent($table, $id, $parents); + + $cache = []; + + foreach ($event->getParents() as $parent) { + if (!isset($parent['table']) || !isset($parent['id'])) { + continue; + } + $cacheKey = $table.'_'.$id; + + if (\in_array($cacheKey, $cache)) { + continue; + } + $cache[] = $cacheKey; + $current['parents'][] = $this->loop($parent['table'], $parent['id']); + } + + return $current; + } + + private function output(SymfonyStyle $io, array $tree, string $prepend = '', int $depth = 0): void + { + $itemCount = \count($tree); + $i = 0; + + foreach ($tree as $item) { + ++$i; + + if ($depth > 0) { + if ($i === $itemCount) { + $newPrepend = $prepend.'└── '; + $nextPrepend = $prepend.' '; + } else { + $newPrepend = $prepend.'├── '; + $nextPrepend = $prepend.'│ '; + } + } else { + $newPrepend = $prepend; + $nextPrepend = $prepend; + } + $io->writeln($newPrepend.$this->createText($item['table'], $item['id'])); + + if ($item['parents'] ?? false) { + $this->output($io, $item['parents'], $nextPrepend, ++$depth); + } + } + } + + private function findEntity(string $table, $id, array &$parents, bool $onlyText = false): ?string + { + Controller::loadLanguageFile('default'); + + switch ($table) { + case ContentModel::getTable(): + $element = ContentModel::findByIdOrAlias($id); + + if ($element) { + $parents[] = ['table' => $element->ptable, 'id' => $element->pid]; + + return 'Content Element: '.($GLOBALS['TL_LANG']['CTE'][$element->type][0] ?? $element->type).' (ID: '.$element->id.', Type: '.$element->type.')'; + } + + return 'Content Element not found: ID '.$id; + + case ArticleModel::getTable(): + $element = ArticleModel::findByPk($id); + + if ($element) { + $parents[] = ['table' => PageModel::getTable(), 'id' => $element->pid]; + + return 'Article: '.$element->title.' (ID: '.$element->id.')'; + } + + return 'Article not found: ID '.$id; + + case ModuleModel::getTable(): + if ($onlyText) { + Controller::loadLanguageFile('modules'); + } + $element = ModuleModel::findByIdOrAlias($id); + + if ($element) { + if (!$onlyText) { + $this->findFrontendModuleParents($element, $parents, $id); + } + + return 'Frontend module: '.($GLOBALS['TL_LANG']['FMD'][$element->type][0] ?? $element->type).' (ID: '.$element->id.', Type: '.$element->type.')'; + } + + return 'Frontend module not found: ID '.$id; + + case LayoutModel::getTable(): + $layout = LayoutModel::findById($id); + + if ($layout) { + $parents[] = ['table' => ThemeModel::getTable(), 'id' => $layout->pid]; + + return 'Layout: '.html_entity_decode($layout->name).' (ID: '.$layout->id.')'; + } + + return 'Layout not found: ID '.$id; + + case ThemeModel::getTable(): + $theme = ThemeModel::findByPk($id); + + if ($theme) { + return 'Theme: '.$theme->name.' (ID: '.$theme->id.')'; + } + + return 'Theme not found: ID '.$id; + + case PageModel::getTable(): + $page = PageModel::findByPk($id); + + if ($page) { + return 'Page: '.$page->title.' (ID: '.$page->id.', Type: '.$page->type.', DNS: '.$page->getFrontendUrl().' )'; + } + + return 'Page not found: ID '.$id; + } + + return null; + } + + private function createText(string $table, $id): string + { + $parents = []; + + if ($text = $this->findEntity($table, $id, $parents, true)) { + return $text; + } + + /** @var ExtendEntityFinderEvent $event */ + $event = $this->runExtendEntityFinderEvent($table, $id, []); + + if ($event->getOutput()) { + return $event->getOutput(); + } + + return 'Unsupported entity: '.$table.' (ID: '.$id.')'; + } + + private function runExtendEntityFinderEvent(string $table, $id, array $parents): ExtendEntityFinderEvent + { + /* @var ExtendEntityFinderEvent $event */ + if (is_subclass_of($this->eventDispatcher, 'Symfony\Contracts\EventDispatcher\EventDispatcherInterface')) { + $event = $this->eventDispatcher->dispatch(new ExtendEntityFinderEvent($table, $id, $parents)); + } else { + /** @noinspection PhpParamsInspection */ + $event = $this->eventDispatcher->dispatch(ExtendEntityFinderEvent::class, new ExtendEntityFinderEvent($table, $id, $parents)); + } + + return $event; + } + + private function findFrontendModuleParents(ModuleModel $module, array &$parents): void + { + $contentelements = ContentModel::findBy(['tl_content.type=?', 'tl_content.module=?'], ['module', $module->id]); + + if ($contentelements) { + foreach ($contentelements as $contentelement) { + $parents[] = ['table' => ContentModel::getTable(), 'id' => $contentelement->id]; + } + } + + $result = Database::getInstance() + ->prepare("SELECT id FROM tl_layout WHERE modules LIKE '%:\"".(string) ((int) $module->id)."\"%'") + ->execute(); + + foreach ($result->fetchEach('id') as $layoutId) { + $parents[] = ['table' => LayoutModel::getTable(), 'id' => $layoutId]; + } + + $result = Database::getInstance() + ->prepare("SELECT id FROM tl_module + WHERE type='html' + AND ( + html LIKE '%{{insert_module::".$module->id."}}%' + OR html LIKE '%{{insert_module::".$module->id."::%')") + ->execute(); + + foreach ($result->fetchEach('id') as $moduleId) { + $parents[] = ['table' => ModuleModel::getTable(), 'id' => $moduleId]; + } + } +} diff --git a/src/ContaoManager/Plugin.php b/src/ContaoManager/Plugin.php index 0016ee62..25834a04 100644 --- a/src/ContaoManager/Plugin.php +++ b/src/ContaoManager/Plugin.php @@ -1,7 +1,7 @@ load('@HeimrichHannotContaoUtilsBundle/Resources/config/commands.yml'); $loader->load('@HeimrichHannotContaoUtilsBundle/Resources/config/choices.yml'); $loader->load('@HeimrichHannotContaoUtilsBundle/Resources/config/parameters.yml'); $loader->load('@HeimrichHannotContaoUtilsBundle/Resources/config/services.yml'); diff --git a/src/Event/AbstractEvent.php b/src/Event/AbstractEvent.php new file mode 100644 index 00000000..7bcf6065 --- /dev/null +++ b/src/Event/AbstractEvent.php @@ -0,0 +1,21 @@ +table = $table; + $this->id = $id; + $this->parents = $parents; + $this->onlyText = $onlyText; + } + + public function getTable(): string + { + return $this->table; + } + + /** + * @return int|string + */ + public function getId() + { + return $this->id; + } + + public function addParent(string $table, $id): void + { + $this->parents[] = ['table' => $table, 'id' => $id]; + } + + public function getParents(): array + { + return $this->parents; + } + + public function setParents(array $parents): void + { + $this->parents = $parents; + } + + public function getOutput(): ?string + { + return $this->output; + } + + public function setOutput(?string $output): void + { + $this->output = $output; + } + + public function isOnlyText(): bool + { + return $this->onlyText; + } +} diff --git a/src/EventListener/ExtendEntityFinderListener.php b/src/EventListener/ExtendEntityFinderListener.php new file mode 100644 index 00000000..3931c793 --- /dev/null +++ b/src/EventListener/ExtendEntityFinderListener.php @@ -0,0 +1,64 @@ +getTable()) { + case BlockModuleModel::getTable(): + $element = BlockModuleModel::findByPk($event->getId()); + + if (!$element) { + return; + } + $event->addParent(BlockModel::getTable(), $element->id); + $event->setOutput('Block module: '.$element->id); + + break; + + case BlockModel::getTable(): + $block = BlockModel::findByPk($event->getId()); + + if ($block) { + $event->addParent(ModuleModel::getTable(), $block->module); + $event->setOutput('Block: '.$block->title.' (ID: '.$block->id.')'); + } + + break; + + case ModuleModel::getTable(): + if ($event->isOnlyText()) { + break; + } + + if (is_numeric($event->getId())) { + /** @var BlockModuleModel[]|Collection|null $blockModules */ + $blockModules = BlockModuleModel::findByModule($event->getId()); + + if ($blockModules) { + foreach ($blockModules as $blockModule) { + $event->addParent(BlockModuleModel::getTable(), $blockModule->id); + } + } + } + + break; + } + } + } +} diff --git a/src/Resources/config/commands.yml b/src/Resources/config/commands.yml deleted file mode 100644 index 4b81a8d3..00000000 --- a/src/Resources/config/commands.yml +++ /dev/null @@ -1,11 +0,0 @@ -services: - _defaults: - autoconfigure: true - autowire: true - - _instanceof: - Contao\CoreBundle\Framework\FrameworkAwareInterface: - calls: - - ["setFramework", ["@contao.framework"]] - - HeimrichHannot\UtilsBundle\Command\CreateImageSizeItemsCommand: ~ diff --git a/src/Resources/config/services.yml b/src/Resources/config/services.yml index 8f62b281..fbbd6fca 100644 --- a/src/Resources/config/services.yml +++ b/src/Resources/config/services.yml @@ -1,8 +1,5 @@ services: _instanceof: - Contao\CoreBundle\Framework\FrameworkAwareInterface: - calls: - - ["setFramework", ["@contao.framework"]] Symfony\Component\DependencyInjection\ContainerAwareInterface: calls: - ["setContainer", ["@service_container"]] @@ -12,8 +9,8 @@ services: autowire: true autoconfigure: true - HeimrichHannot\UtilsBundle\Util\: - resource: '../../Util/*' + HeimrichHannot\UtilsBundle\: + resource: '../../{Command,Util}/*' exclude: '../../Util/Utils.php' autowire: true autoconfigure: true