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')
|
||||
->name('templates.new');
|
||||
|
||||
Route::get('templates/edit/{id}', 'TemplateOverview@getPath')
|
||||
Route::get('templates/edit/{id}', 'EditTemplate@getEdit')
|
||||
->name('templates.edit');
|
||||
|
||||
Route::post('templates/edit/{id}', 'TemplateOverview@getPath')
|
||||
Route::post('templates/edit/{id}', 'EditTemplate@postEdit')
|
||||
->name('templates.postEdit');
|
||||
});
|
||||
|
||||
|
||||
+64
-4
@@ -18,18 +18,78 @@ class Template extends Model
|
||||
'editable'
|
||||
];
|
||||
protected $guarded = [];
|
||||
|
||||
protected $pathModel = null;
|
||||
const EDITED_TEMPLATES_DIRECTORY = 'custom';
|
||||
const TEMPLATE_SUFFIX = '.blade.php';
|
||||
|
||||
const CUSTOM_EDIT_DIRECTORY = 1;
|
||||
const ORIGINAL_DIRECTORY = 2;
|
||||
|
||||
/**
|
||||
* In one path may be many templates
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
<?php
|
||||
return [
|
||||
'core' => 'Core templates',
|
||||
'homepage' => 'Home page',
|
||||
'overview' => 'Templates',
|
||||
'noSubdirectories' => 'No directories available',
|
||||
'parentDirectory' => 'Parent directory',
|
||||
'noTemplates' => 'No templates available',
|
||||
'new' => 'New template',
|
||||
'subdirectories' => 'Subdirectories'
|
||||
'core' => 'Core templates',
|
||||
'homepage' => 'Home page',
|
||||
'overview' => 'Templates',
|
||||
'noSubdirectories' => 'No directories available',
|
||||
'parentDirectory' => 'Parent directory',
|
||||
'noTemplates' => 'No templates available',
|
||||
'new' => 'New template',
|
||||
'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')
|
||||
<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="column large-9">
|
||||
@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