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

heimrichhannot / contao-utils-bundle / 15017483614

14 May 2025 09:41AM UTC coverage: 25.302% (-54.2%) from 79.482%
15017483614

Pull #96

github

koertho
backport anonymize utils
Pull Request #96: Backport anonymize utils

6 of 7 new or added lines in 2 files covered. (85.71%)

248 existing lines in 22 files now uncovered.

1488 of 5881 relevant lines covered (25.3%)

1.56 hits per line

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

8.33
/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\DC_Table;
17
use Contao\LayoutModel;
18
use Contao\ModuleModel;
19
use Contao\PageModel;
20
use Contao\ThemeModel;
21
use Doctrine\DBAL\Connection;
22
use HeimrichHannot\UtilsBundle\EntityFinder\EntityFinderHelper;
23
use HeimrichHannot\UtilsBundle\EntityFinder\Finder;
24
use HeimrichHannot\UtilsBundle\Event\ExtendEntityFinderEvent;
25
use HeimrichHannot\UtilsBundle\Util\Utils;
26
use Symfony\Component\Console\Command\Command;
27
use Symfony\Component\Console\Input\InputArgument;
28
use Symfony\Component\Console\Input\InputInterface;
29
use Symfony\Component\Console\Output\OutputInterface;
30
use Symfony\Component\Console\Style\SymfonyStyle;
31
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
32

