Skip to content

Commit e507dc8

Browse files
authored
Merge pull request #2432 from elcreator/add-template-processor-db-or-file-switch
add(core): Template processor db or file switch
2 parents 6bc0a41 + d984246 commit e507dc8

19 files changed

Lines changed: 1390 additions & 71 deletions

‎core/config/view.php‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,35 @@
44
EVO_BASE_PATH . 'views/'
55
],
66
'compiled' => EVO_STORAGE_PATH . 'blade',
7+
8+
/*
9+
|--------------------------------------------------------------------------
10+
| Template file engines
11+
|--------------------------------------------------------------------------
12+
|
13+
| Which view engines the template form offers to scaffold a file for. A
14+
| document whose template alias resolves to a file under one of the view
15+
| paths above is rendered by that file's engine instead of by the parser,
16+
| and Laravel's view factory already resolves any extension registered with
17+
| it - so this list is not what makes an engine work, only what the manager
18+
| is willing to create a file for. Engines opt in: an extension nobody
19+
| declared here stays out of the UI, and one declared without its engine
20+
| actually being registered is dropped rather than offered.
21+
|
22+
| 'processor' names the [(chunk_processor)] value an engine belongs to, and
23+
| only decides which entry the form preselects. A plugin adds its own from
24+
| its service provider:
25+
|
26+
| config(['view.template_engines' => array_merge(
27+
| config('view.template_engines', []),
28+
| ['latte' => ['label' => 'Latte', 'processor' => 'aLatteX']]
29+
| )]);
30+
|
31+
*/
32+
'template_engines' => [
33+
'blade.php' => ['label' => 'Blade', 'processor' => null],
34+
'php' => ['label' => 'PHP', 'processor' => null],
35+
],
736
'directive' => [
837
//----------
938
/**
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
/**
8+
* Records which template engine a template renders with.
9+
*
10+
* A template whose alias matches a file under a view path is rendered from that
11+
* file, and until now which file won was decided by the order engines happened
12+
* to register with Laravel's view factory - it prepends, so the last plugin to
13+
* boot took precedence over every template on the site at once. The engine
14+
* chosen when the template was saved is stored here instead, so the answer
15+
* belongs to the template rather than to the boot order.
16+
*
17+
* Empty means "decide the old way", which is what every existing template says.
18+
*/
19+
class AddTemplatefileextensionToSiteTemplates extends Migration {
20+
public function up() {
21+
if (!Schema::hasTable('site_templates') || Schema::hasColumn('site_templates', 'templatefileextension')) {
22+
return;
23+
}
24+
25+
Schema::table('site_templates', function (Blueprint $table) {
26+
$table->string('templatefileextension', 20)->default('')->after('templatealias');
27+
});
28+
}
29+
30+
public function down() {
31+
if (Schema::hasTable('site_templates') && Schema::hasColumn('site_templates', 'templatefileextension')) {
32+
Schema::table('site_templates', function (Blueprint $table) {
33+
$table->dropColumn('templatefileextension');
34+
});
35+
}
36+
}
37+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
/**
8+
* Records where a template's code lives: the database, or a file.
9+
*
10+
* Until now a template whose alias happened to match a file under a view path
11+
* was rendered from that file whether or not anybody meant it to be, and there
12+
* was no way to say otherwise. 'db' says so, and costs nothing to check - the
13+
* lookup is skipped entirely rather than probing every view path for every
14+
* registered extension on every request.
15+
*
16+
* Empty keeps the old behaviour, which is what every existing template says.
17+
*/
18+
class AddTemplatesourceToSiteTemplates extends Migration {
19+
public function up() {
20+
if (!Schema::hasTable('site_templates') || Schema::hasColumn('site_templates', 'templatesource')) {
21+
return;
22+
}
23+
24+
Schema::table('site_templates', function (Blueprint $table) {
25+
$table->string('templatesource', 10)->default('')->after('templatealias');
26+
});
27+
}
28+
29+
public function down() {
30+
if (Schema::hasTable('site_templates') && Schema::hasColumn('site_templates', 'templatesource')) {
31+
Schema::table('site_templates', function (Blueprint $table) {
32+
$table->dropColumn('templatesource');
33+
});
34+
}
35+
}
36+
}

‎core/src/Controllers/Template.php‎

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,22 @@
22

33
use EvolutionCMS\Models;
44
use EvolutionCMS\Interfaces\ManagerTheme;
5+
use EvolutionCMS\Support\TemplateFileEngines;
6+
use EvolutionCMS\TemplateProcessor;
57
use Illuminate\Support\Collection;
68
use Illuminate\Database\Eloquent;
79

810
class Template extends AbstractController implements ManagerTheme\PageControllerInterface
911
{
1012
protected $view = 'page.template';
1113

14+
/**
15+
* A template file larger than this is not handed to the editor for
16+
* switching. Templates are not this big; something else is going on, and a
17+
* megabyte of it does not belong inline in the form.
18+
*/
19+
private const MAX_EDITABLE_FILE_BYTES = 524288;
20+
1221
protected $events = [
1322
'OnTempFormPrerender',
1423
'OnTempFormRender'
@@ -51,6 +60,48 @@ public function canView(): bool
5160
public function process() : bool
5261
{
5362
$this->object = $this->parameterData();
63+
$engines = TemplateFileEngines::make();
64+
$existingFiles = $engines->existing((string) $this->object->templatealias);
65+
$winningExtension = $engines->winner(
66+
(string) $this->object->templatealias,
67+
(string) $this->object->templatefileextension
68+
);
69+
70+
// A template that reads from a file shows the file in the editor, not
71+
// the database column - editing what is not rendered would be a trap.
72+
// The database copy stays where it is as the fallback it already is.
73+
// "Automatic" only describes templates that predate the setting. A
74+
// template being created now has no old behaviour to preserve, so it
75+
// starts where its code is being typed.
76+
$templateSource = (string) $this->object->templatesource;
77+
if ($templateSource === '' && !$this->object->getKey()) {
78+
$templateSource = TemplateProcessor::SOURCE_DATABASE;
79+
}
80+
81+
// The editor has to be able to show whatever the selectors point at, the
82+
// moment they are moved - otherwise it keeps displaying code that the
83+
// save is not going to write, which is the one thing an editor must
84+
// never do. The database column and every file already on disk are
85+
// handed to the page so the swap needs no round trip.
86+
$databaseContent = (string) $this->object->content;
87+
$fileContents = [];
88+
foreach ($existingFiles as $extension => $path) {
89+
if (is_readable($path) && filesize($path) <= self::MAX_EDITABLE_FILE_BYTES) {
90+
$fileContents[$extension] = (string) file_get_contents($path);
91+
}
92+
}
93+
94+
$sourceFile = null;
95+
if ($templateSource === TemplateProcessor::SOURCE_FILE) {
96+
$sourceFile = $engines->pathFor(
97+
(string) $this->object->templatealias,
98+
(string) $this->object->templatefileextension
99+
);
100+
$pinned = ltrim((string) $this->object->templatefileextension, '.');
101+
if ($sourceFile !== null && isset($fileContents[$pinned])) {
102+
$this->object->content = $fileContents[$pinned];
103+
}
104+
}
54105
$this->parameters = [
55106
'data' => $this->object,
56107
'categories' => $this->parameterCategories(),
@@ -69,7 +120,24 @@ function (Models\SiteTmplvar $item) {
69120
),
70121
'action' => $this->getIndex(),
71122
'events' => $this->parameterEvents(),
72-
'actionButtons' => $this->parameterActionButtons()
123+
'actionButtons' => $this->parameterActionButtons(),
124+
'templateFileEngines' => $engines->all(),
125+
// A new template follows [(chunk_processor)]; one that already
126+
// recorded an engine keeps showing it, so that opening a template
127+
// and saving it cannot quietly re-point it at another engine.
128+
'templateFileDefault' => $engines->isRegistered($this->object->templatefileextension)
129+
? ltrim((string) $this->object->templatefileextension, '.')
130+
: $engines->defaultExtension(
131+
(string) $this->managerTheme->getCore()->getConfig('chunk_processor')
132+
),
133+
// Only one file can render an alias; the rest are shadowed, so the
134+
// form says which ones are already there and which of them wins.
135+
'templateFileExisting' => $existingFiles,
136+
'templateFileWinner' => $winningExtension,
137+
'templateSource' => $templateSource,
138+
'templateSourceFile' => $sourceFile,
139+
'templateDbContent' => $databaseContent,
140+
'templateFileContents' => $fileContents
73141
];
74142

75143
return true;

‎core/src/Core.php‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,23 @@ class Core extends AbstractLaravel implements Interfaces\CoreInterface
8484
public $documentMethod;
8585
public $documentGenerated;
8686
public $documentContent;
87+
88+
/**
89+
* The view the current document is rendered from, or '' when it is rendered
90+
* from template code by the parser.
91+
*
92+
* A template whose alias resolves to a file under a view path is handed to
93+
* Laravel's view factory, and parseDocumentSource() is then skipped - so by
94+
* the time OnLoadWebDocument fires, documentContent holds finished HTML
95+
* from another engine rather than template source. A listener that
96+
* transforms template code has to be able to tell the two apart, and
97+
* calling TemplateProcessor::getBladeDocumentContent() again to find out is
98+
* not free: it can run a controller's main() a second time.
99+
*
100+
* @var string
101+
* @since 3.5.9
102+
*/
103+
public $documentTemplateView = '';
87104
public $documentOutput;
88105
public $tstart = 0;
89106
public $mstart = 0;
@@ -3300,6 +3317,7 @@ public function prepareResponse()
33003317
}
33013318

33023319
$template = TemplateProcessor::getBladeDocumentContent();
3320+
$this->documentTemplateView = $template ? (string) $template : '';
33033321

33043322
if ($template) {
33053323
$this->documentObject['cacheable'] = 0;
@@ -3326,7 +3344,15 @@ public function prepareResponse()
33263344
app('DLTemplate')->blade->share(array_merge($data, $viewData));
33273345
}
33283346

3329-
$tpl = $this['view']->make($template, $viewData);
3347+
// A template that pinned an engine names the exact file, so the
3348+
// view factory is handed a path rather than a name - resolving
3349+
// a name would put the question back to extension registration
3350+
// order and hand the document to whichever engine booted last.
3351+
$viewPath = TemplateProcessor::getDocumentViewPath();
3352+
3353+
$tpl = $viewPath !== ''
3354+
? $this['view']->file($viewPath, $viewData)
3355+
: $this['view']->make($template, $viewData);
33303356
$templateCode = $tpl->render();
33313357
} else {
33323358
// get the template and start parsing!

‎core/src/Models/SiteTemplate.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
*
99
* @property int $id
1010
* @property string $templatename
11+
* @property string $templatealias
12+
* @property string $templatesource
13+
* @property string $templatefileextension
1114
* @property string $description
1215
* @property int $editor_type
1316
* @property int $category
@@ -60,6 +63,8 @@ class SiteTemplate extends Eloquent\Model
6063
protected $fillable = [
6164
'templatename',
6265
'templatealias',
66+
'templatesource',
67+
'templatefileextension',
6368
'description',
6469
'editor_type',
6570
'category',

0 commit comments

Comments
 (0)