• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

heimrichhannot / contao-utils-bundle / 11857787308

15 Nov 2024 02:12PM UTC coverage: 73.163%. Remained the same
11857787308

push

github

koertho
prepared 3.5.0

946 of 1293 relevant lines covered (73.16%)

3.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

3.76
/src/Command/EntityFinderCommand.php
1
<?php
2

3
/*
4
 * Copyright (c) 2022 Heimrich & Hannot GmbH
5
 *
6
 * @license LGPL-3.0-or-later
7
 */
8

9
namespace HeimrichHannot\UtilsBundle\Command;
10

11
use Contao\ArticleModel;
12
use Contao\ContentModel;
13
use Contao\Controller;
14
use Contao\CoreBundle\Framework\ContaoFramework;
15
use Contao\Database;
16
use Contao\LayoutModel;
17
use Contao\ModuleModel;
18
use Contao\PageModel;
19
use Contao\ThemeModel;
20
use Doctrine\DBAL\Connection;
21
use HeimrichHannot\UtilsBundle\EntityFinder\EntityFinderHelper;
22
use HeimrichHannot\UtilsBundle\Event\ExtendEntityFinderEvent;
23
use Symfony\Component\Console\Attribute\AsCommand;
24
use Symfony\Component\Console\Command\Command;
25
use Symfony\Component\Console\Input\InputArgument;
26
use Symfony\Component\Console\Input\InputInterface;
27
use Symfony\Component\Console\Output\OutputInterface;
28
use Symfony\Component\Console\Style\SymfonyStyle;
29
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
30

31
#[AsCommand(
32
    name: 'huh:utils:entity_finder',
33
    description: 'A command to find where an entity is included.'
