1: <?php
2:
3: namespace Charcoal\Cms\Route;
4:
5: use RuntimeException;
6: use InvalidArgumentException;
7:
8:
9: use Pimple\Container;
10:
11:
12: use Psr\Http\Message\RequestInterface;
13: use Psr\Http\Message\ResponseInterface;
14:
15:
16: use Charcoal\App\Route\TemplateRoute;
17:
18:
19: use Charcoal\Cms\TemplateableInterface;
20:
21:
22: use Charcoal\Factory\FactoryInterface;
23:
24:
25: use Charcoal\Model\ModelInterface;
26: use Charcoal\Loader\CollectionLoader;
27:
28:
29: use Charcoal\Translator\TranslatorAwareTrait;
30:
31:
32: use Charcoal\Object\ObjectRoute;
33: use Charcoal\Object\ObjectRouteInterface;
34: use Charcoal\Object\RoutableInterface;
35:
36: 37: 38: 39: 40: 41:
42: class GenericRoute extends TemplateRoute
43: {
44: use TranslatorAwareTrait;
45:
46: 47: 48: 49: 50:
51: private $path;
52:
53: 54: 55: 56: 57:
58: private $objectRoute;
59:
60: 61: 62: 63: 64:
65: private $contextObject;
66:
67: 68: 69: 70: 71:
72: private $modelFactory;
73:
74: 75: 76: 77: 78:
79: private $collectionLoader;
80:
81: 82: 83: 84: 85: 86: 87: 88:
89: protected $objectRouteClass = ObjectRoute::class;
90:
91: 92: 93: 94: 95:
96: protected $availableTemplates = [];
97:
98: 99: 100: 101: 102:
103: public function __construct(array $data)
104: {
105: parent::__construct($data);
106:
107: $this->setPath(ltrim($data['path'], '/'));
108: }
109:
110: 111: 112: 113: 114: 115:
116: public function setDependencies(Container $container)
117: {
118: $this->setTranslator($container['translator']);
119: $this->setModelFactory($container['model/factory']);
120: $this->setCollectionLoader($container['model/collection/loader']);
121:
122: if (isset($container['config']['templates'])) {
123: $this->availableTemplates = $container['config']['templates'];
124: }
125: }
126:
127: 128: 129: 130: 131: 132:
133: public function pathResolvable(Container $container)
134: {
135: $this->setDependencies($container);
136:
137: $object = $this->loadObjectRouteFromPath();
138: if (!$object->id()) {
139: return false;
140: }
141:
142: $contextObject = $this->loadContextObject();
143:
144: if (!$contextObject || !$contextObject->id()) {
145: return false;
146: }
147:
148: return (!!$contextObject->active() && !!$contextObject->isActiveRoute());
149: }
150:
151: 152: 153: 154: 155: 156: 157: 158:
159: public function __invoke(
160: Container $container,
161: RequestInterface $request,
162: ResponseInterface $response
163: ) {
164: $response = $this->resolveLatestObjectRoute($request, $response);
165:
166: if (!$response->isRedirect()) {
167: $this->resolveTemplateContextObject();
168:
169: $templateContent = $this->templateContent($container, $request);
170:
171: $response->write($templateContent);
172: }
173:
174: return $response;
175: }
176:
177: 178: 179: 180: 181:
182: protected function resolveLatestObjectRoute(
183: RequestInterface $request,
184: ResponseInterface $response
185: ) {
186:
187: $objectRoute = $this->loadObjectRouteFromPath();
188:
189:
190: $latest = $this->getLatestObjectPathHistory($objectRoute);
191:
192:
193: if ($latest->creationDate() > $objectRoute->creationDate()) {
194: $redirection = $this->parseRedirect($latest->slug(), $request);
195: $response = $response->withRedirect($redirection, 301);
196: }
197:
198: return $response;
199: }
200:
201: 202: 203:
204: protected function resolveTemplateContextObject()
205: {
206: $config = $this->config();
207:
208: $objectRoute = $this->loadObjectRouteFromPath();
209: $contextObject = $this->loadContextObject();
210: $currentLang = $objectRoute->lang();
211:
212:
213: $this->setLocale($currentLang);
214:
215: $templateChoice = [];
216:
217:
218: if ($contextObject instanceof TemplateableInterface) {
219: $identProperty = $contextObject->property('template_ident');
220: $controllerProperty = $contextObject->property('controller_ident');
221:
222:
223: $templateIdent = $contextObject->templateIdent() ?: $objectRoute->routeTemplate();
224:
225: $controllerIdent = $contextObject->controllerIdent() ?: $templateIdent;
226:
227: $templateChoice = $identProperty->choice($templateIdent);
228: } else {
229:
230: $templateIdent = $objectRoute->routeTemplate();
231: $controllerIdent = $templateIdent;
232: foreach ($this->availableTemplates as $templateKey => $templateData) {
233: if (!isset($templateData['value'])) {
234: $templateData['value'] = $templateKey;
235: }
236: if ($templateData['value'] === $templateIdent) {
237: $templateChoice = $templateData;
238: break;
239: }
240: }
241: }
242:
243:
244:
245: if (isset($templateChoice['template'])) {
246: $templatePath = $templateChoice['template'];
247: $templateController = $templateChoice['template'];
248: } else {
249: $templatePath = $templateIdent;
250: $templateController = $controllerIdent;
251: }
252:
253:
254: if (isset($templateChoice['controller'])) {
255: $templateController = $templateChoice['controller'];
256: }
257:
258: $config['template'] = $templatePath;
259: $config['controller'] = $templateController;
260:
261:
262: $templateOptions = [];
263:
264:
265: if (isset($templateChoice['template_options'])) {
266: $templateOptions = $templateChoice['template_options'];
267: }
268:
269:
270: if ($contextObject instanceof TemplateableInterface) {
271: if (!empty($contextObject->templateOptions())) {
272: $templateOptions = $contextObject->templateOptions();
273: }
274: }
275:
276: if (isset($templateOptions) && $templateOptions) {
277:
278: $config['template_data'] = array_merge($config['template_data'], $templateOptions);
279: }
280:
281:
282: $routeOptions = $objectRoute->routeOptions();
283: if ($routeOptions && count($routeOptions)) {
284: $config['template_data'] = array_merge($config['template_data'], $routeOptions);
285: }
286:
287: $this->setConfig($config);
288:
289: return $this;
290: }
291:
292: 293: 294: 295: 296:
297: protected function createTemplate(Container $container, RequestInterface $request)
298: {
299: $template = parent::createTemplate($container, $request);
300:
301: $contextObject = $this->loadContextObject();
302: $template->setContextObject($contextObject);
303:
304: return $template;
305: }
306:
307: 308: 309: 310: 311:
312: public function createRouteObject()
313: {
314: $route = $this->modelFactory()->create($this->objectRouteClass());
315:
316: return $route;
317: }
318:
319: 320: 321: 322: 323: 324: 325:
326: protected function setObjectRouteClass($className)
327: {
328: if (!is_string($className)) {
329: throw new InvalidArgumentException(
330: 'Route class name must be a string.'
331: );
332: }
333:
334: $this->objectRouteClass = $className;
335:
336: return $this;
337: }
338:
339: 340: 341: 342: 343:
344: public function objectRouteClass()
345: {
346: return $this->objectRouteClass;
347: }
348:
349: 350: 351: 352: 353: 354: 355: 356:
357: protected function loadContextObject()
358: {
359: if ($this->contextObject) {
360: return $this->contextObject;
361: }
362:
363: $objectRoute = $this->loadObjectRouteFromPath();
364:
365: $obj = $this->modelFactory()->create($objectRoute->routeObjType());
366: $obj->load($objectRoute->routeObjId());
367:
368: $this->contextObject = $obj;
369:
370: return $this->contextObject;
371: }
372:
373: 374: 375: 376: 377:
378: protected function loadObjectRouteFromPath()
379: {
380: if ($this->objectRoute) {
381: return $this->objectRoute;
382: }
383:
384:
385:
386: $route = $this->createRouteObject();
387: $route->loadFromQuery(
388: 'SELECT * FROM `'.$route->source()->table().'` WHERE (`slug` = :route1 OR `slug` = :route2) LIMIT 1',
389: [
390: 'route1' => '/'.$this->path(),
391: 'route2' => $this->path()
392: ]
393: );
394:
395: $this->objectRoute = $route;
396:
397: return $this->objectRoute;
398: }
399:
400: 401: 402: 403: 404: 405: 406: 407: 408: 409:
410: public function getLatestObjectPathHistory(ObjectRouteInterface $route)
411: {
412: $loader = $this->collectionLoader();
413: $loader
414: ->setModel($route)
415: ->addFilter('active', true)
416: ->addFilter('route_obj_type', $route->routeObjType())
417: ->addFilter('route_obj_id', $route->routeObjId())
418: ->addFilter('lang', $route->lang())
419: ->addOrder('creation_date', 'desc')
420: ->setPage(1)
421: ->setNumPerPage(1);
422:
423: if ($route->routeOptionsIdent()) {
424: $loader->addFilter('route_options_ident', $route->routeOptionsIdent());
425: } else {
426: $loader->addFilter('route_options_ident', '', ['operator' => 'IS NULL']);
427: }
428:
429: $collection = $loader->load();
430: $routes = $collection->objects();
431:
432: $latestRoute = $routes[0];
433:
434: return $latestRoute;
435: }
436:
437: 438: 439:
440:
441: 442: 443: 444: 445: 446:
447: protected function setPath($path)
448: {
449: $this->path = $path;
450:
451: return $this;
452: }
453:
454: 455: 456: 457: 458: 459:
460: protected function setModelFactory(FactoryInterface $factory)
461: {
462: $this->modelFactory = $factory;
463:
464: return $this;
465: }
466:
467: 468: 469: 470: 471: 472:
473: public function setCollectionLoader(CollectionLoader $loader)
474: {
475: $this->collectionLoader = $loader;
476:
477: return $this;
478: }
479:
480: 481: 482: 483: 484: 485:
486: protected function setLocale($langCode)
487: {
488: $translator = $this->translator();
489: $translator->setLocale($langCode);
490:
491: $available = $translator->locales();
492: $fallbacks = $translator->getFallbackLocales();
493:
494: array_unshift($fallbacks, $langCode);
495: $fallbacks = array_unique($fallbacks);
496:
497: $locales = [];
498: foreach ($fallbacks as $code) {
499: if (isset($available[$code])) {
500: $locale = $available[$code];
501: if (isset($locale['locales'])) {
502: $choices = (array)$locale['locales'];
503: array_push($locales, ...$choices);
504: } elseif (isset($locale['locale'])) {
505: array_push($locales, $locale['locale']);
506: }
507: }
508: }
509:
510: $locales = array_unique($locales);
511:
512: if ($locales) {
513: setlocale(LC_ALL, $locales);
514: }
515: }
516:
517: 518: 519:
520:
521: 522: 523: 524: 525:
526: protected function path()
527: {
528: return $this->path;
529: }
530:
531: 532: 533: 534: 535: 536:
537: public function modelFactory()
538: {
539: if (!isset($this->modelFactory)) {
540: throw new RuntimeException(
541: sprintf('Model Factory is not defined for "%s"', get_class($this))
542: );
543: }
544:
545: return $this->modelFactory;
546: }
547:
548: 549: 550: 551: 552: 553:
554: protected function collectionLoader()
555: {
556: if (!isset($this->collectionLoader)) {
557: throw new RuntimeException(
558: sprintf('Collection Loader is not defined for "%s"', get_class($this))
559: );
560: }
561:
562: return $this->collectionLoader;
563: }
564:
565: 566: 567:
568: protected function cacheEnabled()
569: {
570: $obj = $this->loadContextObject();
571: return $obj['cache'] ?: false;
572: }
573:
574: 575: 576:
577: protected function cacheTtl()
578: {
579: $obj = $this->loadContextObject();
580: return $obj['cache_ttl'] ?: 0;
581: }
582:
583: 584: 585:
586: protected function cacheIdent()
587: {
588: $obj = $this->loadContextObject();
589: return $obj->objType().'.'.$obj->id();
590: }
591: }
592: