Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions core/config/view.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,35 @@
EVO_BASE_PATH . 'views/'
],
'compiled' => EVO_STORAGE_PATH . 'blade',

/*
|--------------------------------------------------------------------------
| Template file engines
|--------------------------------------------------------------------------
|
| Which view engines the template form offers to scaffold a file for. A
| document whose template alias resolves to a file under one of the view
| paths above is rendered by that file's engine instead of by the parser,
| and Laravel's view factory already resolves any extension registered with
| it - so this list is not what makes an engine work, only what the manager
| is willing to create a file for. Engines opt in: an extension nobody
| declared here stays out of the UI, and one declared without its engine
| actually being registered is dropped rather than offered.
|
| 'processor' names the [(chunk_processor)] value an engine belongs to, and
| only decides which entry the form preselects. A plugin adds its own from
| its service provider:
|
| config(['view.template_engines' => array_merge(
| config('view.template_engines', []),
| ['latte' => ['label' => 'Latte', 'processor' => 'aLatteX']]
| )]);
|
*/
'template_engines' => [
'blade.php' => ['label' => 'Blade', 'processor' => null],
'php' => ['label' => 'PHP', 'processor' => null],
],
'directive' => [
//----------
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

/**
* Records which template engine a template renders with.
*
* A template whose alias matches a file under a view path is rendered from that
* file, and until now which file won was decided by the order engines happened
* to register with Laravel's view factory - it prepends, so the last plugin to
* boot took precedence over every template on the site at once. The engine
* chosen when the template was saved is stored here instead, so the answer
* belongs to the template rather than to the boot order.
*
* Empty means "decide the old way", which is what every existing template says.
*/
class AddTemplatefileextensionToSiteTemplates extends Migration {
public function up() {
if (!Schema::hasTable('site_templates') || Schema::hasColumn('site_templates', 'templatefileextension')) {
return;
}

Schema::table('site_templates', function (Blueprint $table) {
$table->string('templatefileextension', 20)->default('')->after('templatealias');
});
}

public function down() {
if (Schema::hasTable('site_templates') && Schema::hasColumn('site_templates', 'templatefileextension')) {
Schema::table('site_templates', function (Blueprint $table) {
$table->dropColumn('templatefileextension');
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

/**
* Records where a template's code lives: the database, or a file.
*
* Until now a template whose alias happened to match a file under a view path
* was rendered from that file whether or not anybody meant it to be, and there
* was no way to say otherwise. 'db' says so, and costs nothing to check - the
* lookup is skipped entirely rather than probing every view path for every
* registered extension on every request.
*
* Empty keeps the old behaviour, which is what every existing template says.
*/
class AddTemplatesourceToSiteTemplates extends Migration {
public function up() {
if (!Schema::hasTable('site_templates') || Schema::hasColumn('site_templates', 'templatesource')) {
return;
}

Schema::table('site_templates', function (Blueprint $table) {
$table->string('templatesource', 10)->default('')->after('templatealias');
});
}

public function down() {
if (Schema::hasTable('site_templates') && Schema::hasColumn('site_templates', 'templatesource')) {
Schema::table('site_templates', function (Blueprint $table) {
$table->dropColumn('templatesource');
});
}
}
}
70 changes: 69 additions & 1 deletion core/src/Controllers/Template.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@

use EvolutionCMS\Models;
use EvolutionCMS\Interfaces\ManagerTheme;
use EvolutionCMS\Support\TemplateFileEngines;
use EvolutionCMS\TemplateProcessor;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent;

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

/**
* A template file larger than this is not handed to the editor for
* switching. Templates are not this big; something else is going on, and a
* megabyte of it does not belong inline in the form.
*/
private const MAX_EDITABLE_FILE_BYTES = 524288;

protected $events = [
'OnTempFormPrerender',
'OnTempFormRender'
Expand Down Expand Up @@ -51,6 +60,48 @@ public function canView(): bool
public function process() : bool
{
$this->object = $this->parameterData();
$engines = TemplateFileEngines::make();
$existingFiles = $engines->existing((string) $this->object->templatealias);
$winningExtension = $engines->winner(
(string) $this->object->templatealias,
(string) $this->object->templatefileextension
);

// A template that reads from a file shows the file in the editor, not
// the database column - editing what is not rendered would be a trap.
// The database copy stays where it is as the fallback it already is.
// "Automatic" only describes templates that predate the setting. A
// template being created now has no old behaviour to preserve, so it
// starts where its code is being typed.
$templateSource = (string) $this->object->templatesource;
if ($templateSource === '' && !$this->object->getKey()) {
$templateSource = TemplateProcessor::SOURCE_DATABASE;
}

// The editor has to be able to show whatever the selectors point at, the
// moment they are moved - otherwise it keeps displaying code that the
// save is not going to write, which is the one thing an editor must
// never do. The database column and every file already on disk are
// handed to the page so the swap needs no round trip.
$databaseContent = (string) $this->object->content;
$fileContents = [];
foreach ($existingFiles as $extension => $path) {
if (is_readable($path) && filesize($path) <= self::MAX_EDITABLE_FILE_BYTES) {
$fileContents[$extension] = (string) file_get_contents($path);
}
}

$sourceFile = null;
if ($templateSource === TemplateProcessor::SOURCE_FILE) {
$sourceFile = $engines->pathFor(
(string) $this->object->templatealias,
(string) $this->object->templatefileextension
);
$pinned = ltrim((string) $this->object->templatefileextension, '.');
if ($sourceFile !== null && isset($fileContents[$pinned])) {
$this->object->content = $fileContents[$pinned];
}
}
$this->parameters = [
'data' => $this->object,
'categories' => $this->parameterCategories(),
Expand All @@ -69,7 +120,24 @@ function (Models\SiteTmplvar $item) {
),
'action' => $this->getIndex(),
'events' => $this->parameterEvents(),
'actionButtons' => $this->parameterActionButtons()
'actionButtons' => $this->parameterActionButtons(),
'templateFileEngines' => $engines->all(),
// A new template follows [(chunk_processor)]; one that already
// recorded an engine keeps showing it, so that opening a template
// and saving it cannot quietly re-point it at another engine.
'templateFileDefault' => $engines->isRegistered($this->object->templatefileextension)
? ltrim((string) $this->object->templatefileextension, '.')
: $engines->defaultExtension(
(string) $this->managerTheme->getCore()->getConfig('chunk_processor')
),
// Only one file can render an alias; the rest are shadowed, so the
// form says which ones are already there and which of them wins.
'templateFileExisting' => $existingFiles,
'templateFileWinner' => $winningExtension,
'templateSource' => $templateSource,
'templateSourceFile' => $sourceFile,
'templateDbContent' => $databaseContent,
'templateFileContents' => $fileContents
];

return true;
Expand Down
28 changes: 27 additions & 1 deletion core/src/Core.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,23 @@ class Core extends AbstractLaravel implements Interfaces\CoreInterface
public $documentMethod;
public $documentGenerated;
public $documentContent;

/**
* The view the current document is rendered from, or '' when it is rendered
* from template code by the parser.
*
* A template whose alias resolves to a file under a view path is handed to
* Laravel's view factory, and parseDocumentSource() is then skipped - so by
* the time OnLoadWebDocument fires, documentContent holds finished HTML
* from another engine rather than template source. A listener that
* transforms template code has to be able to tell the two apart, and
* calling TemplateProcessor::getBladeDocumentContent() again to find out is
* not free: it can run a controller's main() a second time.
*
* @var string
* @since 3.5.9
*/
public $documentTemplateView = '';
public $documentOutput;
public $tstart = 0;
public $mstart = 0;
Expand Down Expand Up @@ -3300,6 +3317,7 @@ public function prepareResponse()
}

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

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

$tpl = $this['view']->make($template, $viewData);
// A template that pinned an engine names the exact file, so the
// view factory is handed a path rather than a name - resolving
// a name would put the question back to extension registration
// order and hand the document to whichever engine booted last.
$viewPath = TemplateProcessor::getDocumentViewPath();

$tpl = $viewPath !== ''
? $this['view']->file($viewPath, $viewData)
: $this['view']->make($template, $viewData);
$templateCode = $tpl->render();
} else {
// get the template and start parsing!
Expand Down
5 changes: 5 additions & 0 deletions core/src/Models/SiteTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
*
* @property int $id
* @property string $templatename
* @property string $templatealias
* @property string $templatesource
* @property string $templatefileextension
* @property string $description
* @property int $editor_type
* @property int $category
Expand Down Expand Up @@ -60,6 +63,8 @@ class SiteTemplate extends Eloquent\Model
protected $fillable = [
'templatename',
'templatealias',
'templatesource',
'templatefileextension',
'description',
'editor_type',
'category',
Expand Down
Loading