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

heimrichhannot / contao-utils-bundle / 13951376717

19 Mar 2025 04:12PM UTC coverage: 79.32% (+5.6%) from 73.745%
13951376717

push

github

web-flow
Forwardport #87 (#93)

* forwardport #87

* updated tests

* refactored some deprecated code into finder

* fix phpstan reports

* update tests

* add more test coverage

* raise test coveage

* fix bug

---------

Co-authored-by: DDEV User <nobody@example.com>

142 of 178 new or added lines in 6 files covered. (79.78%)

2 existing lines in 1 file now uncovered.

1097 of 1383 relevant lines covered (79.32%)

3.28 hits per line

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

4.35
/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\EntityFinder\Finder;
23
use HeimrichHannot\UtilsBundle\Event\ExtendEntityFinderEvent;
24
use Symfony\Component\Console\Attribute\AsCommand;
25
use Symfony\Component\Console\Command\Command;
26
use Symfony\Component\Console\Input\InputArgument;
27
use Symfony\Component\Console\Input\InputInterface;
28
use Symfony\Component\Console\Output\OutputInterface;
29
use Symfony\Component\Console\Style\SymfonyStyle;
30
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
31

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

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

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

62
        $io->title('Find entity');
×
63

NEW
64
        $table = $input->getArgument('table');
×
NEW
65
        $id = $input->getArgument('id');
×
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);
×
NEW
85
        $event = $this->runLegacyExtendEntityFinderEvent($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 LayoutModel::getTable():
×
172
                $layout = LayoutModel::findById($id);
×
173

174
                if ($layout) {
×
175
                    $parents[] = ['table' => ThemeModel::getTable(), 'id' => $layout->pid];
×
176

177
                    return 'Layout: '.html_entity_decode($layout->name).' (ID: '.$layout->id.')';
×
178
                }
179

180
                return 'Layout not found: ID '.$id;
×
181

182
            case ThemeModel::getTable():
×
183
                $theme = ThemeModel::findByPk($id);
×
184

185
                if ($theme) {
×
186
                    return '<options=bold>Theme: '.$theme->name.'</> (ID: '.$theme->id.')';
×
187
                }
188

189
                return 'Theme not found: ID '.$id;
×
190

191
            case PageModel::getTable():
×
192
                $page = PageModel::findByPk($id);
×
193

194
                if ($page) {
×
195
                    return '<options=bold>Page: '.$page->title.'</> (ID: '.$page->id.', Type: '.$page->type.', DNS: '.$page->getFrontendUrl().' )';
×
196
                }
197

198
                return 'Page not found: ID '.$id;
×
199
        }
200

NEW
201
        $element = $this->finder->find($table, $id);
×
NEW
202
        if ($element) {
×
NEW
203
            if ($onlyText) {
×
NEW
204
                return $element->description;
×
205
            }
NEW
206
            if (null === $element->parents) {
×
NEW
207
                return null;
×
208
            }
NEW
209
            foreach ($element->getParents() as $parent) {
×
NEW
210
                $parents[] = ['table' => $parent['table'], 'id' => $parent['id']];
×
211
            }
NEW
212
            return null;
×
213
        }
214

UNCOV
215
        return null;
×
216
    }
217

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

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

226
        /** @var ExtendEntityFinderEvent $event */
NEW
227
        $event = $this->runLegacyExtendEntityFinderEvent($table, $id, [], true);
×
228

229
        if ($event->getOutput()) {
×
230
            return $event->getOutput();
×
231
        }
232

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

236
    private function runLegacyExtendEntityFinderEvent(string $table, $id, array $parents, bool $onlyText = false): ExtendEntityFinderEvent
237
    {
238

NEW
239
        if ($this->eventDispatcher->hasListeners(ExtendEntityFinderEvent::class)) {
×
NEW
240
            trigger_deprecation(
×
NEW
241
                'heimrichhannot/contao-utils-bundle',
×
NEW
242
                '3.7',
×
NEW
243
                'Using the ExtendEntityFinderEvent is deprecated. Use EntityFinderFindEvent instead.'
×
UNCOV
244
            );
×
245
        }
NEW
246
        $event = $this->eventDispatcher->dispatch(
×
NEW
247
            new ExtendEntityFinderEvent($table, $id, $parents, [], $this->entityFinderHelper, $onlyText),
×
NEW
248
        );
×
249

250
        return $event;
×
251
    }
252

253
    private function findInserttags(ExtendEntityFinderEvent $event): void
254
    {
255
        $stmt = $this->connection->prepare(
×
256
            "SELECT id FROM tl_module WHERE type='html' AND html LIKE ?");
×
257

258
        foreach ($event->getInserttags() as $inserttag) {
×
259
            $result = $stmt->executeQuery(['%'.$inserttag.'%']);
×
260

261
            foreach ($result->fetchAllAssociative() as $row) {
×
262
                $event->addParent(ModuleModel::getTable(), $row['id']);
×
263
            }
264
        }
265
    }
266
}
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