34
)]
35
class EntityFinderCommand extends Command
36
{
37
    public function __construct(
38
        private ContaoFramework $contaoFramework,
39
        private EventDispatcherInterface $eventDispatcher,
40
        private Connection $connection,
41
        private EntityFinderHelper $entityFinderHelper)
42
    {
43
        parent::__construct();
1✔
44
    }
45

46
    protected function configure(): void
47
    {
48
        $this
1✔
49
            ->addArgument('table', InputArgument::REQUIRED, 'The database table')
1✔
50
            ->addArgument('id', InputArgument::REQUIRED, 'The entity id or alias (id is better supported).')
1✔
51
        ;
1✔
52
    }
53

54
    protected function execute(InputInterface $input, OutputInterface $output): int
55
    {
56
        $this->contaoFramework->initialize();
×
57
        $io = new SymfonyStyle($input, $output);
×
58

59
        $io->title('Find entity');
×
60

61
        if ($input->hasArgument('table') && $input->getArgument('table')) {
×
62
            $table = $input->getArgument('table');
×
63
        }
64

65
        if ($input->hasArgument('id') && $input->getArgument('id')) {
×
66
            $id = $input->getArgument('id');
×
67
        }
68

69
        $result = $this->loop($table, $id);
×
70
        $this->output($io, [$result]);
×
71
        $io->newLine();
×
72

73
        return 0;
×
74
    }
75

76
    private function loop(string $table, $id): array
77
    {
78
        $current = [
×
79
            'table' => $table,
×
80
            'id' => $id,
×
81
            'parents' => [],
×
82
        ];
×
83

84
        $parents = [];
×
85

86
        $this->findEntity($table, $id, $parents);
×
87
        $event = $this->runExtendEntityFinderEvent($table, $id, $parents);
×
88

89
        $this->findInserttags($event);
×
90

91
        $cache = [];
×
92

93
        foreach ($event->getParents() as $parent) {
×
94
            if (!isset($parent['table']) || !isset($parent['id'])) {
×
95
                continue;
×
96
            }
97
            $cacheKey = $parent['table'].'_'.$parent['id'];
×
98

99
            if (\in_array($cacheKey, $cache)) {
×
100
                continue;
×
101
            }
102
            $cache[] = $cacheKey;
×
103
            $current['parents'][] = $this->loop($parent['table'], $parent['id']);
×
104
        }
105

106
        return $current;
×
107
    }
108

109
    private function output(SymfonyStyle $io, array $tree, string $prepend = '', int $depth = 0): void
110
    {
111
        $itemCount = \count($tree);
×
112
        $i = 0;
×
113

114
        foreach ($tree as $item) {
×
115
            ++$i;
×
116

117
            if ($depth > 0) {
×
118
                if ($i === $itemCount) {
×
119
                    $newPrepend = $prepend.'└── ';
×
120
                    $nextPrepend = $prepend.'    ';
×
121
                } else {
122
                    $newPrepend = $prepend.'├── ';
×
123
                    $nextPrepend = $prepend.'│   ';
×
124
                }
125
            } else {
126
                $newPrepend = $prepend;
×
127
                $nextPrepend = $prepend;
×
128
            }
129
            $io->writeln($newPrepend.$this->createText($item['table'], $item['id']));
×
130

131
            if ($item['parents'] ?? false) {
×
132
                $this->output($io, $item['parents'], $nextPrepend, ++$depth);
×
133
            }
134
        }
135
    }
136

137
    private function findEntity(string $table, $id, array &$parents, bool $onlyText = false): ?string
138
    {
139
        Controller::loadLanguageFile('default');
×
140

141
        switch ($table) {
142
            case ContentModel::getTable():
×
143
                $element = ContentModel::findByIdOrAlias($id);
×
144

145
                if ($element) {
×
146
                    $parents[] = ['table' => $element->ptable, 'id' => $element->pid];
×
147

148
                    return 'Content Element: '.($GLOBALS['TL_LANG']['CTE'][$element->type][0] ?? $element->type).' (ID: '.$element->id.', Type: '.$element->type.')';
×
149
                }
150

151
                return 'Content Element not found: ID '.$id;
×
152

153
            case ArticleModel::getTable():
×
154
                $element = ArticleModel::findByPk($id);
×
155

156
                if ($element) {
×
157
                    $parents[] = ['table' => PageModel::getTable(), 'id' => $element->pid];
×
158

159
                    if (!$onlyText) {
×
160
                        foreach ($this->entityFinderHelper->findModulesByInserttag('html', 'html', 'insert_article', $element->id) as $id) {
×
161
                            $parents[] = ['table' => ModuleModel::getTable(), 'id' => $id];
×
162
                        }
163
                        foreach ($this->entityFinderHelper->findContentElementByInserttag('html', 'html', 'insert_article', $element->id) as $id) {
×
164
                            $parents[] = ['table' => ContentModel::getTable(), 'id' => $id];
×
165
                        }
166
                    }
167

168
                    return 'Article: '.$element->title.' (ID: '.$element->id.')';
×
169
                }
170

171
                return 'Article not found: ID '.$id;
×
172

173
            case ModuleModel::getTable():
×
174
                if ($onlyText) {
×
175
                    Controller::loadLanguageFile('modules');
×
176
                }
177
                $element = ModuleModel::findByIdOrAlias($id);
×
178

179
                if ($element) {
×
180
                    if (!$onlyText) {
×
181
                        $this->findFrontendModuleParents($element, $parents, $id);
×
182
                    }
183

184
                    return 'Frontend module: '.($GLOBALS['TL_LANG']['FMD'][$element->type][0] ?? $element->type).' (ID: '.$element->id.', Type: '.$element->type.')';
×
185
                }
186

187
                return 'Frontend module not found: ID '.$id;
×
188

189
            case LayoutModel::getTable():
×
190
                $layout = LayoutModel::findById($id);
×
191

192
                if ($layout) {
×
193
                    $parents[] = ['table' => ThemeModel::getTable(), 'id' => $layout->pid];
×
194

195
                    return 'Layout: '.html_entity_decode($layout->name).' (ID: '.$layout->id.')';
×
196
                }
197

198
                return 'Layout not found: ID '.$id;
×
199

200
            case ThemeModel::getTable():
×
201
                $theme = ThemeModel::findByPk($id);
×
202

203
                if ($theme) {
×
204
                    return '<options=bold>Theme: '.$theme->name.'</> (ID: '.$theme->id.')';
×
205
                }
206

207
                return 'Theme not found: ID '.$id;
×
208

209
            case PageModel::getTable():
×
210
                $page = PageModel::findByPk($id);
×
211

212
                if ($page) {
×
213
                    return '<options=bold>Page: '.$page->title.'</> (ID: '.$page->id.', Type: '.$page->type.', DNS: '.$page->getFrontendUrl().' )';
×
214
                }
215

216
                return 'Page not found: ID '.$id;
×
217
        }
218

219
        return null;
×
220
    }
221

222
    private function createText(string $table, $id): string
223
    {
224
        $parents = [];
×
225

226
        if ($text = $this->findEntity($table, $id, $parents, true)) {
×
227
            return $text;
×
228
        }
229

230
        /** @var ExtendEntityFinderEvent $event */
231
        $event = $this->runExtendEntityFinderEvent($table, $id, [], true);
×
232

233
        if ($event->getOutput()) {
×
234
            return $event->getOutput();
×
235
        }
236

237
        return 'Unsupported entity: '.$table.' (ID: '.$id.')';
×
238
    }
239

240
    private function runExtendEntityFinderEvent(string $table, $id, array $parents, bool $onlyText = false): ExtendEntityFinderEvent
241
    {
242
        /* @var ExtendEntityFinderEvent $event */
243
        if (is_subclass_of($this->eventDispatcher, 'Symfony\Contracts\EventDispatcher\EventDispatcherInterface')) {
×
244
            $event = $this->eventDispatcher->dispatch(
×
245
                new ExtendEntityFinderEvent($table, $id, $parents, [], $this->entityFinderHelper, $onlyText),
×
246
                ExtendEntityFinderEvent::class
×
247
            );
×
248
        } else {
249
            /** @noinspection PhpParamsInspection */
250
            $event = $this->eventDispatcher->dispatch(
×
251
                ExtendEntityFinderEvent::class,
×
252
                new ExtendEntityFinderEvent($table, $id, $parents, [], $this->entityFinderHelper, $onlyText)
×
253
            );
×
254
        }
255

256
        return $event;
×
257
    }
258

259
    private function findFrontendModuleParents(ModuleModel $module, array &$parents): void
260
    {
261
        $contentelements = ContentModel::findBy(['tl_content.type=?', 'tl_content.module=?'], ['module', $module->id]);
×
262

263
        if ($contentelements) {
×
264
            foreach ($contentelements as $contentelement) {
×
265
                $parents[] = ['table' => ContentModel::getTable(), 'id' => $contentelement->id];
×
266
            }
267
        }
268

269
        $result = Database::getInstance()
×
270
            ->prepare("SELECT id FROM tl_layout WHERE modules LIKE '%:\"".(string) ((int) $module->id)."\"%'")
×
271
            ->execute();
×
272

273
        foreach ($result->fetchEach('id') as $layoutId) {
×
274
            $parents[] = ['table' => LayoutModel::getTable(), 'id' => $layoutId];
×
275
        }
276

277
        $result = Database::getInstance()
×
278
            ->prepare("SELECT id FROM tl_module
×
279
                        WHERE type='html'
280
                        AND (
281
                            html LIKE '%{{insert_module::".$module->id."}}%'
×
282
                            OR html LIKE '%{{insert_module::".$module->id."::%')")
×
283
            ->execute();
×
284

285
        foreach ($result->fetchEach('id') as $moduleId) {
×
286
            $parents[] = ['table' => ModuleModel::getTable(), 'id' => $moduleId];
×
287
        }
288
    }
289

290
    private function findInserttags(ExtendEntityFinderEvent $event): void
291
    {
292
        $stmt = $this->connection->prepare(
×
293
            "SELECT id FROM tl_module WHERE type='html' AND html LIKE ?");
×
294

295
        foreach ($event->getInserttags() as $inserttag) {
×
296
            $result = $stmt->executeQuery(['%'.$inserttag.'%']);
×
297

298
            foreach ($result->fetchAllAssociative() as $row) {
×
299
                $event->addParent(ModuleModel::getTable(), $row['id']);
×
300
            }
301
        }
302
    }
303
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc