Issues (18)

src/Api/Providers/AllLinksControllerInfo.php (1 issue)

1
<?php
2
3
namespace Sunnysideup\TemplateOverview\Api\Providers;
4
5
use ReflectionClass;
6
use ReflectionMethod;
7
use SilverStripe\Admin\LeftAndMain;
8
use SilverStripe\CMS\Controllers\ContentController;
9
use SilverStripe\Control\Controller;
10
use SilverStripe\Control\Director;
11
use SilverStripe\Core\ClassInfo;
12
use SilverStripe\Core\Config\Config;
13
use SilverStripe\Core\Injector\Injector;
14
use SilverStripe\ORM\DataObject;
15
use SilverStripe\ORM\DB;
16
use Sunnysideup\TemplateOverview\Api\AllLinksProviderBase;
17
18
class AllLinksControllerInfo extends AllLinksProviderBase
19
{
20
    /**
21
     * @var array
22
     */
23
    protected $linksAndActions = [];
24
25
    /**
26
     * @var array
27
     */
28
    protected $reflectionClasses = [];
29
30
    /**
31
     * @var array
32
     */
33
    protected $classObjects = [];
34
35
    /**
36
     * @var array
37
     */
38
    protected $dataRecordClassNames = [];
39
40
    /**
41
     * @var array
42
     */
43
    protected $dataRecordClassObjects = [];
44
45
    /**
46
     * @var array
47
     */
48
    protected $routes = [];
49
50
    /**
51
     * @var array
52
     */
53
    protected $nameSpaces = [];
54
55
    /**
56
     * @param array $nameSpaces
57
     */
58
    public function setValidNameSpaces($nameSpaces): self
59
    {
60
        $this->nameSpaces = $nameSpaces;
61
62
        return $this;
63
    }
64
65
    public function getCustomisedLinks(): array
66
    {
67
        $finalArray = [];
68
        $classes = ClassInfo::subclassesFor(DataObject::class, false);
69
        foreach ($classes as $className) {
70
            if (\Page::class !== $className) {
0 ignored issues
show
The type Page was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
71
                $classObject = $className::get()->first();
72
                if ($classObject && $classObject->hasMethod('templateOverviewTests')) {
73
                    $array = $classObject->templateOverviewTests();
74
                    if (is_array($array) && count($array)) {
75
                        foreach ($array as $customLink) {
76
                            $finalArray[$customLink] = $customLink;
77
                        }
78
                    }
79
                }
80
            }
81
        }
82
83
        return $finalArray;
84
    }
85
86
    public function getAllLinksInner(): array
87
    {
88
        $finalFinalArray = [];
89
90
        $linksAndActions = $this->getLinksAndActions();
91
        $allowedActions = $linksAndActions['Actions'];
92
        $controllerLinks = $linksAndActions['Links'];
93
        $finalArray = $linksAndActions['CustomLinks'];
94
95
        // die('---');
96
        //construct array!
97
        foreach ($allowedActions as $className => $methods) {
98
            $link = $controllerLinks[$className];
99
            if ($link) {
100
                $finalArray[$link] = $className;
101
            } else {
102
                $link = '???';
103
            }
104
105
            if ('/' !== substr((string) $link, -1)) {
106
                $link .= '/';
107
            }
108
109
            if ('/' !== substr((string) $link, 0, 1)) {
110
                $link = '/' . $link;
111
            }
112
113
            if (is_array($methods)) {
114
                foreach ($methods as $method) {
115
                    unset($allowedActions[$className][$method]);
116
                    $finalArray[$link . $method . '/'] = $className;
117
                }
118
            }
119
        }
120
121
        foreach ($finalArray as $link => $className) {
122
            $finalFinalArray[] = [
123
                'ClassName' => $className,
124
                'Link' => $link,
125
            ];
126
        }
127
128
        usort($finalFinalArray, function ($a, $b) {
129
            if ($a['ClassName'] !== $b['ClassName']) {
130
                return $a['ClassName'] <=> $b['ClassName'];
131
            }
132
133
            return $a['Link'] <=> $b['Link'];
134
        });
135
136
        return $finalFinalArray;
137
    }
138
139
    /**
140
     * returns Array with Links and Actions.
141
     */
142
    public function getLinksAndActions(): array
143
    {
144
        if ([] === $this->linksAndActions) {
145
            $this->linksAndActions['Links'] = [];
146
            $this->linksAndActions['Actions'] = [];
147
            $this->linksAndActions['CustomLinks'] = [];
148
            $classes = ClassInfo::subclassesFor(Controller::class);
149
            foreach ($classes as $className) {
150
                $this->getLinksAndActionsInner($className);
151
            }
152
        }
153
154
        return $this->linksAndActions;
155
    }
156
157
    protected function getLinksAndActionsInner($className)
158
    {
159
        $isValidController = $this->isValidController($className);
160
        if ($isValidController) {
161
            $this->linksAndActions['Links'][$className] = '';
162
163
            //main links
164
165
            //custom links
166
            $customLinks = $this->findCustomLinks($className);
167
            foreach ($customLinks as $customLink) {
168
                $this->linksAndActions['CustomLinks'][$customLink] = $className;
169
            }
170
171
            $link = $this->findLink($className);
172
            if ('' !== $link) {
173
                $this->linksAndActions['Links'][$className] = $link;
174
            }
175
176
            $array = array_merge(
177
                $this->findAllowedActions($className),
178
                $this->findURLHandlers($className)
179
            );
180
            $this->linksAndActions['Actions'][$className] = $array;
181
        }
182
    }
183
184
    /**
185
     * can it be used?
186
     *
187
     * @param string $className
188
     */
189
    protected function isValidController($className): bool
190
    {
191
        return (bool) $this->controllerReflectionClass($className);
192
    }
193
194
    /**
195
     * @param string $className
196
     *
197
     * @return null|ReflectionClass
198
     */
199
    protected function controllerReflectionClass($className)
200
    {
201
        if (! isset($this->reflectionClasses[$className])) {
202
            $this->reflectionClasses[$className] = null;
203
            //skip base class
204
            if (Controller::class === $className) {
205
                return null;
206
            }
207
208
            //check for abstract ones
209
            $controllerReflectionClass = new ReflectionClass($className);
210
            if ($controllerReflectionClass->isAbstract()) {
211
                // echo '<hr />Ditching because of abstract: '.$className;
212
                return null;
213
            }
214
215
            //match to filter
216
            $filterMatch = ! (bool) count($this->nameSpaces);
217
            foreach ($this->nameSpaces as $filter) {
218
                if (false !== strpos($className, $filter)) {
219
                    $filterMatch = true;
220
                }
221
            }
222
223
            if (! $filterMatch) {
224
                return null;
225
            }
226
227
            //check for ones that can not be constructed
228
            if ($controllerReflectionClass->isSubclassOf(LeftAndMain::class)) {
229
                return null;
230
            }
231
232
            $params = $controllerReflectionClass->getConstructor()->getParameters();
233
            if ($controllerReflectionClass->isSubclassOf(ContentController::class)) {
234
                //do nothing
235
            } elseif ([] !== $params) {
236
                return null;
237
            }
238
239
            $this->reflectionClasses[$className] = $controllerReflectionClass;
240
241
            return $this->reflectionClasses[$className];
242
        }
243
244
        return $this->reflectionClasses[$className];
245
    }
246
247
    /**
248
     * @return null|DataObject
249
     */
250
    protected function findSingleton(string $className)
251
    {
252
        $this->classObjects[$className] = null;
253
        if (null !== $this->controllerReflectionClass($className)) {
254
            $this->classObjects[$className] = null;
255
            if (! isset($this->classObjects[$className])) {
256
                try {
257
                    $this->classObjects[$className] = Injector::inst()->get($className);
258
                } catch (\Error $error) {
259
                    $this->classObjects[$className] = null;
260
                }
261
            }
262
        }
263
264
        return $this->classObjects[$className];
265
    }
266
267
    /**
268
     * @param string $className
269
     *
270
     * @return null|DataObject
271
     */
272
    protected function findDataRecord($className)
273
    {
274
        if (! isset($this->dataRecordClassObjects[$className])) {
275
            $this->dataRecordClassObjects[$className] = null;
276
            $dataRecordClassName = substr((string) $className, 0, -1 * strlen('Controller'));
277
            if (class_exists($dataRecordClassName) && is_subclass_of($dataRecordClassName, DataObject::class)) {
278
                $this->dataRecordClassNames[$className] = $dataRecordClassName;
279
                $this->dataRecordClassObjects[$className] = DataObject::get_one(
280
                    $dataRecordClassName,
281
                    null,
282
                    null,
283
                    DB::get_conn()->random() . ' ASC'
284
                );
285
            }
286
        }
287
288
        return $this->dataRecordClassObjects[$className];
289
    }
290
291
    /**
292
     * @param string $className
293
     */
294
    protected function findCustomLinks($className): array
295
    {
296
        $array1 = [];
297
        $array2 = [];
298
        $classObject = $this->findSingleton($className);
299
        if ($classObject) {
300
            if ($classObject->hasMethod('templateOverviewTests')) {
301
                $array1 = $classObject->templateOverviewTests();
302
            }
303
        }
304
305
        $object = $this->findDataRecord($className);
306
        if (null !== $object) {
307
            if ($object->hasMethod('templateOverviewTests')) {
308
                $array2 = $object->templateOverviewTests();
309
            }
310
        }
311
312
        return $array1 + $array2;
313
    }
314
315
    /**
316
     * @param string $className
317
     */
318
    protected function findAllowedActions($className): array
319
    {
320
        $allowedActions = Config::inst()->get($className, 'allowed_actions', Config::UNINHERITED);
321
322
        return $this->getBestArray($allowedActions);
323
    }
324
325
    /**
326
     * @param string $className
327
     */
328
    protected function findURLHandlers($className): array
329
    {
330
        $urlHandlers = Config::inst()->get($className, 'url_handlers', Config::UNINHERITED);
331
332
        return $this->getBestArray($urlHandlers);
333
    }
334
335
    protected function getBestArray($array): array
336
    {
337
        if (is_array($array)) {
338
            if ($this->isAssociativeArray($array)) {
339
                $array = array_keys($array);
340
            }
341
        } else {
342
            $array = [];
343
        }
344
345
        return $array;
346
    }
347
348
    /**
349
     * @param string $className
350
     */
351
    protected function findLink($className): string
352
    {
353
        $link = $this->findControllerLink($className);
354
        if ('' === $link) {
355
            $link = $this->findRouteLink($className);
356
            if ('' === $link) {
357
                $link = $this->findSegmentLink($className);
358
                if ('' === $link) {
359
                    $link = $this->findMethodLink($className);
360
                }
361
            }
362
        }
363
364
        $link = '/' . $link . '/';
365
366
        return str_replace('//', '/', $link);
367
    }
368
369
    /**
370
     * @param string $className
371
     */
372
    protected function findControllerLink($className): string
373
    {
374
        $object = $this->findDataRecord($className);
375
        if ($object && $object->hasMethod('Link')) {
376
            $tmp = $object->Link();
377
            $tmpArray = explode('?', $tmp);
378
379
            return $tmpArray[0];
380
        }
381
382
        return '';
383
    }
384
385
    /**
386
     * @param string $className
387
     */
388
    protected function findRouteLink($className): string
389
    {
390
        if ([] === $this->routes) {
391
            $this->routes = Config::inst()->get(Director::class, 'rules');
392
        }
393
394
        $route = array_search($className, $this->routes, true);
395
        if ($route) {
396
            $routeArray = explode('//', $route);
397
398
            return $routeArray[0];
399
        }
400
401
        return '';
402
    }
403
404
    /**
405
     * @param string $className
406
     */
407
    protected function findSegmentLink($className): string
408
    {
409
        //check if there is a link of some sort
410
        $urlSegment = Config::inst()->get($className, 'url_segment');
411
        if ($urlSegment) {
412
            $urlSegment .= '/';
413
        } else {
414
            $urlSegment = '';
415
        }
416
417
        return $urlSegment;
418
    }
419
420
    /**
421
     * @param string $className
422
     */
423
    protected function findMethodLink($className): string
424
    {
425
        $controllerReflectionClass = $this->controllerReflectionClass($className);
426
        if (null !== $controllerReflectionClass) {
427
            foreach ($controllerReflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
428
                if ($method->class === $className) {
429
                    if ('Link' === $method->name) {
430
                        $classObject = $this->findSingleton($className);
431
                        if ($classObject) {
432
                            return $classObject->Link();
433
                        }
434
                    }
435
                }
436
            }
437
        }
438
439
        return '';
440
    }
441
442
    private function isAssociativeArray(array $arr): bool
443
    {
444
        if ([] === $arr) {
445
            return false;
446
        }
447
448
        return array_keys($arr) !== range(0, count($arr) - 1);
449
    }
450
}
451