FEATURE #1431: add template edit
This commit is contained in:
@@ -0,0 +1,179 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests;
|
||||||
|
use App\Http\Requests\EditTemplateRequest;
|
||||||
|
use App\Models\Template;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
|
||||||
|
class EditTemplate extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Render form for editing template
|
||||||
|
*
|
||||||
|
* @param $template
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function getEdit($template)
|
||||||
|
{
|
||||||
|
$template = Template::findOrFail($template);
|
||||||
|
$path = $template->getPath();
|
||||||
|
|
||||||
|
// load template content
|
||||||
|
$absolutePath = $template->getPathToTemplate();
|
||||||
|
$content = file_get_contents($absolutePath);
|
||||||
|
|
||||||
|
return view('admin.templates.edit', [
|
||||||
|
'template' => $template,
|
||||||
|
'templateContent' => $content,
|
||||||
|
'isWritable' => is_writable(dirname($absolutePath)),
|
||||||
|
'path' => $path,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save meta information about template and edited content
|
||||||
|
*
|
||||||
|
* @param EditTemplateRequest $request
|
||||||
|
* @param $templateId
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function postEdit(EditTemplateRequest $request, $templateId)
|
||||||
|
{
|
||||||
|
// find template
|
||||||
|
$template = Template::findOrFail($templateId);
|
||||||
|
|
||||||
|
// setup meta information
|
||||||
|
$template->name = $request->name;
|
||||||
|
|
||||||
|
if ($request->description) {
|
||||||
|
$template->description = $request->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
// save meta information into database
|
||||||
|
$template->save();
|
||||||
|
|
||||||
|
// try to save content
|
||||||
|
return $this->saveContent($template, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save physical content of given template
|
||||||
|
*
|
||||||
|
* @param Template $template
|
||||||
|
* @param EditTemplateRequest $request
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
private function saveContent(Template $template, EditTemplateRequest $request)
|
||||||
|
{
|
||||||
|
// get templates with paths
|
||||||
|
$originalTemplate = $template->getPathToTemplate(Template::ORIGINAL_DIRECTORY);
|
||||||
|
$customTemplate = $template->getPathToTemplate(Template::CUSTOM_EDIT_DIRECTORY);
|
||||||
|
|
||||||
|
// get paths
|
||||||
|
$originalDir = dirname($originalTemplate);
|
||||||
|
$customDir = dirname($customTemplate);
|
||||||
|
|
||||||
|
// try to save content
|
||||||
|
if (!is_writable($customDir) && !is_writable($originalDir)) {
|
||||||
|
return redirect()
|
||||||
|
->route('templates.edit', ['id' => $request->id])
|
||||||
|
->withInput()
|
||||||
|
->with('error', trans('admin/templates.cannotWriteContent'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// save new content only if something was changed
|
||||||
|
if ($this->isContentDifferent($originalTemplate, $customTemplate, $request->templateContent)) {
|
||||||
|
|
||||||
|
// create directory for custom updates
|
||||||
|
if (!is_dir($customDir)) {
|
||||||
|
File::makeDirectory($customDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make copy of existing template (if exists)
|
||||||
|
$this->backupTemplates($customTemplate);
|
||||||
|
|
||||||
|
// save content
|
||||||
|
File::put($customTemplate, $request->templateContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->route('templates.overview', ['id' => $template->path])
|
||||||
|
->with('success', trans('admin/templates.editSuccessfull'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get last "revision" of edited template.
|
||||||
|
*
|
||||||
|
* Template changes are not discarded, however new file is created. Last template is renamed to
|
||||||
|
* <template>.blade.php.<num>, where <num> is result of this method.
|
||||||
|
*
|
||||||
|
* @param $template
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
private function getNewRevisionNumber($template)
|
||||||
|
{
|
||||||
|
$number = 0;
|
||||||
|
while (File::exists($template . '.' . $number)) {
|
||||||
|
$number++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If exists previously edited template, rename it to <template>.blade.php.<number>
|
||||||
|
*
|
||||||
|
* @param $template
|
||||||
|
*/
|
||||||
|
private function backupTemplates($template)
|
||||||
|
{
|
||||||
|
// get last sequence number
|
||||||
|
$number = $this->getNewRevisionNumber($template);
|
||||||
|
|
||||||
|
// nothing to backup
|
||||||
|
if ($number == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// rename template
|
||||||
|
File::move($template, $template . '.' . $number);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare data from previous/original template
|
||||||
|
*
|
||||||
|
* @param $originalTemplate
|
||||||
|
* @param $customTemplate
|
||||||
|
* @param $currentContent
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function isContentDifferent($originalTemplate, $customTemplate, $currentContent)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* get last revision of template - we need to substract 1 from result from getNewRevisionNumber(),
|
||||||
|
* because this method returns number of first non-existing template
|
||||||
|
*/
|
||||||
|
$lastRevision = $this->getNewRevisionNumber($customTemplate) - 1;
|
||||||
|
|
||||||
|
// select compared file
|
||||||
|
if ($lastRevision < 0) {
|
||||||
|
$diffFile = $originalTemplate;
|
||||||
|
} else {
|
||||||
|
$diffFile = $customTemplate . '.' . $lastRevision;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get content
|
||||||
|
$originalContent = file_get_contents($diffFile);
|
||||||
|
|
||||||
|
return $originalContent != $currentContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
class EditTemplateRequest extends Request
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => 'required|max:128',
|
||||||
|
'templateContent' => 'required'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -61,10 +61,10 @@ Route::group([
|
|||||||
Route::get('templates/new/{pathId}', 'TemplateOverview@getPath')
|
Route::get('templates/new/{pathId}', 'TemplateOverview@getPath')
|
||||||
->name('templates.new');
|
->name('templates.new');
|
||||||
|
|
||||||
Route::get('templates/edit/{id}', 'TemplateOverview@getPath')
|
Route::get('templates/edit/{id}', 'EditTemplate@getEdit')
|
||||||
->name('templates.edit');
|
->name('templates.edit');
|
||||||
|
|
||||||
Route::post('templates/edit/{id}', 'TemplateOverview@getPath')
|
Route::post('templates/edit/{id}', 'EditTemplate@postEdit')
|
||||||
->name('templates.postEdit');
|
->name('templates.postEdit');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+64
-4
@@ -18,18 +18,78 @@ class Template extends Model
|
|||||||
'editable'
|
'editable'
|
||||||
];
|
];
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
protected $pathModel = null;
|
||||||
const EDITED_TEMPLATES_DIRECTORY = 'custom';
|
const EDITED_TEMPLATES_DIRECTORY = 'custom';
|
||||||
const TEMPLATE_SUFFIX = '.blade.php';
|
const TEMPLATE_SUFFIX = '.blade.php';
|
||||||
|
const CUSTOM_EDIT_DIRECTORY = 1;
|
||||||
|
const ORIGINAL_DIRECTORY = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In one path may be many templates
|
* In one path may be many templates
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
*/
|
*/
|
||||||
public function template()
|
public function getPath()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('App\Models\TemplatePath', 'id');
|
if (!$this->pathModel) {
|
||||||
|
|
||||||
|
$this->pathModel = TemplatePath::where('id', $this->path)
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->pathModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for absolute path to template
|
||||||
|
*
|
||||||
|
* @param null $mode
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPathToTemplate($mode = null)
|
||||||
|
{
|
||||||
|
$templateName = $this->template_name;
|
||||||
|
$path = $this->getPath();
|
||||||
|
|
||||||
|
// delete namespace if given
|
||||||
|
if (strpos($this->template_name, '::')) {
|
||||||
|
list(, $templateName) = explode('::', $templateName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get template name, do not care about directories (they are already contained in path)
|
||||||
|
$parts = explode('.', $templateName);
|
||||||
|
$templateName = last($parts);
|
||||||
|
|
||||||
|
// just a shortcut for this unbelievable long constant name...
|
||||||
|
$_ = DIRECTORY_SEPARATOR;
|
||||||
|
|
||||||
|
// path parts
|
||||||
|
$absolutePath = base_path($path->path);
|
||||||
|
$templatePath = str_replace('.', $_, $templateName);
|
||||||
|
$file = $templatePath . self::TEMPLATE_SUFFIX;
|
||||||
|
|
||||||
|
// compose two versions of path - original and with "custom" directory
|
||||||
|
$original = $absolutePath . $_ . $file;
|
||||||
|
$edited = $absolutePath . $_ . self::EDITED_TEMPLATES_DIRECTORY . $_ . $file;
|
||||||
|
|
||||||
|
// decide which path will be returned
|
||||||
|
switch ($mode) {
|
||||||
|
case self::CUSTOM_EDIT_DIRECTORY:
|
||||||
|
return $edited;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::ORIGINAL_DIRECTORY:
|
||||||
|
return $original;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (file_exists($absolutePath . $_ . self::EDITED_TEMPLATES_DIRECTORY . $_ . $file)) {
|
||||||
|
return $edited;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $original;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,5 +7,11 @@ return [
|
|||||||
'parentDirectory' => 'Parent directory',
|
'parentDirectory' => 'Parent directory',
|
||||||
'noTemplates' => 'No templates available',
|
'noTemplates' => 'No templates available',
|
||||||
'new' => 'New template',
|
'new' => 'New template',
|
||||||
'subdirectories' => 'Subdirectories'
|
'subdirectories' => 'Subdirectories',
|
||||||
|
'edit' => 'Edit template :name',
|
||||||
|
'name' => 'Template name',
|
||||||
|
'description' => 'Template description',
|
||||||
|
'editSuccessfull' => 'Template successfully edited.',
|
||||||
|
'notWritable' => 'Cannot write to :file. Any submited changes in this file will <strong>not</strong> be saved!',
|
||||||
|
'cannotWriteContent' => 'Cannot save template content, directory has unsufficient permissions. Meta information were saved.'
|
||||||
];
|
];
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
@extends('admin.layout')
|
||||||
|
|
||||||
|
@section('pageName', @trans('admin/templates.edit', ['name' => $template->template_name]))
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<h2>@lang('admin/templates.edit', ['name' => $template->template_name])</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
@include('admin.templates.editForm')
|
||||||
|
</div>
|
||||||
|
@stop
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
@if (session('success'))
|
||||||
|
<div data-alert class="callout success">
|
||||||
|
<strong>{{ session('success') }}</strong>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if (session('error'))
|
||||||
|
<div data-alert class="callout alert">
|
||||||
|
<strong>{{ session('error') }}</strong>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if (!$isWritable)
|
||||||
|
<div data-alert class="callout warning">
|
||||||
|
@lang('admin/templates.notWritable', ['file' => $path->path])
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form action="{{route('templates.postEdit', ['id' => $template->id])}}" method="POST">
|
||||||
|
{!! csrf_field() !!}
|
||||||
|
<div class="row">
|
||||||
|
<div class="column large-8">
|
||||||
|
<div style="height: 30em">
|
||||||
|
@include('admin.snippets.aceEditor', [
|
||||||
|
'value' => old('templateContent', $templateContent),
|
||||||
|
'theme' => 'xcode',
|
||||||
|
'mode' => 'html',
|
||||||
|
'name' => 'templateContent'
|
||||||
|
])
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column large-4">
|
||||||
|
<label>
|
||||||
|
@lang('admin/templates.name')
|
||||||
|
|
||||||
|
<input type="text" name="name" value="{{old('name', $template->name)}}" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
@lang('admin/templates.description')
|
||||||
|
|
||||||
|
<textarea name="description" rows="3">{{old('description', $template->description)}}</textarea>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button type="submit" class="button success">
|
||||||
|
@lang('admin/templates.edit', ['name' => $template->template_name])
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
@@ -5,6 +5,18 @@
|
|||||||
@section('content')
|
@section('content')
|
||||||
<h1>@lang('admin/templates.overview')</h1>
|
<h1>@lang('admin/templates.overview')</h1>
|
||||||
|
|
||||||
|
@if (session('success'))
|
||||||
|
<div data-alert class="callout success">
|
||||||
|
<strong>{{ session('success') }}</strong>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if (session('error'))
|
||||||
|
<div data-alert class="callout alert">
|
||||||
|
<strong>{{ session('error') }}</strong>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="column large-9">
|
<div class="column large-9">
|
||||||
@include('admin.templates.templatesTiles')
|
@include('admin.templates.templatesTiles')
|
||||||
|
|||||||
Regular → Executable
@@ -0,0 +1,55 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
@yield('head_meta')
|
||||||
|
<title>@yield('pageName', @trans('visitor/common.welcome')) | {{$vendorName}}</title>
|
||||||
|
<link rel="stylesheet" href="{{asset('../resources/assets/css/foundation.min.css')}}" />
|
||||||
|
<link rel="stylesheet" href="{{asset('../resources/assets/css/font-awesome.min.css')}}" />
|
||||||
|
<link rel="stylesheet" href="{{asset('../resources/assets/css/admin.css')}}" />
|
||||||
|
@yield('head_stylesheets')
|
||||||
|
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Lato:100" rel="stylesheet" type="text/css">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
color: #B0BEC5;
|
||||||
|
display: table;
|
||||||
|
font-weight: 100;
|
||||||
|
font-family: 'Lato';
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
text-align: center;
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 72px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="content">
|
||||||
|
@yield('content')
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Regular → Executable
Reference in New Issue
Block a user