33
class EntityFinderCommand extends Command
34
{
35
    protected static $defaultName = 'huh:utils:entity_finder';
36

37
    private ContaoFramework $contaoFramework;
38
    private EventDispatcherInterface $eventDispatcher;
39
    private Connection $connection;
40
    private EntityFinderHelper $entityFinderHelper;
41
    private Utils $utils;
42
    private Finder $finder;
43

44
    public function __construct(
45
        ContaoFramework $contaoFramework,
46
        EventDispatcherInterface $eventDispatcher,
47
        Connection $connection,
48
        EntityFinderHelper $entityFinderHelper,
49
        Utils $utils,
50
        Finder $finder
51
    )
52
    {
53
        parent::__construct();
1✔
54

55
        $this->contaoFramework = $contaoFramework;
1✔
56
        $this->eventDispatcher = $eventDispatcher;
1✔
57
        $this->connection = $connection;
1✔
58
        $this->entityFinderHelper = $entityFinderHelper;
1✔
59
        $this->utils = $utils;
1✔
60
        $this->finder = $finder;
1✔
61
    }
62

63
    protected function configure()
64
    {
65
        $this
1✔
66
            ->addArgument('table', InputArgument::REQUIRED, 'The database table')
1✔
67
            ->addArgument('id', InputArgument::REQUIRED, 'The entity id or alias (id is better supported).')
1✔
68
            ->setDescription('A command to find where an entity is included.')
1✔
69
        ;
1✔
70
    }
71

72
    protected function execute(InputInterface $input, OutputInterface $output)
73
    {
UNCOV
74
        $this->contaoFramework->initialize();
×
75
        $io = new SymfonyStyle($input, $output);
×
76

77
        $io->title('Find entity');
×
78

79
        if ($input->hasArgument('table') && $input->getArgument('table')) {
×
UNCOV
80
            $table = $input->getArgument('table');
×
81
        }
82

83
        if ($input->hasArgument('id') && $input->getArgument('id')) {
×
84
            $id = $input->getArgument('id');
×
85
        }
86

UNCOV
87
        $result = $this->loop($table, $id);
×
88
        $this->output($io, [$result]);
×
UNCOV
89
        $io->newLine();
×
90

91
        return 0;
×
92
    }
93

94
    private function loop(string $table, $id): array
95
    {
96
        $current = [
×
97
            'table' => $table,
×
UNCOV
98
            'id' => $id,
×
99
            'parents' => [],
×
100
        ];
×
101

UNCOV
102
        $parents = [];
×
103

UNCOV
104
        $this->findEntity($table, $id, $parents);
×
UNCOV
105
        $event = $this->runExtendEntityFinderEvent($table, $id, $parents);
×
106

UNCOV
107
        $this->findInserttags($event);
×
108

109
        $cache = [];
×
110

111
        foreach ($event->getParents() as $parent) {
×
112
            if (!isset($parent['table']) || !isset($parent['id'])) {
×
UNCOV
113
                continue;
×
114
            }
115
            $cacheKey = $parent['table'].'_'.$parent['id'];
×
116

117
            if (\in_array($cacheKey, $cache)) {
×
UNCOV
118
                continue;
×
119
            }
120
            $cache[] = $cacheKey;
×
UNCOV
121
            $current['parents'][] = $this->loop($parent['table'], $parent['id']);
×
122
        }
123

124
        return $current;
×
125
    }
126

127
    private function output(SymfonyStyle $io, array $tree, string $prepend = '', int $depth = 0): void
128
    {
129
        $itemCount = \count($tree);
×
UNCOV
130
        $i = 0;
×
131

UNCOV
132
        foreach ($tree as $item) {
×
UNCOV
133
            ++$i;
×
134

UNCOV
135
            if ($depth > 0) {
×
136
                if ($i === $itemCount) {
×
UNCOV
137
                    $newPrepend = $prepend.'└── ';
×
UNCOV
138
                    $nextPrepend = $prepend.'    ';
×
139
                } else {
140
                    $newPrepend = $prepend.'├── ';
×
UNCOV
141
                    $nextPrepend = $prepend.'│   ';
×
142
                }
143
            } else {
UNCOV
144
                $newPrepend = $prepend;
×
145
                $nextPrepend = $prepend;
×
146
            }
UNCOV
147
            $io->writeln($newPrepend.$this->createText($item['table'], $item['id']));
×
148

UNCOV
149
            if ($item['parents'] ?? false) {
×
150
                $this->output($io, $item['parents'], $nextPrepend, ++$depth);
×
151
            }
152
        }
153
    }
154

155
    private function findEntity(string $table, $id, array &$parents, bool $onlyText = false): ?string
156
    {
157
        Controller::loadLanguageFile('default');
×
158

159
        switch ($table) {
160
            case ContentModel::getTable():
×
161
                $element = ContentModel::findByIdOrAlias($id);
×
162

UNCOV
163
                if ($element) {
×
UNCOV
164
                    $parents[] = ['table' => $element->ptable, 'id' => $element->pid];
×
165

UNCOV
166
                    return 'Content Element: ' . ($GLOBALS['TL_LANG']['CTE'][$element->type][0] ?? $element->type) . ' (ID: ' . $element->id . ', Type: ' . $element->type . ')';
×
167
                }
168

UNCOV
169
                return 'Content Element not found: ID ' . $id;
×
170

171
            case ArticleModel::getTable():
×
UNCOV
172
                $element = ArticleModel::findByPk($id);
×
173

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

UNCOV
177
                    if (!$onlyText) {
×
UNCOV
178
                        foreach ($this->entityFinderHelper->findModulesByInserttag('html', 'html', 'insert_article', $element->id) as $id) {
×
179
                            $parents[] = ['table' => ModuleModel::getTable(), 'id' => $id];
×
180
                        }
181
                        foreach ($this->entityFinderHelper->findContentElementByInserttag('html', 'html', 'insert_article', $element->id) as $id) {
×
182
                            $parents[] = ['table' => ContentModel::getTable(), 'id' => $id];
×
183
                        }
184
                    }
185

UNCOV
186
                    return 'Article: ' . $element->title . ' (ID: ' . $element->id . ')';
×
187
                }
188

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

191
            case ModuleModel::getTable():
×
UNCOV
192
                if ($onlyText) {
×
193
                    Controller::loadLanguageFile('modules');
×
194
                }
UNCOV
195
                $element = ModuleModel::findByIdOrAlias($id);
×
196

197
                if ($element) {
×
UNCOV
198
                    if (!$onlyText) {
×
UNCOV
199
                        $this->findFrontendModuleParents($element, $parents, $id);
×
200
                    }
201

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

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

UNCOV
207
            case LayoutModel::getTable():
×
208
                $layout = LayoutModel::findById($id);
×
209

UNCOV
210
                if ($layout) {
×
211
                    $parents[] = ['table' => ThemeModel::getTable(), 'id' => $layout->pid];
×
212

UNCOV
213
                    return 'Layout: ' . html_entity_decode($layout->name) . ' (ID: ' . $layout->id . ')';
×
214
                }
215

UNCOV
216
                return 'Layout not found: ID ' . $id;
×
217

UNCOV
218
            case ThemeModel::getTable():
×
219
                $theme = ThemeModel::findByPk($id);
×
220

221
                if ($theme) {
×
222
                    return '<options=bold>Theme: ' . $theme->name . '</> (ID: ' . $theme->id . ')';
×
223
                }
224

UNCOV
225
                return 'Theme not found: ID ' . $id;
×
226

UNCOV
227
            case PageModel::getTable():
×
228
                $page = PageModel::findByPk($id);
×
229

UNCOV
230
                if ($page) {
×
UNCOV
231
                    return '<options=bold>Page: ' . $page->title . '</> (ID: ' . $page->id . ', Type: ' . $page->type . ', DNS: ' . $page->getFrontendUrl() . ' )';
×
232
                }
233

UNCOV
234
                return 'Page not found: ID ' . $id;
×
235
        }
236

UNCOV
237
        $element = $this->finder->find($table, $id);
×
238
        if ($element) {
×
239
            if ($onlyText) {
×
240
                return $element->getDescription();
×
241
            }
242
            if (null === $element->getParents()) {
×
243
                return null;
×
244
            }
245
            foreach ($element->getParents()($element->getTable(), $element->getId()) as $parent) {
×
246
                $parents[] = ['table' => $parent['table'], 'id' => $parent['id']];
×
247
            }
UNCOV
248
            return null;
×
249
        }
250

UNCOV
251
        return null;
×
252
    }
253

254
    private function createText(string $table, $id): string
255
    {
UNCOV
256
        $parents = [];
×
257

258
        if ($text = $this->findEntity($table, $id, $parents, true)) {
×
UNCOV
259
            return $text;
×
260
        }
261

262
        /** @var ExtendEntityFinderEvent $event */
UNCOV
263
        $event = $this->runExtendEntityFinderEvent($table, $id, [], true);
×
264

UNCOV
265
        if ($event->getOutput()) {
×
UNCOV
266
            return $event->getOutput();
×
267
        }
268

UNCOV
269
        return 'Unsupported entity: '.$table.' (ID: '.$id.')';
×
270
    }
271

272
    private function runExtendEntityFinderEvent(string $table, $id, array $parents, bool $onlyText = false): ExtendEntityFinderEvent
273
    {
UNCOV
274
        $event = $this->eventDispatcher->dispatch(
×
UNCOV
275
            new ExtendEntityFinderEvent($table, $id, $parents, [], $this->entityFinderHelper, $onlyText),
×
UNCOV
276
            ExtendEntityFinderEvent::class
×
UNCOV
277
        );
×
278

UNCOV
279
        return $event;
×
280
    }
281

282
    private function findFrontendModuleParents(ModuleModel $module, array &$parents): void
283
    {
UNCOV
284
        $contentelements = ContentModel::findBy(['tl_content.type=?', 'tl_content.module=?'], ['module', $module->id]);
×
285

UNCOV
286
        if ($contentelements) {
×
UNCOV
287
            foreach ($contentelements as $contentelement) {
×
UNCOV
288
                $parents[] = ['table' => ContentModel::getTable(), 'id' => $contentelement->id];
×
289
            }
290
        }
291

UNCOV
292
        $result = Database::getInstance()
×
UNCOV
293
            ->prepare("SELECT id FROM tl_layout WHERE modules LIKE '%:\"".(string) ((int) $module->id)."\"%'")
×
UNCOV
294
            ->execute();
×
295

UNCOV
296
        foreach ($result->fetchEach('id') as $layoutId) {
×
UNCOV
297
            $parents[] = ['table' => LayoutModel::getTable(), 'id' => $layoutId];
×
298
        }
299

UNCOV
300
        $result = Database::getInstance()
×
UNCOV
301
            ->prepare("SELECT id FROM tl_module
×
302
                        WHERE type='html'
303
                        AND (
UNCOV
304
                            html LIKE '%{{insert_module::".$module->id."}}%'
×
UNCOV
305
                            OR html LIKE '%{{insert_module::".$module->id."::%')")
×
UNCOV
306
            ->execute();
×
307

UNCOV
308
        foreach ($result->fetchEach('id') as $moduleId) {
×
UNCOV
309
            $parents[] = ['table' => ModuleModel::getTable(), 'id' => $moduleId];
×
310
        }
311
    }
312

313
    private function findInserttags(ExtendEntityFinderEvent $event): void
314
    {
UNCOV
315
        $stmt = $this->connection->prepare(
×
UNCOV
316
            "SELECT id FROM tl_module WHERE type='html' AND html LIKE ?");
×
317

UNCOV
318
        foreach ($event->getInserttags() as $inserttag) {
×
UNCOV
319
            $result = $stmt->executeQuery(['%'.$inserttag.'%']);
×
320

UNCOV
321
            foreach ($result->fetchAllAssociative() as $row) {
×
UNCOV
322
                $event->addParent(ModuleModel::getTable(), $row['id']);
×
323
            }
324
        }
325
    }
326
}
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