Merge branch 'template_support' into dev

This commit is contained in:
Michal Kváček
2016-09-20 20:17:06 +02:00
148 changed files with 890 additions and 5299 deletions
+74
View File
@@ -0,0 +1,74 @@
<?php
namespace App\Console\Commands;
use App\Models\Module;
use Illuminate\Console\Command;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class SetTemplate extends Command
{
/**
* The name and signature of the console command.
* @var string
*/
protected $signature = 'template:set {template}';
/**
* The console command description.
* @var string
*/
protected $description = 'Install template';
/**
* Execute the console command.
* @return mixed
*/
public function handle()
{
$template = $this->argument('template');
$templatePath = app_path('../templates/' . $template);
if (!is_dir($templatePath) || !file_exists($templatePath . '/config.php')) {
throw new NotFoundHttpException('Invalid template ' . $template . '!');
}
// try to find currently installed template
$current = Module::where('is_template', true)
->first();
// install template
$config = @include $templatePath . '/config.php';
$config['staticName'] = $template;
if ($current) {
$info = json_decode($current->module_settings);
$this->line('Template is already installed. Currently using template ' . trans($current->name) . ' located in /templates/' . $info->staticName.'.');
$confirm = $this->ask('Uninstall it and install *' . trans($config['name']) . '*? All data associated with installed template will be lost! [y/n]');
if ($confirm === 'y') {
$this->line('Uninstalling ' . $current->name . '.');
$current->uninstall();
} else {
$this->line('Aborting installation.');
return;
}
}
try {
Module::install($config['name'], $config['alias'], $config['description'], $config['handler'], false, $config);
} catch (\Exception $e) {
$this->error('Cannot install template ' . $template . '. Message: ' . $e->getMessage());
return;
}
// theme written
$this->line('Template successfully setup.');
$this->line('');
$this->line(' ** PUBLISHING ASSETS AND VIEWS **');
// publish assets and views for module and theme
$this->call('vendor:publish', ['--tag' => ['public-assets'], '--force' => true]);
$this->call('vendor:publish', ['--tag' => ['module-views'], '--force' => true]);
$this->call('vendor:publish');
}
}
-70
View File
@@ -1,70 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class SetTheme extends Command
{
/**
* The name and signature of the console command.
* @var string
*/
protected $signature = 'theme:set {theme_name}';
/**
* The console command description.
* @var string
*/
protected $description = 'Set theme for visitor area';
/**
* Create a new command instance.
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
* @return mixed
*/
public function handle()
{
$theme = $this->argument('theme_name');
if (!is_dir(resource_path('views/visitor/' . $theme))) {
throw new NotFoundHttpException('Invalid theme ' . $theme . '!');
}
// load .env file
$envFile = base_path('.env');
$env = file_get_contents($envFile);
if (!Str::contains($env, 'THEME=')) {
$env = sprintf("%s\nTHEME=%s\n", $env, $theme);
} else {
$env = preg_replace('/THEME=(.*)/i', 'THEME=' . $theme, $env, -1, $cnt);
}
// try to write content
$bytesWritten = file_put_contents($envFile, $env);
// check if write was successfull
if (!$bytesWritten) {
throw new \Exception('Cannot write to ' . base_path('.env') . '. Add THEME=' . $theme . ' to your .env file manualy.');
}
// theme written
$this->line('Theme successfully setup.');
$this->line('');
$this->line(' ** PUBLISHING ASSETS AND VIEWS **');
// publish assets and views for module and theme
$this->call('vendor:publish', ['--tag' => ['public-assets'], '--force' => true]);
$this->call('vendor:publish', ['--tag' => ['module-views'], '--force' => true]);
$this->call('vendor:publish');
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ class Kernel extends ConsoleKernel
Commands\Translations::class,
Commands\TranslateAll::class,
Commands\InitDatabaseSchema::class,
Commands\SetTheme::class,
Commands\SetTemplate::class,
AddLanguage::class
];
+1 -1
View File
@@ -5,7 +5,7 @@ use App\Models\User;
interface BaseModuleHandler
{
public static function getModuleId();
// public static function getModuleId();
/**
* Register admin menu (or multiple ones)
@@ -0,0 +1,64 @@
<?php
namespace App\Handlers\Contracts;
use App\Http\Requests\Request;
use App\Models\Content;
use App\Models\Module;
/**
* Interface TemplateHandler
*
* Inerface used for registering template into CMS core.
*
* @package App\Handlers\Contracts
*/
interface TemplateHandler extends BaseModuleHandler
{
/**
* Uninstall template
*
* Delete some items in database, delete files etc.
*
* @param Module $template
*/
public static function uninstall(Module $template);
/**
* Get list of registered templates and supported modules.
*
* Returned array is expected to be array of arrays with following items: module, namespace and templateName.
* Example:
* return [
* ['module' => 'Article', 'templateName' => 'article::template.name']
* ];
*
* When no "namespace" is specified, value strtolower(module) is used instaed.
*
* @return array
*/
public static function getPublicTemplatePaths();
/**
* Prepare Content object to be rendered using Blade template
*
* @param Content $content
*/
public static function beforeRender(Content & $content);
/**
* Modify input data into format which can be understood by edit template
*
* @param Content $content
*/
public static function beforePopulatingEditForm(Content & $content);
/**
* Slightly modify Content object before saving into database.
*
* This way we can do almost whatever we want - even in template
*
* @param Content $content
* @param Request $request
*/
public static function beforeSave(Content & $content, Request $request);
}
@@ -187,7 +187,6 @@ class TemplateRegistrator
$registeredTemplates = Templates::getWithTranslations();
if (!in_array($templateName, $registeredTemplates)) {
$params = [
'template_name' => $templateName,
'name' => $templateName,
+22 -5
View File
@@ -13,7 +13,7 @@ class Template
*
* @return null|string
*/
public static function name($template)
public static function name($template, $throwExceptions = true)
{
$namespace = null;
if (strpos($template, '::')) {
@@ -21,10 +21,8 @@ class Template
$namespace .= '::';
}
$theme = config('app.theme');
// list of prefixes
$prefixes = [$theme . '.custom.', $theme . '.', ''];
$prefixes = ['custom.', ''];
$view = null;
foreach ($prefixes as $prefix) {
@@ -35,7 +33,11 @@ class Template
}
if (is_null($view)) {
throw new InvalidArgumentException('Cannot find template for ' . $namespace . $template);
if ($throwExceptions) {
throw new InvalidArgumentException('Cannot find template for ' . $namespace . $template);
} else {
return false;
}
}
return $view;
@@ -59,6 +61,13 @@ class Template
return config('app.upload_url') . '/' . $filename;
}
/**
* Get path to given asset
*
* @param $path
*
* @return string
*/
public static function asset($path)
{
if (env('APP_DEBUG')) {
@@ -75,6 +84,14 @@ class Template
return config('app.url') . '/assets/' . config('app.theme') . '/' . $path;
}
/**
* Print date in configured format
*
* @param $datestamp
* @param bool $withTime
*
* @return false|string
*/
public static function date($datestamp, $withTime = true)
{
$timestamp = strtotime($datestamp);
@@ -14,6 +14,7 @@ use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
use Modules\Categories\Entities\Category;
use Pingpong\Modules\Facades\Module as ModuleInstance;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class CreateNewContent extends Controller
{
/**
@@ -87,6 +88,9 @@ class CreateNewContent extends Controller
public function postNew(CreateNewContentRequest $request, $moduleId)
{
$page = new Content();
$template = Module::where('is_template', true)
->where('enabled', true)
->first();
try {
$module = Module::findOrFail($moduleId);
} catch (NotFoundHttpException $e) {
@@ -120,6 +124,10 @@ class CreateNewContent extends Controller
$page->title_photo = $file ? $file['filename'] : null;
// let template to modify request and content page
call_user_func_array([$template->handler, 'beforeSave'], [&$page, $request]);
// save module-specific stuff
call_user_func_array([$module->handler, 'createNew'], [&$page, $request]);
+21 -5
View File
@@ -56,23 +56,32 @@ class EditContent extends Controller
$page = Content::findOrFail($id);
$page->module_settings = json_decode($page->module_settings);
$allModules = ModulesHandlerToId::getWithTranslations();
$moduleId = $allModules[$page->module_handler];
$module = Module::findOrFail($moduleId);
// select template
$template = Module::where('is_template', true)
->where('enabled', true)
->first();
// get module ID
$module = Module::where('handler', $page->module_handler)->firstOrFail();
$templates = Template::where('module_id', $moduleId)
// get all Blade templates for rendering page
$templates = Template::where('module_id', $module->id)
->select(['template_name', 'name', 'description'])
->get();
// get module for Categories - PingPong module
$categoryModule = ModuleInstance::get('categories');
// add categories, if supported
$categories = [];
if ($module->support_categories && $categoryModule->enabled()) {
$categories = Category::where('module_id', $moduleId)
$categories = Category::where('module_id', $module->id)
->get();
}
// modify data for edit form according to installed template
call_user_func_array([$template->handler, 'beforePopulatingEditForm'], [&$page]);
$history = $this->history->getStructuredHistoryOf($page);
// render view
@@ -107,6 +116,10 @@ class EditContent extends Controller
* @var $page Content
*/
$page = Content::findOrFail($id);
$template = Module::where('is_template', true)
->where('enabled', true)
->first();
} catch (NotFoundHttpException $e) {
// log attempt of invalid edit
@@ -121,6 +134,9 @@ class EditContent extends Controller
// upload file - if provided
$file = $page->uploadTitlePhoto($request);
// let template to modify request and content page
call_user_func_array([$template->handler, 'beforeSave'], [&$page, $request]);
$request->url = Str::slug($request->url);
$request->parentPage = $request->parentPage ? $request->parentPage : null;
@@ -79,6 +79,13 @@ class EditWidget extends Controller
// create new widget
$widget = Widget::findOrFail($id);
// check if widget area is deletable
if (!$widget->deletable) {
return redirect()
->back()
->with('error', trans('admin/widgets.notDeletable'));
}
$widget->delete();
// redirect with success notification
@@ -3,7 +3,6 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests;
use App\Http\Requests\EditWidgetAreaRequest;
use App\Models\WidgetArea;
use Illuminate\Database\Eloquent\ModelNotFoundException;
@@ -61,6 +60,13 @@ class EditWidgetArea extends Controller
return $this->invalidArea();
}
// check if widget area is deletable
if (!$area->deletable) {
return redirect()
->back()
->with('error', trans('admin/widgetAreas.notDeletableArea'));
}
// delete area
$area->delete();
@@ -5,6 +5,8 @@ namespace App\Http\Controllers\Visitor;
use App\Http\Controllers\Controller;
use App\Http\Requests;
use App\Models\Content;
use App\Models\Module;
use App\Models\Template;
use Illuminate\Support\Facades\Auth;
class ContentPage extends Controller
@@ -59,6 +61,11 @@ class ContentPage extends Controller
// get handler
$handler = new $page->module_handler;
$template = Module::where('is_template', true)->where('enabled', true)->first();
// do something template-specific
call_user_func_array([$template->handler, 'beforeRender'], [&$page]);
// render page
return $handler->render($page, '');
}
+51 -3
View File
@@ -2,6 +2,7 @@
namespace App\Models;
use App\Handlers\Contracts\TemplateHandler;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\App;
@@ -20,7 +21,8 @@ class Module extends Model
'module_settings',
'enabled',
'support_categories',
'manages_content'
'manages_content',
'is_template'
];
protected $guarded = [];
@@ -45,6 +47,7 @@ class Module extends Model
}
$managesContent = $handler ? in_array('App\Handlers\Contracts\AppHandler', class_implements($handler)) : false;
$isTemplate = $handler ? in_array(TemplateHandler::class, class_implements($handler)) : false;
$supportCategories = $managesContent ? call_user_func([$handler, 'supportCategories']) : false;
if ($parentModuleId) {
@@ -62,8 +65,9 @@ class Module extends Model
'support_categories' => $supportCategories,
'alias' => $alias,
'module_settings' => json_encode($settings),
'parent_id' => $parentModuleId,
'enabled' => 1
'parent_id' => $parentModuleId ? $parentModuleId : null,
'enabled' => 1,
'is_template' => $isTemplate
]);
// install something module-specific
@@ -73,4 +77,48 @@ class Module extends Model
return $module;
}
/**
* Delete module from database and uninstall its dependencies
*
* @throws \ErrorException
*/
public function uninstall()
{
// check if user does not want to install module in runtime
if (!App::runningInConsole()) {
throw new \ErrorException('Modules can be uninstalled only via Artisan!');
}
// delete everything in /resources/views/visitor
if ($this->is_template) {
$this->deleteDirectory(resource_path('views/visitor'));
$this->deleteDirectory(public_path('assets'));
// delete all template paths
TemplatePath::getQuery()->delete();
}
if ($this->handler && is_callable([$this->handler, 'uninstall'])) {
call_user_func([$this->handler, 'uninstall'], $this);
}
$this->delete();
}
/**
* Delete directory when uninstalling module.
*
* @param $directory
*
* @throws \ErrorException
*/
private function deleteDirectory($directory)
{
$success = \File::deleteDirectory($directory, true);
if (!$success) {
throw new \ErrorException('Cannot delete ' . $directory);
}
}
}
+3 -44
View File
@@ -30,8 +30,6 @@ class AppServiceProvider extends ServiceProvider
{
// ignore running from artisan
if (App::runningInConsole()) {
$this->publishViewResources();
return;
}
@@ -39,13 +37,6 @@ class AppServiceProvider extends ServiceProvider
$this->registerTemplates();
$this->registerSettings();
$this->registerSearch();
// check if given template is valid
$theme = config('app.theme');
$themeDir = resource_path('views/visitor/' . $theme);
if (!$theme || empty($theme) || !is_dir($themeDir)) {
throw new InvalidArgumentException('No or invalid theme set! Use artisan theme:set or edit .env file manually and include THEME option, please');
}
}
/**
@@ -120,10 +111,10 @@ class AppServiceProvider extends ServiceProvider
/**
* @var $registrator TemplateRegistrator
*/
$registrator = $this->app->make('registerTemplateDir');
$registrator->setBaseDirectory('resources/views/visitor');
// $registrator = $this->app->make('registerTemplateDir');
// $registrator->setBaseDirectory('resources/views/visitor');
$registrator->register(config('app.theme'), 'admin/templates.core');
// $registrator->register(config('app.theme'), 'admin/templates.core');
}
/**
@@ -150,37 +141,5 @@ class AppServiceProvider extends ServiceProvider
$registrator->register(new Files(), new AdminFiles(), SearchEngineRegistrator::ADMIN);
}
/**
* Publish all public assets from all themes
*/
private function publishViewResources()
{
$theme = config('app.theme');
$themeDir = resource_path('views/visitor/' . $theme);
// publish assets if exists for current theme
if (is_dir($themeDir . '/' . Template::ASSETS_DIRECTORY)) {
$this->publishes([$themeDir . '/' . Template::ASSETS_DIRECTORY => public_path('assets/' . $theme)], 'public-assets');
}
if (is_dir($themeDir . '/' . Template::MODULES_DIRECTORY)) {
$this->publishModuleViews($themeDir . '/' . Template::MODULES_DIRECTORY);
}
}
private function publishModuleViews($themeModules)
{
$theme = config('app.theme');
$modules = File::directories($themeModules);
$moduleViews = [];
foreach ($modules as $module) {
$moduleName = strtolower(basename($module));
$moduleViews[$module] = resource_path('views/modules/' . $moduleName . '/visitor/' . $theme);
}
if (!empty($moduleViews)) {
$this->publishes($moduleViews, 'module-views');
}
}
}
@@ -5,18 +5,13 @@ use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
use Symfony\Component\Translation\Translator;
class LaravelLocalizationServiceProvider extends ServiceProvider
{
public function register()
{
try {
$languages = DB::table('languages')
->get();
} catch (\Exception $e) {
$languages = [];
}
$languages = DB::table('languages')
->get();
$supportedLocales = [];
if (empty($languages)) {
@@ -28,11 +23,7 @@ class LaravelLocalizationServiceProvider extends ServiceProvider
];
// create default language
try {
Language::create($supportedLocales[config('app.locale')]);
} catch (\Exception $e) {
// ignore, table languages probably does not exist.
}
Language::create($supportedLocales[config('app.locale')]);
}
foreach ($languages as $language) {
+3 -2
View File
@@ -21,7 +21,8 @@ class ModuleManager extends ServiceProvider
}
// this service provider needs published config - config is merged right after this service provider
$modules = Module::get();
$modules = Module::where('is_template', false)
->get();
// iterate over all modules and enable or disable them
foreach ($modules as $module) {
@@ -58,7 +59,7 @@ class ModuleManager extends ServiceProvider
// check if module config is writable
if (!is_writable(dirname($path))) {
Log::warning('Cannot write to '.$path);
Log::warning('Cannot write to ' . $path);
return;
}
+118
View File
@@ -0,0 +1,118 @@
<?php
namespace App\Providers;
use App\Handlers\Registrators\TemplateRegistrator;
use App\Models\Module;
use App\Models\Template;
use Illuminate\Support\Facades\App;
use Illuminate\Support\ServiceProvider;
use InvalidArgumentException;
class TemplateServiceProvider extends ServiceProvider
{
private function getTemplate()
{
// select template
$template = Module::where('is_template', true)
->where('enabled', true)
->first();
if ($template) {
$template->settings = json_decode($template->module_settings);
}
return $template;
}
public function boot()
{
// ignore running from artisan
if (App::runningInConsole()) {
$this->publishViewResources();
return;
}
$template = $this->getTemplate();
if (!$template) {
throw new \ErrorException('Template is not set, cannot continue');
}
// include files
$files = isset($template->settings->files) ? $template->settings->files : [];
foreach ($files as $file) {
include_once $file;
}
// register templates
$publicTemplates = call_user_func([$template->handler, 'getPublicTemplatePaths']);
if (!is_array($publicTemplates)) {
throw new \ErrorException($template->handler . '::getPublicTemplatePaths() must return array!');
}
if (empty($publicTemplates)) {
return;
}
/**
* @var $registrator TemplateRegistrator
*/
$registrator = $this->app->make('registerTemplateDir');
foreach ($publicTemplates as $template) {
// setup some global variables
$module = strtolower($template['module']);
$namespace = isset($template['namespace']) ? $template['namespace'] : strtolower($template['module']);
try {
$registrator->setBaseDirectory('resources/views/modules/' . $module);
$registrator->setNamespace($namespace);
$registrator->setModuleId(call_user_func([config($module . '.handler'), 'getModuleId']));
// register path with current template name
$registrator->register('visitor', $template['templateName']);
} catch (InvalidArgumentException $e) {
// ignore this exception
}
}
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
}
/**
* Publish all public assets from all themes
*/
private function publishViewResources()
{
$template = $this->getTemplate();
if (!$template) {
return;
}
$templateDir = app_path('../templates/' . $template->settings->staticName . '/Resources');
$assetsDir = $templateDir . '/' . Template::ASSETS_DIRECTORY;
$viewsDir = $templateDir . '/views';
// publish assets if exists for current theme
if (is_dir($assetsDir)) {
$this->publishes([$assetsDir => public_path('assets/visitor')], 'public-assets');
}
// publish base visitors views
$this->publishes([$viewsDir . '/visitor' => resource_path('views/visitor')], 'module-views');
// publish admin views
if (is_dir($viewsDir . '/modules')) {
$this->publishes([$viewsDir . '/modules' => resource_path('views/modules')], 'module-views');
}
}
}