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

heimrichhannot / contao-utils-bundle / 8111841604

01 Mar 2024 01:05PM UTC coverage: 72.668% (+0.09%) from 72.575%
8111841604

push

github

koertho
fix test

779 of 1072 relevant lines covered (72.67%)

3.5 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\Command\Command;
24
use Symfony\Component\Console\Input\InputArgument;
25
use Symfony\Component\Console\Input\InputInterface;
26
use Symfony\Component\Console\Output\OutputInterface;
27
use Symfony\Component\Console\Style\SymfonyStyle;
28
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
29

30
class EntityFinderCommand extends Command
31
{
32
    protected static $defaultName = 'huh:utils:entity_finder';
33
    protected static $defaultDescription = 'A command to find where an entity is included.';
34

35
    public function __construct(
36
        private ContaoFramework $contaoFramework,
37
        private EventDispatcherInterface $eventDispatcher,
38
        private Connection $connection,
39
        private EntityFinderHelper $entityFinderHelper)
40
    {
41
        parent::__construct();
1✔
42
    }
43

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

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

57
        $io->title('Find entity');
×
58

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

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

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

71
        return 0;
×
72
    }
73

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

82
        $parents = [];
×
83

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

87
        $this->findInserttags($event);
×
88

89
        $cache = [];
×
90

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

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

104
        return $current;
×
105
    }
106

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

112
        foreach ($tree as $item) {
×
113
            ++$i;
×
114

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

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

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

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

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

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

149
                return 'Content Element not found: ID '.$id;
×
150

151
            case ArticleModel::getTable():
×
152
                $element = ArticleModel::findByPk($id);
×
153

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

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

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

169
                return 'Article not found: ID '.$id;
×
170

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

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

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

185
                return 'Frontend module not found: ID '.$id;
×
186

187
            case LayoutModel::getTable():
×
188
                $layout = LayoutModel::findById($id);
×
189

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

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

196
                return 'Layout not found: ID '.$id;
×
197

198
            case ThemeModel::getTable():
×
199
                $theme = ThemeModel::findByPk($id);
×
200

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

205
                return 'Theme not found: ID '.$id;
×
206

207
            case PageModel::getTable():
×
208
                $page = PageModel::findByPk($id);
×
209

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

214
                return 'Page not found: ID '.$id;
×
215
        }
216

217
        return null;
×
218
    }
219

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

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

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

231
        if ($event->getOutput()) {
×
232
            return $event->getOutput();
×
233
        }
234

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

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

254
        return $event;
×
255
    }
256

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

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

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

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

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

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

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

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

296
            foreach ($result->fetchAllAssociative() as $row) {
×
297
                $event->addParent(ModuleModel::getTable(), $row['id']);
×
298
            }
299
        }
300
    }
301
}
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

© 2025 Coveralls, Inc