diff --git a/.env.example b/.env.example index 69a303d4..dba86cf7 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,8 @@ -APP_ENV=testing -APP_DEBUG=true -APP_KEY=ZKZAzM23wQ6BPji6zffBjaTzZ3yW7zKQ +APP_KEY= +APP_ENV= +APP_DEBUG= -DB_HOST=localhost +DB_HOST= DB_DATABASE= DB_USERNAME= DB_PASSWORD= @@ -16,5 +16,4 @@ MAIL_PASSWORD= SITE_URL=/ UPLOAD_URL=/public/uploads -THUMBNAILS_URL=/uploads/thumbnails -THEME= \ No newline at end of file +THUMBNAILS_URL=/uploads/thumbnails \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9c93cf6e..1bfe88c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ # published files from modules -/resources/views/modules /public/modules -/resources/views/vendor /public/assets +/resources/views/modules +/resources/views/visitor +/resources/views/vendor # directories with custom templates **/views/**/custom/* diff --git a/app/Console/Commands/InstallModule.php b/app/Console/Commands/InstallModule.php index 2a9a0c7c..ad5fffc5 100644 --- a/app/Console/Commands/InstallModule.php +++ b/app/Console/Commands/InstallModule.php @@ -40,8 +40,6 @@ class InstallModule extends Command /** * Execute the console command. - * - * @return mixed */ public function handle() { @@ -56,6 +54,9 @@ class InstallModule extends Command // get config $config = $this->loadConfig(); + if (empty($config)) + return; + // check if it is valid $this->checkMandatoryFields($config); @@ -78,6 +79,9 @@ class InstallModule extends Command $basePath = $this->modulesPath . DIRECTORY_SEPARATOR . $this->moduleName . DIRECTORY_SEPARATOR; $path = $basePath . 'Config' . DIRECTORY_SEPARATOR . 'config.php'; + if (basename($basePath) == 'Database') + return []; + // check if module has Config/config.php file if (!file_exists($path)) { $path = $basePath . 'info.php'; diff --git a/app/Console/Commands/SetTemplate.php b/app/Console/Commands/SetTemplate.php new file mode 100644 index 00000000..22901daa --- /dev/null +++ b/app/Console/Commands/SetTemplate.php @@ -0,0 +1,78 @@ +argument('template'); + $publish = $this->argument('publish') !== 'no-publish'; + + $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 set up.'); + + if ($publish) { + $this->line(''); + $this->line(' ** PUBLISHING ASSETS AND VIEWS **'); + // publish assets and views for module and theme + $this->call('vendor:publish'); + $this->call('vendor:publish', ['--tag' => ['public-assets'], '--force' => true]); + $this->call('vendor:publish', ['--tag' => ['module-views'], '--force' => true]); + } + } +} diff --git a/app/Console/Commands/SetTheme.php b/app/Console/Commands/SetTheme.php deleted file mode 100644 index dd788a79..00000000 --- a/app/Console/Commands/SetTheme.php +++ /dev/null @@ -1,70 +0,0 @@ -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'); - } -} diff --git a/app/Console/Commands/TranslateAll.php b/app/Console/Commands/TranslateAll.php index 26635d17..508802fc 100644 --- a/app/Console/Commands/TranslateAll.php +++ b/app/Console/Commands/TranslateAll.php @@ -29,15 +29,32 @@ class TranslateAll extends Command /** * Execute the console command. - * @return mixed */ public function handle() { $this->call('translate:dir', ['dir' => 'resources/lang']); - - $modules = File::directories('modules'); - foreach ($modules as $module) { - $this->call('translate:dir', ['dir' => $module.'/Resources/lang']); + + $this->crawlDirectory('modules'); + $this->crawlDirectory('templates', ['templates/Base']); + $this->crawlDirectory('templates/Base'); + } + + /** + * Crawl directories and search for directories with translations + * + * @param $directory + * @param array $ignoredDirectories + */ + protected function crawlDirectory($directory, $ignoredDirectories = []) + { + $directories = File::directories($directory); + foreach ($directories as $dir) { + + if (in_array($dir, $ignoredDirectories)) { + continue; + } + + $this->call('translate:dir', ['dir' => $dir . '/Resources/lang']); } } } \ No newline at end of file diff --git a/app/Console/Commands/Translations.php b/app/Console/Commands/TranslateDirectory.php similarity index 89% rename from app/Console/Commands/Translations.php rename to app/Console/Commands/TranslateDirectory.php index aca0693c..22e1bcf5 100644 --- a/app/Console/Commands/Translations.php +++ b/app/Console/Commands/TranslateDirectory.php @@ -6,7 +6,7 @@ use Illuminate\Console\Command; use Illuminate\Support\Facades\File; use Illuminate\Support\Str; -class Translations extends Command +class TranslateDirectory extends Command { /** * The name and signature of the console command. @@ -25,7 +25,6 @@ class Translations extends Command /** * Execute the console command. - * @return mixed */ public function handle() { @@ -33,11 +32,21 @@ class Translations extends Command $defaultLocales = config('app.fallback_locale'); $currentLocales = config('app.locale'); - $defaultLocalesDir = $dir . '/' . $defaultLocales; - $currentLocalesDir = $dir . '/' . $currentLocales; + $defaultLocalesDir = base_path($dir . '/' . $defaultLocales); + $currentLocalesDir = base_path($dir . '/' . $currentLocales); if (!is_dir($defaultLocalesDir)) { // this is the place for checking for submodules + + $modulePath = dirname(dirname($dir)); + + // does exist directory with translations? + if (is_dir(base_path($modulePath . '/Resources/lang')) || file_exists(base_path($modulePath.DIRECTORY_SEPARATOR.'info.php'))) { + $modules = File::directories($modulePath); + foreach ($modules as $module) { + $this->call('translate:dir', ['dir' => $module . '/Resources/lang']); + } + } return; } diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 395f776d..4c3aadf1 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -18,10 +18,10 @@ class Kernel extends ConsoleKernel Commands\ChangePassword::class, Commands\InstallModule::class, Commands\InstallAllModules::class, - Commands\Translations::class, + Commands\TranslateDirectory::class, Commands\TranslateAll::class, Commands\InitDatabaseSchema::class, - Commands\SetTheme::class, + Commands\SetTemplate::class, AddLanguage::class ]; diff --git a/app/Enumerations/ActiveContentStatuses.php b/app/Enumerations/ActiveContentStatuses.php deleted file mode 100644 index ed48c909..00000000 --- a/app/Enumerations/ActiveContentStatuses.php +++ /dev/null @@ -1,18 +0,0 @@ - $value) { - if ($key == Content::STATUS_DELETED) { - continue; - } - - self::push($key, $value); - } - } -} \ No newline at end of file diff --git a/app/Enumerations/ContentStatuses.php b/app/Enumerations/ContentStatuses.php index 8ed34f9f..9ed89887 100644 --- a/app/Enumerations/ContentStatuses.php +++ b/app/Enumerations/ContentStatuses.php @@ -9,7 +9,6 @@ class ContentStatuses extends BaseEnumeration { $roles = [ Content::STATUS_DRAFT => 'admin/contents.status_' . Content::STATUS_DRAFT, - Content::STATUS_DELETED => 'admin/contents.status_' . Content::STATUS_DELETED, Content::STATUS_PROTECTED => 'admin/contents.status_' . Content::STATUS_PROTECTED, Content::STATUS_PUBLIC => 'admin/contents.status_' . Content::STATUS_PUBLIC ]; diff --git a/app/Enumerations/UserStates.php b/app/Enumerations/UserStates.php index a693dce9..e28d4662 100644 --- a/app/Enumerations/UserStates.php +++ b/app/Enumerations/UserStates.php @@ -15,7 +15,6 @@ class UserStates extends BaseEnumeration public static function init() { $items = [ - User::STATE_DELETED => 'admin/userManagement.status_' . User::STATE_DELETED, User::STATE_BLOCKED => 'admin/userManagement.status_' . User::STATE_BLOCKED, User::STATE_ACTIVE => 'admin/userManagement.status_' . User::STATE_ACTIVE ]; diff --git a/app/Events/DeleteContent.php b/app/Events/DeleteContent.php index fe1f555a..47e2a7ff 100644 --- a/app/Events/DeleteContent.php +++ b/app/Events/DeleteContent.php @@ -12,7 +12,6 @@ class DeleteContent extends Event * @var Content */ public $page; - public $revision; /** * Create a new event instance. @@ -20,9 +19,8 @@ class DeleteContent extends Event * @param Content $page * @param $revision */ - public function __construct(Content $page, $revision) + public function __construct(Content $page) { $this->page = $page; - $this->revision = $revision; } } diff --git a/app/Handlers/Contracts/BaseModuleHandler.php b/app/Handlers/Contracts/BaseModuleHandler.php index c66e8a4e..9cd17c6b 100644 --- a/app/Handlers/Contracts/BaseModuleHandler.php +++ b/app/Handlers/Contracts/BaseModuleHandler.php @@ -5,8 +5,6 @@ use App\Models\User; interface BaseModuleHandler { - public static function getModuleId(); - /** * Register admin menu (or multiple ones) * @@ -18,9 +16,10 @@ interface BaseModuleHandler public static function registerAdminMenu(User $user); /** - * Install module + * Install module. * - * @return mixed + * This method is called after inserting info about module into database. It can be called only using Artisan in + * CLI. */ public static function install(); } \ No newline at end of file diff --git a/app/Handlers/Contracts/ContentTitles.php b/app/Handlers/Contracts/ContentTitles.php new file mode 100644 index 00000000..5d44f24f --- /dev/null +++ b/app/Handlers/Contracts/ContentTitles.php @@ -0,0 +1,40 @@ + '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); +} \ No newline at end of file diff --git a/app/Handlers/Handler.php b/app/Handlers/Handler.php index 63ddab59..06a29ae4 100644 --- a/app/Handlers/Handler.php +++ b/app/Handlers/Handler.php @@ -2,6 +2,7 @@ namespace App\Handlers; use App\Handlers\Contracts\AppHandler; +use App\Http\Controllers\Controller; use App\Models\Content; /** @@ -29,6 +30,19 @@ abstract class Handler extends BaseHandler implements AppHandler return History::class; } + /** + * Render given module into visitor area. + * + * @param Content $content DB model from table contents + * @param array $parameters parameters used for modifications/custom settings of given model + * + * @return mixed + */ + public function render(Content $content, $parameters) + { + return Controller::VisitorsView($content->template, ['page' => $content, 'extra' => $parameters]); + } + public static function getExtendedFormSections() { return [ diff --git a/app/Handlers/Registrators/TemplateRegistrator.php b/app/Handlers/Registrators/TemplateRegistrator.php index 6788df67..1d0f5257 100644 --- a/app/Handlers/Registrators/TemplateRegistrator.php +++ b/app/Handlers/Registrators/TemplateRegistrator.php @@ -187,7 +187,6 @@ class TemplateRegistrator $registeredTemplates = Templates::getWithTranslations(); if (!in_array($templateName, $registeredTemplates)) { - $params = [ 'template_name' => $templateName, 'name' => $templateName, @@ -197,8 +196,6 @@ class TemplateRegistrator 'editable' => 1 ]; - echo 'ukladam do db '.$templateName."\n"; - // save template into database $newTemplate = Template::create($params); diff --git a/app/Helpers/CurrentTemplate.php b/app/Helpers/CurrentTemplate.php new file mode 100644 index 00000000..2dfaa2cc --- /dev/null +++ b/app/Helpers/CurrentTemplate.php @@ -0,0 +1,44 @@ +where('enabled', true) + ->first(); + } + + return self::$template; + } + + /** + * Shortcut for getting template handler + * + * @return string + * @throws \ErrorException + */ + public static function handler() + { + $template = self::get(); + + if (!$template) { + throw new \ErrorException('No template selected, no handler available!'); + } + + return $template->handler; + } +} \ No newline at end of file diff --git a/app/Helpers/PageRenderer.php b/app/Helpers/PageRenderer.php new file mode 100644 index 00000000..06e31ab9 --- /dev/null +++ b/app/Helpers/PageRenderer.php @@ -0,0 +1,39 @@ +module_settings = json_decode($page->module_settings); + + // do something template-specific + $parameters = call_user_func_array([CurrentTemplate::handler(), 'beforeRender'], [&$page]); + + /** + * @var $handler AppHandler + */ + // get handler + $handler = new $page->module_handler; + // render page + return $handler->render($page, $parameters); + } +} diff --git a/app/Helpers/Settings.php b/app/Helpers/Settings.php new file mode 100644 index 00000000..cac5e30d --- /dev/null +++ b/app/Helpers/Settings.php @@ -0,0 +1,70 @@ +$key)) { + $exists = true; + return self::$settings->$key; + } + + $exists = false; + return $default; + } + + /** + * Get all settings + * + * @return array + */ + public static function all() + { + self::init(); + + return self::$settings; + } + + /** + * Initialize settings array + */ + private static function init() + { + if (!self::$initialized) { + $model = new SettingsModel(); + self::$settings = $model->getAll(); + } + } +} \ No newline at end of file diff --git a/app/Helpers/Template.php b/app/Helpers/Template.php index a085d384..fab3a803 100644 --- a/app/Helpers/Template.php +++ b/app/Helpers/Template.php @@ -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); diff --git a/app/Helpers/functions.php b/app/Helpers/functions.php index 5fd8b190..938ae77e 100644 --- a/app/Helpers/functions.php +++ b/app/Helpers/functions.php @@ -11,4 +11,16 @@ function e($value) { return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); +} + +/** + * Shortcut for \App\Helpers\Settings::get() + * + * @param $key + * @param null $default + * + * @return null|string + */ +function setting($key, $default = null) { + return \App\Helpers\Settings::get($key, $default); } \ No newline at end of file diff --git a/app/Http/Controllers/Admin/ActionLogOverview.php b/app/Http/Controllers/Admin/ActionLog/Overview.php similarity index 77% rename from app/Http/Controllers/Admin/ActionLogOverview.php rename to app/Http/Controllers/Admin/ActionLog/Overview.php index 1f5d5f08..5e2e0c5d 100644 --- a/app/Http/Controllers/Admin/ActionLogOverview.php +++ b/app/Http/Controllers/Admin/ActionLog/Overview.php @@ -1,12 +1,11 @@ log = $log; } diff --git a/app/Http/Controllers/Admin/ContentOverview.php b/app/Http/Controllers/Admin/Contents/ContentOverview.php similarity index 70% rename from app/Http/Controllers/Admin/ContentOverview.php rename to app/Http/Controllers/Admin/Contents/ContentOverview.php index d6b95f39..6e96e6d5 100644 --- a/app/Http/Controllers/Admin/ContentOverview.php +++ b/app/Http/Controllers/Admin/Contents/ContentOverview.php @@ -1,7 +1,8 @@ handler; + + if (is_callable([$module->handler, 'getOverviewTitle'])) { + $pageHeading = call_user_func([$module->handler, 'getOverviewTitle']); + } } // get pages for overview @@ -47,10 +53,11 @@ class ContentOverview extends Controller $homepage = $this->content->getHomepage(); return view('admin.content.overview', [ - 'pages' => $pages, - 'homepage' => $homepage, - 'statuses' => ContentStatuses::getWithTranslations(), - 'module' => isset($module) ? $module : null + 'pages' => $pages, + 'homepage' => $homepage, + 'pageHeading' => $pageHeading, + 'statuses' => ContentStatuses::getWithTranslations(), + 'module' => isset($module) ? $module : null ]); } } \ No newline at end of file diff --git a/app/Http/Controllers/Admin/CreateNewContent.php b/app/Http/Controllers/Admin/Contents/CreateNewContent.php similarity index 77% rename from app/Http/Controllers/Admin/CreateNewContent.php rename to app/Http/Controllers/Admin/Contents/CreateNewContent.php index 1beee33b..61d8d6a4 100644 --- a/app/Http/Controllers/Admin/CreateNewContent.php +++ b/app/Http/Controllers/Admin/Contents/CreateNewContent.php @@ -1,8 +1,10 @@ trans($module->name)]); + if (is_callable([$module->handler, 'getCreateNewTitle'])) { + $pageHeading = call_user_func([$module->handler, 'getCreateNewTitle']); + } + + // get list of usable templates $templates = Template::where('module_id', $moduleId) ->select(['template_name', 'name', 'description']) ->get(); - $categoryModule = ModuleInstance::get('categories'); // add categories, if supported + $categoryModule = ModuleInstance::get('categories'); $categories = []; if ($module->support_categories && $categoryModule->enabled()) { $categories = Category::where('module_id', $moduleId) @@ -61,6 +86,7 @@ class CreateNewContent extends Controller // render view return view('admin.content.new', [ + 'pageHeading' => $pageHeading, 'contentStatuses' => $statuses, 'templates' => $templates, 'moduleId' => $moduleId, @@ -86,7 +112,6 @@ class CreateNewContent extends Controller */ public function postNew(CreateNewContentRequest $request, $moduleId) { - $page = new Content(); try { $module = Module::findOrFail($moduleId); } catch (NotFoundHttpException $e) { @@ -96,6 +121,10 @@ class CreateNewContent extends Controller ->with('error', trans('admin/contents.invalidModule')); } + // create new page + $page = new Content(); + $page->template = $request->template; + // setup basic info $page->user_id = \Auth::user()->id; $page->language = LaravelLocalization::getCurrentLocale(); @@ -107,20 +136,26 @@ class CreateNewContent extends Controller $page->meta_description = $request->metaDescription; $page->content = $request->pageContent; $page->status = $request->status; - $page->template = $request->template; $page->module_handler = $module->handler; $page->category_id = (int)$request->category > 0 ? $request->category : null; + // is any parent page specified? if ($request->parentPage) { $page->parent_id = $request->parentPage; } // upload file - if provided - $page->uploadTitlePhoto($request); + $file = $page->uploadTitlePhoto($request); + + $page->title_photo = $file ? $file['filename'] : null; + + // let template to modify request and content page + call_user_func_array([CurrentTemplate::handler(), 'beforeSave'], [&$page, $request]); // save module-specific stuff call_user_func_array([$module->handler, 'createNew'], [&$page, $request]); + // save content of page before (possible) uploading related files $page->save(); // upload files assigned to content page diff --git a/app/Http/Controllers/Admin/EditContent.php b/app/Http/Controllers/Admin/Contents/EditContent.php similarity index 86% rename from app/Http/Controllers/Admin/EditContent.php rename to app/Http/Controllers/Admin/Contents/EditContent.php index cae69a9a..fda4f63e 100644 --- a/app/Http/Controllers/Admin/EditContent.php +++ b/app/Http/Controllers/Admin/Contents/EditContent.php @@ -1,14 +1,15 @@ history = $history; @@ -56,27 +65,38 @@ 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); + // get module ID + $module = Module::where('handler', $page->module_handler) + ->firstOrFail(); - $templates = Template::where('module_id', $moduleId) + $pageHeading = trans('admin/contents.edit', ['name' => e($page->title)]); + if (is_callable([$module->handler, 'getCreateNewTitle'])) { + $pageHeading = call_user_func([$module->handler, 'getEditTitle'], $page); + } + + // 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([CurrentTemplate::handler(), 'beforePopulatingEditForm'], [&$page]); + $history = $this->history->getStructuredHistoryOf($page); // render view return view('admin.content.edit', [ + 'pageHeading' => $pageHeading, 'contentStatuses' => $statuses, 'page' => $page, 'module' => $module, @@ -118,12 +138,16 @@ class EditContent extends Controller ->with('error', trans('admin/contents.invalidPage')); } + // upload file - if provided + $file = $page->uploadTitlePhoto($request); + + // let template to modify request and content page + call_user_func_array([CurrentTemplate::handler(), 'beforeSave'], [&$page, $request]); + $request->url = Str::slug($request->url); $request->parentPage = $request->parentPage ? $request->parentPage : null; - $request->titlePhotoFilename = $page->title_photo; - // upload file - if provided - $page->uploadTitlePhoto($request); + $request->titlePhotoFilename = $file ? $file['filename'] : null; // array of fields in database and fields from request $fields = [ @@ -142,7 +166,8 @@ class EditContent extends Controller ]; // save new values with history - $data = self::getFields($request, $fields); + $data = self::getFields($request, array_values($fields)); + list($revision, $changedColumns) = $page->updateWithHistory($fields, $data); $changedColumns += call_user_func_array([$page->module_handler, 'edit'], [&$page, $request, $revision]); @@ -178,7 +203,6 @@ class EditContent extends Controller { // get possible statuses $statuses = ContentStatuses::getWithTranslations(); - unset($statuses[Content::STATUS_DELETED]); // check if given status is valid if (!isset($statuses[$status])) { @@ -241,23 +265,13 @@ class EditContent extends Controller // change url to something unique $oldURL = $page->url; - $page->url = uniqid($page->url . '_deleted'); - $page->save(); - - list ($revision, $changedColumns) = $page->updateWithHistory(['status' => 'status'], ['status' => Content::STATUS_DELETED]); + $page->delete(); // let module to delete some extra content created during page's life call_user_func([$page->module_handler, 'delete'], $page); - // was something changed? - if ($changedColumns == 0) { - return redirect() - ->route('content.edit', ['id' => $page->id]) - ->with('error', trans('admin/contents.nothingToEdit')); - } - // trigger delete event - Event::fire(new DeleteContent($page, $revision)); + Event::fire(new DeleteContent($page)); // redirect back to overview with info for user return redirect() @@ -286,7 +300,7 @@ class EditContent extends Controller } catch (NotFoundHttpException $e) { // page not found return redirect() - ->route('content.overview') + ->back() ->with('error', trans('admin/contents.invalidPage')); } @@ -301,7 +315,7 @@ class EditContent extends Controller // was something changed? if ($changedColumns == 0) { return redirect() - ->route('content.overview') + ->back() ->with('error', trans('admin/contents.nothingToEdit')); } @@ -310,7 +324,7 @@ class EditContent extends Controller // redirect back to overview with info for user return redirect() - ->route('content.overview') + ->back() ->with('success', trans('admin/contents.homepageSetup', [ 'name' => $page->title, 'url' => $page->url diff --git a/app/Http/Controllers/Admin/History.php b/app/Http/Controllers/Admin/Contents/History.php similarity index 92% rename from app/Http/Controllers/Admin/History.php rename to app/Http/Controllers/Admin/Contents/History.php index f7ad6d86..a58cdbcf 100644 --- a/app/Http/Controllers/Admin/History.php +++ b/app/Http/Controllers/Admin/Contents/History.php @@ -1,6 +1,7 @@ route('content.overview'); - -// return view('admin.dashboard'); - } -} diff --git a/app/Http/Controllers/Admin/Dashboard/Dashboard.php b/app/Http/Controllers/Admin/Dashboard/Dashboard.php new file mode 100644 index 00000000..3e666127 --- /dev/null +++ b/app/Http/Controllers/Admin/Dashboard/Dashboard.php @@ -0,0 +1,14 @@ +route('content.overview'); + } +} diff --git a/app/Http/Controllers/Admin/CreateNewDirectory.php b/app/Http/Controllers/Admin/FileManager/CreateNewDirectory.php similarity index 93% rename from app/Http/Controllers/Admin/CreateNewDirectory.php rename to app/Http/Controllers/Admin/FileManager/CreateNewDirectory.php index 31e960fa..8f6bfc00 100644 --- a/app/Http/Controllers/Admin/CreateNewDirectory.php +++ b/app/Http/Controllers/Admin/FileManager/CreateNewDirectory.php @@ -1,8 +1,8 @@ route('upload.overview', ['id' => $request->parentDirectory]) - ->with('error', trans('admin/fileManager.subdirectoryExists')) - ->withInput(); + ->withInput() + ->with('error', trans('admin/fileManager.subdirectoryExists')); } } diff --git a/app/Http/Controllers/Admin/EditDirectory.php b/app/Http/Controllers/Admin/FileManager/EditDirectory.php similarity index 88% rename from app/Http/Controllers/Admin/EditDirectory.php rename to app/Http/Controllers/Admin/FileManager/EditDirectory.php index 7992a7cb..d3fd42d2 100644 --- a/app/Http/Controllers/Admin/EditDirectory.php +++ b/app/Http/Controllers/Admin/FileManager/EditDirectory.php @@ -1,8 +1,8 @@ route('upload.overview', ['id' => $directoryId]) - ->with('error', trans('admin/fileManager.subdirectoryExists')) - ->withInput(); + ->withInput() + ->with('error', trans('admin/fileManager.subdirectoryExists')); } } diff --git a/app/Http/Controllers/Admin/EditUploadedFile.php b/app/Http/Controllers/Admin/FileManager/EditUploadedFile.php similarity index 96% rename from app/Http/Controllers/Admin/EditUploadedFile.php rename to app/Http/Controllers/Admin/FileManager/EditUploadedFile.php index dc923eba..c29bb82a 100644 --- a/app/Http/Controllers/Admin/EditUploadedFile.php +++ b/app/Http/Controllers/Admin/FileManager/EditUploadedFile.php @@ -1,10 +1,10 @@ file = $file; } - /** * Uploads any file * - * @return \Illuminate\Http\RedirectResponse + * @param Request $request + * + * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse */ public function postFile(Request $request) { @@ -38,12 +40,6 @@ class UploadFile extends Controller $uploadedFiles[] = $uploadedFile; } - // we do not want to redirect - if ($request->ajax()) { - return $this->jsonResponse($uploadedFiles); - } - - // upload was not performed via ajax, redirect user return redirect() ->route('upload.overview', ['directory' => $request->directoryId]) ->with('success', trans_choice('admin/fileManager.uploadSuccessfull', $successfullUploads)); @@ -52,31 +48,17 @@ class UploadFile extends Controller } } + /** + * Send message and redirect to upload.overview when upload failed + * + * @param $message + * + * @return \Illuminate\Http\RedirectResponse + */ protected function invalidUpload($message) { return redirect() ->route('upload.overview') ->with('error', trans('admin/fileManager.' . $message)); } - - /** - * Send response about uploaded file as JSON - * - * @param $uploadedFile - * - * @return \Illuminate\Http\JsonResponse - */ - private function jsonResponse($uploadedFile) - { - $filename = $uploadedFile['file']->getFilename(); - $url = config('app.upload_path') . DIRECTORY_SEPARATOR . $filename; - - $data = [ - 'filename' => $filename, - 'url' => $url, - 'id' => $uploadedFile['id'] - ]; - - return response()->json($data); - } } \ No newline at end of file diff --git a/app/Http/Controllers/Admin/UploadedFilesOverview.php b/app/Http/Controllers/Admin/FileManager/UploadedFilesOverview.php similarity index 93% rename from app/Http/Controllers/Admin/UploadedFilesOverview.php rename to app/Http/Controllers/Admin/FileManager/UploadedFilesOverview.php index 6b5c0a21..c8d1def9 100644 --- a/app/Http/Controllers/Admin/UploadedFilesOverview.php +++ b/app/Http/Controllers/Admin/FileManager/UploadedFilesOverview.php @@ -1,9 +1,7 @@ loginFailed(); } + /** + * Logs out user + * + * @return \Illuminate\Http\RedirectResponse + */ + public function logout() + { + + // remove user information + \Auth::logout(); + + // redirect to login form with info about successfull logout + return redirect() + ->route('login') + ->with('success', trans('admin/common.logoutSuccessfull')); + } + private function loginFailed() { return redirect() diff --git a/app/Http/Controllers/Admin/CopyTemplate.php b/app/Http/Controllers/Admin/Templates/CopyTemplate.php similarity index 93% rename from app/Http/Controllers/Admin/CopyTemplate.php rename to app/Http/Controllers/Admin/Templates/CopyTemplate.php index f4598804..1a407547 100644 --- a/app/Http/Controllers/Admin/CopyTemplate.php +++ b/app/Http/Controllers/Admin/Templates/CopyTemplate.php @@ -1,12 +1,10 @@ template_name, '::')) { list($namespace) = explode('::', $template->template_name); diff --git a/app/Http/Controllers/Admin/EditTemplate.php b/app/Http/Controllers/Admin/Templates/EditTemplate.php similarity index 95% rename from app/Http/Controllers/Admin/EditTemplate.php rename to app/Http/Controllers/Admin/Templates/EditTemplate.php index e7995dc5..fb372b83 100644 --- a/app/Http/Controllers/Admin/EditTemplate.php +++ b/app/Http/Controllers/Admin/Templates/EditTemplate.php @@ -1,6 +1,6 @@ getPath(); @@ -89,7 +92,7 @@ class EditTemplate extends Controller } // save new content only if something was changed - if ($this->isContentDifferent($originalTemplate, $customTemplate, $request->templateContent)) { + if ($template->editable && $this->isContentDifferent($originalTemplate, $customTemplate, $request->templateContent)) { // create directory for custom updates if (!is_dir($customDir)) { diff --git a/app/Http/Controllers/Admin/TemplateOverview.php b/app/Http/Controllers/Admin/Templates/TemplateOverview.php similarity index 81% rename from app/Http/Controllers/Admin/TemplateOverview.php rename to app/Http/Controllers/Admin/Templates/TemplateOverview.php index f766b14e..8baf7300 100644 --- a/app/Http/Controllers/Admin/TemplateOverview.php +++ b/app/Http/Controllers/Admin/Templates/TemplateOverview.php @@ -1,11 +1,9 @@ id; } - // @todo add some authorization (e.g. via can/cannot methods) - // try to find given user - $user = User::where('status', '!=', User::STATE_DELETED) - ->findOrFail($id); + $user = User::findOrFail($id); $states = UserStates::getWithTranslations(); $roles = UserRoles::getWithTranslations(); diff --git a/app/Http/Controllers/Admin/UserDetail.php b/app/Http/Controllers/Admin/Users/UserDetail.php similarity index 91% rename from app/Http/Controllers/Admin/UserDetail.php rename to app/Http/Controllers/Admin/Users/UserDetail.php index 46c4d461..8f7cf6d8 100644 --- a/app/Http/Controllers/Admin/UserDetail.php +++ b/app/Http/Controllers/Admin/Users/UserDetail.php @@ -1,10 +1,10 @@ ', User::STATE_DELETED) - ->orderBy('id', 'desc'); + $users = User::orderBy('id', 'desc'); // get statuses $statuses = UserStates::getWithTranslations(); $statuses[''] = trans('admin/userManagement.allStatuses'); - unset($statuses[User::STATE_DELETED]); // filter using status if (!is_null($status) && isset($statuses[$status])) { @@ -29,9 +26,9 @@ class UsersOverview extends Controller $users = $users->paginate($perPage); return view('admin.userManagement.overview', [ - 'users' => $users, - 'roles' => UserRoles::getWithTranslations(), - 'statuses' => $statuses, + 'users' => $users, + 'roles' => UserRoles::getWithTranslations(), + 'statuses' => $statuses, 'currentStatus' => $status ]); } diff --git a/app/Http/Controllers/Admin/CreateNewWidgetArea.php b/app/Http/Controllers/Admin/Widgets/Areas/CreateNewWidgetArea.php similarity index 85% rename from app/Http/Controllers/Admin/CreateNewWidgetArea.php rename to app/Http/Controllers/Admin/Widgets/Areas/CreateNewWidgetArea.php index 9fe19ce6..91c22e35 100644 --- a/app/Http/Controllers/Admin/CreateNewWidgetArea.php +++ b/app/Http/Controllers/Admin/Widgets/Areas/CreateNewWidgetArea.php @@ -1,9 +1,8 @@ new WidgetArea()]); } /** diff --git a/app/Http/Controllers/Admin/EditWidgetArea.php b/app/Http/Controllers/Admin/Widgets/Areas/EditWidgetArea.php similarity index 81% rename from app/Http/Controllers/Admin/EditWidgetArea.php rename to app/Http/Controllers/Admin/Widgets/Areas/EditWidgetArea.php index b163308d..819600e5 100644 --- a/app/Http/Controllers/Admin/EditWidgetArea.php +++ b/app/Http/Controllers/Admin/Widgets/Areas/EditWidgetArea.php @@ -1,9 +1,8 @@ invalidArea(); } + // check if widget area is deletable + if (!$area->deletable) { + return redirect() + ->back() + ->with('error', trans('admin/widgetAreas.notDeletableArea')); + } + // delete area $area->delete(); diff --git a/app/Http/Controllers/Admin/WidgetAreaOverview.php b/app/Http/Controllers/Admin/Widgets/Areas/WidgetAreaOverview.php similarity index 84% rename from app/Http/Controllers/Admin/WidgetAreaOverview.php rename to app/Http/Controllers/Admin/Widgets/Areas/WidgetAreaOverview.php index 079752cf..d813d1f2 100644 --- a/app/Http/Controllers/Admin/WidgetAreaOverview.php +++ b/app/Http/Controllers/Admin/Widgets/Areas/WidgetAreaOverview.php @@ -1,8 +1,8 @@ deletable) { + return redirect() + ->back() + ->with('error', trans('admin/widgets.notDeletable')); + } + $widget->delete(); // redirect with success notification diff --git a/app/Http/Controllers/Admin/WidgetsOverview.php b/app/Http/Controllers/Admin/Widgets/WidgetsOverview.php similarity index 81% rename from app/Http/Controllers/Admin/WidgetsOverview.php rename to app/Http/Controllers/Admin/Widgets/WidgetsOverview.php index 424c2e84..4dc6ba1d 100644 --- a/app/Http/Controllers/Admin/WidgetsOverview.php +++ b/app/Http/Controllers/Admin/Widgets/WidgetsOverview.php @@ -1,6 +1,7 @@ module_handler; + // add one new "view" for page + $page->view(); - // render page - return $handler->render($page, ''); + return PageRenderer::render($page); } } diff --git a/app/Http/Controllers/Visitor/Homepage.php b/app/Http/Controllers/Visitor/Homepage.php index 00c0264d..a40ba1da 100644 --- a/app/Http/Controllers/Visitor/Homepage.php +++ b/app/Http/Controllers/Visitor/Homepage.php @@ -17,10 +17,10 @@ class Homepage extends ContentPage } // render page - try { +// try { return $this->renderPage($page); - } catch (\Exception $e) { - return view('errors.homepageNotAvailable', ['exception' => $e, 'reason' => 'exception']); - } +// } catch (\Exception $e) { +// return view('errors.homepageNotAvailable', ['exception' => $e, 'reason' => 'exception']); +// } } } diff --git a/app/Http/Requests/EditTemplateRequest.php b/app/Http/Requests/EditTemplateRequest.php index 63b35b07..aedf05af 100644 --- a/app/Http/Requests/EditTemplateRequest.php +++ b/app/Http/Requests/EditTemplateRequest.php @@ -12,8 +12,7 @@ class EditTemplateRequest extends Request public function rules() { return [ - 'name' => 'required|max:128', - 'templateContent' => 'required' + 'name' => 'required|max:128', ]; } } diff --git a/app/Http/routes.php b/app/Http/routes.php index d1913d45..f5f885c5 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -24,235 +24,26 @@ Route::group([ 'prefix' => 'admin' ], function () { - // these routes are available only for user with role admin - Route::group(['middleware' => 'onlyAdmin'], function () { - // display form for creating new user - Route::get('users/new', 'CreateNewUser@getNewUser') - ->name('users.new'); - - // POST methods - // display form for creating new user - Route::post('users/new', 'CreateNewUser@postRegistration') - ->name('users.newPost'); - - // display form for edit of existing user - Route::get('users/edit/{id}', 'EditUser@getEditUser') - ->name('users.edit'); - - // form for adding new custom field - Route::get('customfields/new', 'CreateNewCustomField@getNew') - ->name('customfields.new'); - - // form for editing custom field - Route::get('customfields/edit/{id}', 'EditCustomField@getEditCustomField') - ->name('customfields.edit'); - - // form for adding new custom field - Route::post('customfields/new', 'CreateNewCustomField@postNew') - ->name('customfields.newPost'); - - // form for editing custom field - Route::post('customfields/edit/{id}', 'EditCustomField@postEditCustomField') - ->name('customfields.editPost'); - - Route::get('customfields/delete/{id}', 'EditCustomField@deleteCustomField') - ->name('customfields.delete'); - - Route::get('templates/overview/path/{id}', 'TemplateOverview@getPath') - ->name('templates.overview'); - - Route::post('templates/copy', 'CopyTemplate@createCopy') - ->name('templates.postCopy'); - - // Route::get('templates/new/{pathId}', 'TemplateOverview@getPath') - // ->name('templates.new'); - - Route::get('templates/edit/{id}', 'EditTemplate@getEdit') - ->name('templates.edit'); - - Route::post('templates/edit/{id}', 'EditTemplate@postEdit') - ->name('templates.postEdit'); - }); - - // list of all static pages - Route::get('content/overview/{moduleId?}/{status?}', 'ContentOverview@getOverview') - ->name('content.overview'); - - // form for creating new static page - Route::get('content/new/{moduleId}', 'CreateNewContent@getNew') - ->name('content.new'); - - // form for editing existing static page - Route::get('content/edit/{id}', 'EditContent@getEdit') - ->name('content.edit'); - - // post method for creating new static page - Route::post('content/new/{moduleId}', 'CreateNewContent@postNew') - ->name('content.newPost'); - - // post method for creating new static page - Route::post('content/edit/{id}', 'EditContent@postEdit') - ->name('content.editPost'); - - // routes for quick actions - - // change status of page (protected, public or draft) - Route::get('content/status/{id}/{status}', 'EditContent@changeStatus') - ->name('content.changeStatus'); - - // set homepage - Route::get('content/homepage/{id}', 'EditContent@setHomepage') - ->name('content.setHomepage'); - - // delete page - Route::get('content/delete/{id}', 'EditContent@deletePage') - ->name('content.delete'); - - // these routes are available for all logged (authenticated) users + // include all routes in routes/admin subfolder + $files = ['contents', 'fileManager', 'settings', 'templates', 'users', 'widgets']; + foreach ($files as $file) { + require_once __DIR__ . DIRECTORY_SEPARATOR . 'routes' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR . $file . '.php'; + } // display dashboard with some usefull information - Route::get('/', 'Dashboard@getIndex') + Route::get('/', 'Dashboard\Dashboard@getIndex') ->name('admin.dashboard'); - // display user detail in admin - Route::get('users/detail/{id?}', 'UserDetail@getDetail') - ->name('users.detail'); - - // method for saving edited data of user - Route::post('users/edit/{id}', 'EditUser@postEdit') - ->name('users.editPost'); - - // display list of all users registered into our system - Route::get('users/overview/{state?}/{perPage?}', 'UsersOverview@getOverview') - ->name('users.overview'); - - // history of changes for specified page - Route::get('history/{id}', 'History@getOverview') - ->name('content.history'); - - // history of changes for specified page - Route::get('history/{revision}/{id}', 'History@getDiff') - ->name('history.diff'); - // list of all actions - Route::get('actions', 'ActionLogOverview@getOverview') + Route::get('actions', 'ActionLog\Overview@getOverview') ->name('log.overview'); - // routes for widget areas - Route::get('widgets/areas/overview', 'WidgetAreaOverview@getOverview') - ->name('widgetareas.overview'); - - // form for new widget area - Route::get('widgets/areas/new', 'CreateNewWidgetArea@getNew') - ->name('widgetareas.new'); - - // form for new widget area - Route::post('widgets/areas/new', 'CreateNewWidgetArea@postNew') - ->name('widgetareas.postNew'); - - // edit form for widget area - Route::get('widgets/areas/edit/{id}', 'EditWidgetArea@getEdit') - ->name('widgetareas.edit'); - - Route::post('widgets/areas/edit/{id}', 'EditWidgetArea@postEdit') - ->name('widgetareas.postEdit'); - - // edit form for widget area - Route::get('widgets/areas/delete/{id}', 'EditWidgetArea@deleteArea') - ->name('widgetareas.delete'); - - // list of all installed widget types - Route::get('widgets/overview', 'WidgetsOverview@getOverview') - ->name('widgets.overview'); - - // create new instance of widget of given type - Route::get('widgets/new/{type}', 'CreateNewWidget@getNew') - ->name('widgets.new'); - - Route::post('widgets/new/{type}', 'CreateNewWidget@postNew') - ->name('widgets.postNew'); - - // edit widget settings - Route::get('widgets/edit/{id}', 'EditWidget@getEdit') - ->name('widgets.edit'); - - // delete widget settings - Route::get('widgets/delete/{id}', 'EditWidget@deleteWidget') - ->name('widgets.delete'); - - Route::post('widgets/edit/{id}', 'EditWidget@postEdit') - ->name('widgets.postEdit'); - - // display form for association of widget and widget area - Route::get('widgets/associate/{widgetArea}', 'AssociateWidget@getList') - ->name('widgets.associate'); - - Route::get('widgets/deassociate/{widgetId}/{widgetArea}', 'AssociateWidget@deleteAssociation') - ->name('widgets.deleteAssociation'); - - Route::get('widgets/associate/{widgetId}/{widgetArea}', 'AssociateWidget@makeAssociation') - ->name('widgets.makeAssociation'); - - // uploader methods - Route::get('directories/new/{id?}', 'CreateNewDirectory@getNew') - ->name('upload.newDirectory'); - - Route::get('directories/edit/{id}', 'EditDirectory@getEdit') - ->name('upload.editDirectory'); - - Route::post('directories/edit/{id}', 'EditDirectory@postEdit') - ->name('upload.postEditDirectory'); - - Route::get('files/overview/{id?}/{type?}/{subtype?}', 'UploadedFilesOverview@getOverview') - ->name('upload.overview'); - - route::post('directories/new', 'CreateNewDirectory@postNew') - ->name('upload.postNewDirectory'); - - Route::get('upload/detail/{id}', 'EditUploadedFile@getDetail') - ->name('upload.detail'); - - Route::post('upload/detail/{id}', 'EditUploadedFile@postEdit') - ->name('upload.postEdit'); - - Route::get('upload/delete/{id}', 'EditUploadedFile@deleteFile') - ->name('upload.delete'); - - // uploader methods - Route::post('upload/file', 'UploadFile@postFile') - ->name('upload.postFile'); - - Route::post('upload/image', 'UploadFile@postImage') - ->name('upload.postImage'); - - // form with settings - Route::get('settings', 'Settings@getOverview') - ->name('settings.overview'); - - Route::post('settings', 'Settings@saveSettings') - ->name('settings.save'); - + // search form with results Route::get('search', 'Search@getSearch') ->name('search'); - // custom fields - // ------------------------------ - - // list of all custom fields - Route::get('customfields/overview', 'CustomFieldsOverview@getOverview') - ->name('customfields.overview'); - // logout method - Route::get('logout', function () { - - // remove user information - \Auth::logout(); - - // redirect to login form with info about successfull logout - return redirect() - ->route('login') - ->with('success', trans('admin/common.logoutSuccessfull')); - }) + Route::get('logout', 'Login@logout') ->name('logout'); }); @@ -264,13 +55,12 @@ Route::group([ Route::group(['middleware' => ['web']], function () { + // Homepage Route::get('/', 'Visitor\Homepage@getIndex') ->name('page.index'); // display login form - Route::get('admin/login', function () { - return view('admin.loginForm'); - }) + Route::get('admin/login', 'Admin\Login@getLogin') ->name('loginForm'); // handle login diff --git a/app/Http/routes/admin/contents.php b/app/Http/routes/admin/contents.php new file mode 100644 index 00000000..6e7b6a6a --- /dev/null +++ b/app/Http/routes/admin/contents.php @@ -0,0 +1,42 @@ + 'Contents'], function () { + // list of all static pages + Route::get('content/overview/{moduleId?}/{status?}', 'ContentOverview@getOverview') + ->name('content.overview'); + + // form for creating new static page + Route::get('content/new/{moduleId}', 'CreateNewContent@getNew') + ->name('content.new'); + + // form for editing existing static page + Route::get('content/edit/{id}', 'EditContent@getEdit') + ->name('content.edit'); + + // post method for creating new static page + Route::post('content/new/{moduleId}', 'CreateNewContent@postNew') + ->name('content.newPost'); + + // post method for creating new static page + Route::post('content/edit/{id}', 'EditContent@postEdit') + ->name('content.editPost'); + + // change status of page (protected, public or draft) + Route::get('content/status/{id}/{status}', 'EditContent@changeStatus') + ->name('content.changeStatus'); + + // set homepage + Route::get('content/homepage/{id}', 'EditContent@setHomepage') + ->name('content.setHomepage'); + + // delete page + Route::get('content/delete/{id}', 'EditContent@deletePage') + ->name('content.delete'); + + // history of changes for specified page + Route::get('history/{id}', 'History@getOverview') + ->name('content.history'); + + // history of changes for specified page + Route::get('history/{revision}/{id}', 'History@getDiff') + ->name('history.diff'); +}); \ No newline at end of file diff --git a/app/Http/routes/admin/fileManager.php b/app/Http/routes/admin/fileManager.php new file mode 100644 index 00000000..58eecfaf --- /dev/null +++ b/app/Http/routes/admin/fileManager.php @@ -0,0 +1,32 @@ + 'FileManager'], function () { + + Route::get('files/overview/{id?}/{type?}/{subtype?}', 'UploadedFilesOverview@getOverview') + ->name('upload.overview'); + + // uploader methods + Route::get('directories/new/{id?}', 'CreateNewDirectory@getNew') + ->name('upload.newDirectory'); + + Route::get('directories/edit/{id}', 'EditDirectory@getEdit') + ->name('upload.editDirectory'); + + Route::post('directories/edit/{id}', 'EditDirectory@postEdit') + ->name('upload.postEditDirectory'); + + route::post('directories/new', 'CreateNewDirectory@postNew') + ->name('upload.postNewDirectory'); + + Route::get('upload/detail/{id}', 'EditUploadedFile@getDetail') + ->name('upload.detail'); + + Route::post('upload/detail/{id}', 'EditUploadedFile@postEdit') + ->name('upload.postEdit'); + + Route::get('upload/delete/{id}', 'EditUploadedFile@deleteFile') + ->name('upload.delete'); + + // uploader methods + Route::post('upload', 'UploadFile@postFile') + ->name('upload.postFile'); +}); diff --git a/app/Http/routes/admin/settings.php b/app/Http/routes/admin/settings.php new file mode 100644 index 00000000..70b2b996 --- /dev/null +++ b/app/Http/routes/admin/settings.php @@ -0,0 +1,7 @@ +name('settings.overview'); + +Route::post('settings', 'Settings@saveSettings') + ->name('settings.save'); \ No newline at end of file diff --git a/app/Http/routes/admin/templates.php b/app/Http/routes/admin/templates.php new file mode 100644 index 00000000..bb265533 --- /dev/null +++ b/app/Http/routes/admin/templates.php @@ -0,0 +1,15 @@ + 'onlyAdmin', 'namespace' => 'Templates'], function () { + Route::get('templates/overview/path/{id}', 'TemplateOverview@getPath') + ->name('templates.overview'); + + Route::post('templates/copy', 'CopyTemplate@createCopy') + ->name('templates.postCopy'); + + Route::get('templates/edit/{id}', 'EditTemplate@getEdit') + ->name('templates.edit'); + + Route::post('templates/edit/{id}', 'EditTemplate@postEdit') + ->name('templates.postEdit'); +}); \ No newline at end of file diff --git a/app/Http/routes/admin/users.php b/app/Http/routes/admin/users.php new file mode 100644 index 00000000..69d643f7 --- /dev/null +++ b/app/Http/routes/admin/users.php @@ -0,0 +1,27 @@ + 'Users'], function () { + // display form for creating new user + Route::get('users/new', 'CreateNewUser@getNewUser') + ->name('users.new'); + + // POST methods + // display form for creating new user + Route::post('users/new', 'CreateNewUser@postRegistration') + ->name('users.newPost'); + + // display form for edit of existing user + Route::get('users/edit/{id}', 'EditUser@getEditUser') + ->name('users.edit'); + + // display user detail in admin + Route::get('users/detail/{id?}', 'UserDetail@getDetail') + ->name('users.detail'); + + // method for saving edited data of user + Route::post('users/edit/{id}', 'EditUser@postEdit') + ->name('users.editPost'); + + // display list of all users registered into our system + Route::get('users/overview/{state?}/{perPage?}', 'UsersOverview@getOverview') + ->name('users.overview'); +}); diff --git a/app/Http/routes/admin/widgets.php b/app/Http/routes/admin/widgets.php new file mode 100644 index 00000000..d10df3fd --- /dev/null +++ b/app/Http/routes/admin/widgets.php @@ -0,0 +1,60 @@ + 'Widgets'], function () { + + // list of all installed widget types + Route::get('widgets/overview', 'WidgetsOverview@getOverview') + ->name('widgets.overview'); + + // create new instance of widget of given type + Route::get('widgets/new/{type}', 'CreateNewWidget@getNew') + ->name('widgets.new'); + + Route::post('widgets/new/{type}', 'CreateNewWidget@postNew') + ->name('widgets.postNew'); + + // edit widget settings + Route::get('widgets/edit/{id}', 'EditWidget@getEdit') + ->name('widgets.edit'); + + // delete widget settings + Route::get('widgets/delete/{id}', 'EditWidget@deleteWidget') + ->name('widgets.delete'); + + Route::post('widgets/edit/{id}', 'EditWidget@postEdit') + ->name('widgets.postEdit'); + + // display form for association of widget and widget area + Route::get('widgets/associate/{widgetArea}', 'AssociateWidget@getList') + ->name('widgets.associate'); + + Route::get('widgets/deassociate/{widgetId}/{widgetArea}', 'AssociateWidget@deleteAssociation') + ->name('widgets.deleteAssociation'); + + Route::get('widgets/associate/{widgetId}/{widgetArea}', 'AssociateWidget@makeAssociation') + ->name('widgets.makeAssociation'); + + Route::group(['namespace' => 'Areas'], function () { + Route::get('widgets/areas/overview', 'WidgetAreaOverview@getOverview') + ->name('widgetareas.overview'); + + // form for new widget area + Route::get('widgets/areas/new', 'CreateNewWidgetArea@getNew') + ->name('widgetareas.new'); + + // form for new widget area + Route::post('widgets/areas/new', 'CreateNewWidgetArea@postNew') + ->name('widgetareas.postNew'); + + // edit form for widget area + Route::get('widgets/areas/edit/{id}', 'EditWidgetArea@getEdit') + ->name('widgetareas.edit'); + + Route::post('widgets/areas/edit/{id}', 'EditWidgetArea@postEdit') + ->name('widgetareas.postEdit'); + + // edit form for widget area + Route::get('widgets/areas/delete/{id}', 'EditWidgetArea@deleteArea') + ->name('widgetareas.delete'); + }); +}); diff --git a/app/Listeners/LogDeleteContent.php b/app/Listeners/LogDeleteContent.php index 1c443dd4..f5e9ff47 100644 --- a/app/Listeners/LogDeleteContent.php +++ b/app/Listeners/LogDeleteContent.php @@ -22,7 +22,6 @@ class LogDeleteContent extends Logger 'id' => $event->page->id, 'url' => $event->page->url, 'title' => $event->page->title, - 'revision' => $event->revision ]; $this->saveLog($parameters); diff --git a/app/Models/Content.php b/app/Models/Content.php index 8d13ee10..3c85be0e 100644 --- a/app/Models/Content.php +++ b/app/Models/Content.php @@ -5,6 +5,7 @@ namespace App\Models; use App\Enumerations\ContentStatuses; use App\Http\Requests\Request; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\DB; use Mcamara\LaravelLocalization\Facades\LaravelLocalization; @@ -15,6 +16,7 @@ use Modules\Categories\Entities\Category; */ class Content extends Model { + use SoftDeletes; public $timestamps = true; protected $fillable = [ 'user_id', @@ -38,7 +40,6 @@ class Content extends Model protected $guarded = []; protected $subpages = []; protected $subpagesInit = false; - const STATUS_DELETED = 'deleted'; const STATUS_DRAFT = 'draft'; const STATUS_PROTECTED = 'protected'; const STATUS_PUBLIC = 'public'; @@ -59,8 +60,7 @@ class Content extends Model */ public function getPageQuery() { - return Content::where('status', '!=', Content::STATUS_DELETED) - ->where('status', '!=', Content::STATUS_DRAFT); + return Content::where('status', '!=', Content::STATUS_DRAFT); } /** @@ -179,6 +179,7 @@ class Content extends Model * @param bool $handler * @param bool $status * @param bool $joinAuthor + * @param bool $joinParent * * @return */ @@ -186,9 +187,10 @@ class Content extends Model { $query = DB::table('contents') ->select('contents.*', 'modules.name AS module_name', 'modules.id AS module_id') - ->where('contents.status', '<>', self::STATUS_DELETED) - ->where('contents.language', LaravelLocalization::getCurrentLocale()) ->join('modules', 'modules.handler', '=', 'contents.module_handler') + ->whereNull('contents.deleted_at') + ->where('contents.language', LaravelLocalization::getCurrentLocale()) + ->where('modules.enabled', 1) ->orderBy('created_at', 'DESC'); // filter results using handler @@ -293,10 +295,10 @@ class Content extends Model public function uploadTitlePhoto(Request $request) { if ($request->hasFile('titlePhoto')) { - $file = $this->file->upload($request->file('titlePhoto'), $request); - - $this->title_photo = $file['filename']; + return $this->file->upload($request->file('titlePhoto'), $request); } + + return null; } /** @@ -435,4 +437,12 @@ class Content extends Model // save history ContentHistory::create($history); } + + public function view() + { + $this->timestamps = false; + $this->viewed++; + $this->save(); + $this->timestamps = true; + } } diff --git a/app/Models/Module.php b/app/Models/Module.php index d782c2d9..82cd681e 100644 --- a/app/Models/Module.php +++ b/app/Models/Module.php @@ -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,51 @@ 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) + { + if (!is_dir($directory)) + return; + + $success = \File::deleteDirectory($directory, true); + + if (!$success) { + throw new \ErrorException('Cannot delete ' . $directory); + } + } } \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index ef114c18..be576578 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -6,6 +6,7 @@ use App\Helpers\Gravatar; use Illuminate\Auth\Authenticatable; use Illuminate\Contracts\Auth\Authenticatable as AuthenticableInterface; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\Access\Authorizable; /** @@ -13,8 +14,8 @@ use Illuminate\Foundation\Auth\Access\Authorizable; */ class User extends Model implements AuthenticableInterface { - use Authenticatable, Authorizable; - public $timestamps = false; + use SoftDeletes, Authenticatable, Authorizable; + public $timestamps = true; protected $fillable = [ 'email', 'username', diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index fe7104a6..20fb3f5c 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -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'); - } - } } diff --git a/app/Providers/GlobalViewDataProvider.php b/app/Providers/GlobalViewDataProvider.php index c0c92845..f201c027 100644 --- a/app/Providers/GlobalViewDataProvider.php +++ b/app/Providers/GlobalViewDataProvider.php @@ -21,11 +21,11 @@ class GlobalViewDataProvider extends ServiceProvider return; } - $settings = new Settings(); - - foreach ($settings->getForView() as $key => $value) { - view()->share($key, $value); - } +// $settings = new Settings(); +// +// foreach ($settings->getForView() as $key => $value) { +// view()->share($key, $value); +// } $this->extendBlade(); } @@ -73,6 +73,14 @@ class GlobalViewDataProvider extends ServiceProvider return preg_replace($pattern, $code, $view); }); + // shortcut for displaying Gravatar + Blade::extend(function ($view) { + $pattern = $this->createPattern('gravatar'); + $code = ''; + + return preg_replace($pattern, $code, $view); + }); + // shortcut for assets (placed in /public/assets/ Blade::extend(function ($view) { $pattern = $this->createPattern('asset'); diff --git a/app/Providers/LaravelLocalizationServiceProvider.php b/app/Providers/LaravelLocalizationServiceProvider.php index 30b153dc..94f761fc 100644 --- a/app/Providers/LaravelLocalizationServiceProvider.php +++ b/app/Providers/LaravelLocalizationServiceProvider.php @@ -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) { diff --git a/app/Providers/ModuleManager.php b/app/Providers/ModuleManager.php index dff7e0be..f925aec1 100644 --- a/app/Providers/ModuleManager.php +++ b/app/Providers/ModuleManager.php @@ -3,7 +3,6 @@ namespace App\Providers; use App\Models\Module; -use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Log; use Illuminate\Support\ServiceProvider; @@ -15,16 +14,31 @@ class ModuleManager extends ServiceProvider */ public function boot() { - // ignore running from artisan - if (App::runningInConsole()) { + // this service provider needs published config - config is merged right after this service provider + try { + $modules = Module::where('enabled', true) + ->get(); + } catch (\Exception $e) { + // ignore error return; } - // this service provider needs published config - config is merged right after this service provider - $modules = Module::get(); - // iterate over all modules and enable or disable them foreach ($modules as $module) { + + $module->module_settings = json_decode($module->module_settings); + + if ($module->is_template) { + + if (isset($module->module_settings->providers) && !empty($module->module_settings->providers)) { + foreach ($module->module_settings->providers as $provider) { + $this->app->register($provider); + } + } + + continue; + } + $this->createModulesJson($module); } } @@ -47,7 +61,7 @@ class ModuleManager extends ServiceProvider */ private function createModulesJson(Module $module) { - $config = json_decode($module->module_settings); + $config = $module->module_settings; if (is_null($config)) { return; @@ -58,7 +72,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; } diff --git a/app/Providers/SubmodulesManager.php b/app/Providers/SubmodulesManager.php index 26408faf..48907798 100644 --- a/app/Providers/SubmodulesManager.php +++ b/app/Providers/SubmodulesManager.php @@ -9,11 +9,11 @@ use Pingpong\Modules\Json; class SubmodulesManager extends ServiceProvider { /** - * Register the service provider. + * Boot submodules * * @return void */ - public function register() + public function boot() { $enabledModules = \Module::enabled(); @@ -52,4 +52,14 @@ class SubmodulesManager extends ServiceProvider } } } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + // TODO: Implement register() method. + } } \ No newline at end of file diff --git a/app/Providers/TemplateServiceProvider.php b/app/Providers/TemplateServiceProvider.php new file mode 100644 index 00000000..d4ff4457 --- /dev/null +++ b/app/Providers/TemplateServiceProvider.php @@ -0,0 +1,122 @@ +where('enabled', true) + ->first(); + + if ($template) { + $template->settings = json_decode($template->module_settings); + } + } catch (\Exception $e) { + return null; + } + + 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'); + } + } +} \ No newline at end of file diff --git a/app/Widgets/Menu/Menu.php b/app/Widgets/Menu/Menu.php index b3ca55d9..67429d17 100644 --- a/app/Widgets/Menu/Menu.php +++ b/app/Widgets/Menu/Menu.php @@ -72,8 +72,7 @@ class Menu extends NonInstallableWidget public static function selectContentPages($ids) { - $pages = Content::where('status', '!=', Content::STATUS_DELETED) - ->where('language', LaravelLocalization::getCurrentLocale()) + $pages = Content::where('language', LaravelLocalization::getCurrentLocale()) ->whereIn('id', $ids) ->get(); $used = []; diff --git a/composer.json b/composer.json index 69031af8..2a77208a 100644 --- a/composer.json +++ b/composer.json @@ -30,16 +30,11 @@ ], "psr-4": { "App\\": "app/", - "Modules\\": "modules/" + "Modules\\": "modules/", + "Templates\\": "templates/" } }, "scripts": { - "post-root-package-install": [ - "php -r \"copy('.env.example', '.env');\"" - ], - "post-create-project-cmd": [ - "php artisan key:generate" - ], "post-install-cmd": [ "php artisan clear-compiled", "php artisan optimize" diff --git a/config/app.php b/config/app.php index dd7ce301..49291806 100644 --- a/config/app.php +++ b/config/app.php @@ -129,49 +129,49 @@ return [ 'providers' => [ - /* - * Laravel Framework Service Providers... - */ - Illuminate\Auth\AuthServiceProvider::class, - Illuminate\Broadcasting\BroadcastServiceProvider::class, - Illuminate\Bus\BusServiceProvider::class, - Illuminate\Cache\CacheServiceProvider::class, - Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, - Illuminate\Cookie\CookieServiceProvider::class, - Illuminate\Database\DatabaseServiceProvider::class, - Illuminate\Encryption\EncryptionServiceProvider::class, - Illuminate\Filesystem\FilesystemServiceProvider::class, - Illuminate\Foundation\Providers\FoundationServiceProvider::class, - Illuminate\Hashing\HashServiceProvider::class, - Illuminate\Mail\MailServiceProvider::class, - Illuminate\Pagination\PaginationServiceProvider::class, - Illuminate\Pipeline\PipelineServiceProvider::class, - Illuminate\Queue\QueueServiceProvider::class, - Illuminate\Redis\RedisServiceProvider::class, - Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, - Illuminate\Session\SessionServiceProvider::class, - Illuminate\Translation\TranslationServiceProvider::class, - Illuminate\Validation\ValidationServiceProvider::class, - Illuminate\View\ViewServiceProvider::class, - Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider::class, - // this class must be before ModulesServiceProvider from PingPong, because it handles files module.json - App\Providers\ModuleManager::class, - Pingpong\Modules\ModulesServiceProvider::class, - Pingpong\Menus\MenusServiceProvider::class, - App\Providers\SubmodulesManager::class, + /* + * Laravel Framework Service Providers... + */ + Illuminate\Auth\AuthServiceProvider::class, + Illuminate\Broadcasting\BroadcastServiceProvider::class, + Illuminate\Bus\BusServiceProvider::class, + Illuminate\Cache\CacheServiceProvider::class, + Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, + Illuminate\Cookie\CookieServiceProvider::class, + Illuminate\Database\DatabaseServiceProvider::class, + Illuminate\Encryption\EncryptionServiceProvider::class, + Illuminate\Filesystem\FilesystemServiceProvider::class, + Illuminate\Foundation\Providers\FoundationServiceProvider::class, + Illuminate\Hashing\HashServiceProvider::class, + Illuminate\Mail\MailServiceProvider::class, + Illuminate\Pagination\PaginationServiceProvider::class, + Illuminate\Pipeline\PipelineServiceProvider::class, + Illuminate\Queue\QueueServiceProvider::class, + Illuminate\Redis\RedisServiceProvider::class, + Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, + Illuminate\Session\SessionServiceProvider::class, + Illuminate\Translation\TranslationServiceProvider::class, + Illuminate\Validation\ValidationServiceProvider::class, + Illuminate\View\ViewServiceProvider::class, + Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider::class, + // this class must be before ModulesServiceProvider from PingPong, because it handles files module.json + App\Providers\ModuleManager::class, + Pingpong\Modules\ModulesServiceProvider::class, + Pingpong\Menus\MenusServiceProvider::class, + App\Providers\SubmodulesManager::class, + Franzose\ClosureTable\ClosureTableServiceProvider::class, + \Watson\Sitemap\SitemapServiceProvider::class, + /* + * Application Service Providers... + */ + App\Providers\AppServiceProvider::class, + App\Providers\AuthServiceProvider::class, + App\Providers\EventServiceProvider::class, + App\Providers\RouteServiceProvider::class, + App\Providers\GlobalViewDataProvider::class, + \App\Providers\TemplateServiceProvider::class, - Franzose\ClosureTable\ClosureTableServiceProvider::class, - \Watson\Sitemap\SitemapServiceProvider::class, - /* - * Application Service Providers... - */ - App\Providers\AppServiceProvider::class, - App\Providers\AuthServiceProvider::class, - App\Providers\EventServiceProvider::class, - App\Providers\RouteServiceProvider::class, - App\Providers\GlobalViewDataProvider::class, - -], + ], /* |-------------------------------------------------------------------------- | Class Aliases @@ -185,41 +185,41 @@ return [ 'aliases' => [ - 'App' => Illuminate\Support\Facades\App::class, - 'Artisan' => Illuminate\Support\Facades\Artisan::class, - 'Auth' => Illuminate\Support\Facades\Auth::class, - 'Blade' => Illuminate\Support\Facades\Blade::class, - 'Cache' => Illuminate\Support\Facades\Cache::class, - 'Config' => Illuminate\Support\Facades\Config::class, - 'Cookie' => Illuminate\Support\Facades\Cookie::class, - 'Crypt' => Illuminate\Support\Facades\Crypt::class, - 'DB' => Illuminate\Support\Facades\DB::class, - 'Eloquent' => Illuminate\Database\Eloquent\Model::class, - 'Event' => Illuminate\Support\Facades\Event::class, - 'File' => Illuminate\Support\Facades\File::class, - 'Gate' => Illuminate\Support\Facades\Gate::class, - 'Hash' => Illuminate\Support\Facades\Hash::class, - 'Lang' => Illuminate\Support\Facades\Lang::class, - 'Log' => Illuminate\Support\Facades\Log::class, - 'Mail' => Illuminate\Support\Facades\Mail::class, - 'Menu' => Pingpong\Menus\MenuFacade::class, - 'Module' => Pingpong\Modules\Facades\Module::class, - 'Password' => Illuminate\Support\Facades\Password::class, - 'Queue' => Illuminate\Support\Facades\Queue::class, - 'Redirect' => Illuminate\Support\Facades\Redirect::class, - 'Redis' => Illuminate\Support\Facades\Redis::class, - 'Request' => Illuminate\Support\Facades\Request::class, - 'Response' => Illuminate\Support\Facades\Response::class, - 'Route' => Illuminate\Support\Facades\Route::class, - 'Schema' => Illuminate\Support\Facades\Schema::class, - 'Session' => Illuminate\Support\Facades\Session::class, - 'Sitemap' => Watson\Sitemap\Facades\Sitemap::class, - 'Storage' => Illuminate\Support\Facades\Storage::class, - 'URL' => Illuminate\Support\Facades\URL::class, - 'Validator' => Illuminate\Support\Facades\Validator::class, - 'LaravelLocalization' => Mcamara\LaravelLocalization\Facades\LaravelLocalization::class, - 'View' => Illuminate\Support\Facades\View::class, + 'App' => Illuminate\Support\Facades\App::class, + 'Artisan' => Illuminate\Support\Facades\Artisan::class, + 'Auth' => Illuminate\Support\Facades\Auth::class, + 'Blade' => Illuminate\Support\Facades\Blade::class, + 'Cache' => Illuminate\Support\Facades\Cache::class, + 'Config' => Illuminate\Support\Facades\Config::class, + 'Cookie' => Illuminate\Support\Facades\Cookie::class, + 'Crypt' => Illuminate\Support\Facades\Crypt::class, + 'DB' => Illuminate\Support\Facades\DB::class, + 'Eloquent' => Illuminate\Database\Eloquent\Model::class, + 'Event' => Illuminate\Support\Facades\Event::class, + 'File' => Illuminate\Support\Facades\File::class, + 'Gate' => Illuminate\Support\Facades\Gate::class, + 'Hash' => Illuminate\Support\Facades\Hash::class, + 'Lang' => Illuminate\Support\Facades\Lang::class, + 'Log' => Illuminate\Support\Facades\Log::class, + 'Mail' => Illuminate\Support\Facades\Mail::class, + 'Menu' => Pingpong\Menus\MenuFacade::class, + 'Module' => Pingpong\Modules\Facades\Module::class, + 'Password' => Illuminate\Support\Facades\Password::class, + 'Queue' => Illuminate\Support\Facades\Queue::class, + 'Redirect' => Illuminate\Support\Facades\Redirect::class, + 'Redis' => Illuminate\Support\Facades\Redis::class, + 'Request' => Illuminate\Support\Facades\Request::class, + 'Response' => Illuminate\Support\Facades\Response::class, + 'Route' => Illuminate\Support\Facades\Route::class, + 'Schema' => Illuminate\Support\Facades\Schema::class, + 'Session' => Illuminate\Support\Facades\Session::class, + 'Sitemap' => Watson\Sitemap\Facades\Sitemap::class, + 'Storage' => Illuminate\Support\Facades\Storage::class, + 'URL' => Illuminate\Support\Facades\URL::class, + 'Validator' => Illuminate\Support\Facades\Validator::class, + 'LaravelLocalization' => Mcamara\LaravelLocalization\Facades\LaravelLocalization::class, + 'View' => Illuminate\Support\Facades\View::class, -], + ], ]; diff --git a/database/migrations/2016_09_19_192132_SaveTemplatesIntoModulesTable.php b/database/migrations/2016_09_19_192132_SaveTemplatesIntoModulesTable.php new file mode 100644 index 00000000..2b5b484b --- /dev/null +++ b/database/migrations/2016_09_19_192132_SaveTemplatesIntoModulesTable.php @@ -0,0 +1,32 @@ +boolean('is_template')->default(false)->comment('Templates can be treated as modules.'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('modules', function (Blueprint $table){ + $table->dropColumn('is_template'); + }); + } +} diff --git a/database/migrations/2016_09_19_225527_AddNumberOfViewsOfPage.php b/database/migrations/2016_09_19_225527_AddNumberOfViewsOfPage.php new file mode 100644 index 00000000..d4061908 --- /dev/null +++ b/database/migrations/2016_09_19_225527_AddNumberOfViewsOfPage.php @@ -0,0 +1,34 @@ +integer('viewed') + ->comment('How many times has been this content page displayed?') + ->after('updated_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('contents', function (Blueprint $table) { + $table->dropColumn('viewed'); + }); + } +} diff --git a/database/migrations/2016_09_20_113806_AddTemplateSupportToWidgets.php b/database/migrations/2016_09_20_113806_AddTemplateSupportToWidgets.php new file mode 100644 index 00000000..ff448074 --- /dev/null +++ b/database/migrations/2016_09_20_113806_AddTemplateSupportToWidgets.php @@ -0,0 +1,55 @@ +integer('template_id') + ->comment('Commes this widget with template?') + ->after('handler') + ->nullable(true) + ->default(null); + $table->integer('module_id') + ->comment('Commes this widget with some module?') + ->after('template_id') + ->nullable(true) + ->default(null); + + $table->foreign('template_id') + ->references('id') + ->on('modules') + ->onDelete('cascade') + ->onUpdate('cascade'); + + $table->foreign('module_id') + ->references('id') + ->on('modules') + ->onDelete('cascade') + ->onUpdate('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('widget_types', function (Blueprint $table) { + $table->dropForeign('widget_types_module_id_foreign'); + $table->dropForeign('widget_types_template_id_foreign'); + $table->dropColumn(['template_id', 'module_id']); + }); + } +} diff --git a/database/migrations/2016_09_20_114930_MakeWidgetsAndWidgetAreasNotDeletable.php b/database/migrations/2016_09_20_114930_MakeWidgetsAndWidgetAreasNotDeletable.php new file mode 100644 index 00000000..b68636cf --- /dev/null +++ b/database/migrations/2016_09_20_114930_MakeWidgetsAndWidgetAreasNotDeletable.php @@ -0,0 +1,44 @@ +boolean('deletable') + ->default(true) + ->comment('Can this widget be deleted?'); + }); + + Schema::table('widget_areas', function (Blueprint $table) { + $table->boolean('deletable') + ->default(true) + ->comment('Can this widget area be deleted?'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('widgets', function (Blueprint $table) { + $table->dropColumn('deletable'); + }); + + Schema::table('widget_areas', function (Blueprint $table) { + $table->dropColumn('deletable'); + }); + } +} diff --git a/database/migrations/2016_09_22_191204_AddSoftDeletesToContent.php b/database/migrations/2016_09_22_191204_AddSoftDeletesToContent.php new file mode 100644 index 00000000..5046f05d --- /dev/null +++ b/database/migrations/2016_09_22_191204_AddSoftDeletesToContent.php @@ -0,0 +1,53 @@ +getDoctrineSchemaManager() + ->getDatabasePlatform(); + $platform->registerDoctrineTypeMapping('enum', 'string'); + } + + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + + Schema::table('contents', function (Blueprint $table) { + $table->softDeletes() + ->after('updated_at'); + }); + + DB::statement(" + ALTER TABLE `contents` + CHANGE `status` `status` enum('draft','protected','public') COLLATE 'utf8_unicode_ci' NOT NULL DEFAULT 'draft' AFTER `module_settings` + "); + + DB::statement('UPDATE contents SET status="protected", deleted_at=NOW() WHERE status=""'); + DB::statement('DELETE FROM content_history WHERE new_value="deleted" OR old_value="deleted"'); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('contents', function (Blueprint $table) { + $table->dropSoftDeletes(); + }); + + DB::statement("ALTER TABLE `contents` +CHANGE `status` `status` enum('draft','protected','public', 'deleted') COLLATE 'utf8_unicode_ci' NULL DEFAULT 'draft' AFTER `module_settings`;"); + } +} diff --git a/database/migrations/2016_09_22_195825_AddSoftDeletesToUsers.php b/database/migrations/2016_09_22_195825_AddSoftDeletesToUsers.php new file mode 100644 index 00000000..fd1b05e2 --- /dev/null +++ b/database/migrations/2016_09_22_195825_AddSoftDeletesToUsers.php @@ -0,0 +1,61 @@ +getDoctrineSchemaManager() + ->getDatabasePlatform(); + $platform->registerDoctrineTypeMapping('enum', 'string'); + } + + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('users', function (Blueprint $table) { + $table->timestamp('created_at') + ->nullable() + ->after('id'); + $table->timestamp('updated_at') + ->nullable() + ->after('created_at'); + $table->timestamp('deleted_at') + ->nullable() + ->after('updated_at'); + }); + + DB::statement(" + ALTER TABLE `users` + CHANGE `status` `status` enum('blocked','active') COLLATE 'utf8_unicode_ci' NOT NULL DEFAULT 'active' + "); + + DB::statement('UPDATE users SET created_at=NOW(), updated_at=NOW()'); + DB::statement('UPDATE users SET status="blocked", deleted_at=NOW() WHERE status=""'); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropTimestamps(); + $table->dropSoftDeletes(); + }); + + DB::statement(" + ALTER TABLE `users` + CHANGE `status` `status` enum('blocked','active', 'deleted') COLLATE 'utf8_unicode_ci' NOT NULL DEFAULT 'active' + "); + } +} diff --git a/database/migrations/2016_09_24_175541_FixForeignKeysOnActionLog.php b/database/migrations/2016_09_24_175541_FixForeignKeysOnActionLog.php new file mode 100644 index 00000000..c24b2248 --- /dev/null +++ b/database/migrations/2016_09_24_175541_FixForeignKeysOnActionLog.php @@ -0,0 +1,44 @@ +dropForeign('fk_action_log_action_log_type1'); + $table->dropForeign('fk_action_log_user1'); + + + $table->foreign('type_id') + ->references('id') + ->on('action_log_types') + ->onDelete('cascade') + ->onUpdate('cascade'); + + + $table->foreign('user_id') + ->references('id') + ->on('users') + ->onDelete('cascade') + ->onUpdate('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // do nothing + } +} diff --git a/database/schema.sql b/database/schema.sql index 46ad3ef6..1f04812a 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -1,364 +1,712 @@ --- MySQL Script generated by MySQL Workbench --- Út 26. leden 2016, 22:44:34 CET --- Model: New Model Version: 1.0 --- MySQL Workbench Forward Engineering +-- MySQL dump 10.16 Distrib 10.1.17-MariaDB, for Linux (x86_64) +-- +-- Host: localhost Database: minicms +-- ------------------------------------------------------ +-- Server version 10.1.17-MariaDB -SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +SET NAMES utf8; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; -SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES'; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; --- ----------------------------------------------------- --- Schema minicms --- ----------------------------------------------------- +-- +-- Table structure for table `action_log` +-- --- ----------------------------------------------------- --- Table `users` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `users` ( - `id` INT NOT NULL AUTO_INCREMENT, - `email` VARCHAR(64) NOT NULL COMMENT 'E-mail is used as a contact and also as a username', - `username` VARCHAR(64) NOT NULL COMMENT 'Username can be used also for login. Default value is users email.', - `password` VARCHAR(128) NOT NULL COMMENT 'Password hash', - `role` ENUM('visitor', 'editor', 'admin') NOT NULL DEFAULT 'visitor' COMMENT 'User\'s role determines his permissions in the system.', - `remember_token` VARCHAR(128) NULL DEFAULT NULL COMMENT 'Token used for \"remember me\" feature', - `name` VARCHAR(128) NULL DEFAULT NULL, - `status` ENUM('deleted', 'blocked', 'active') NOT NULL DEFAULT 'active', +DROP TABLE IF EXISTS `action_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `action_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `type_id` int(11) NOT NULL, + `user_id` int(11) NOT NULL, + `ip` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'User''s IP Address', + `user_agent` mediumtext COLLATE utf8_unicode_ci NOT NULL COMMENT 'Browser string of client, who performed some action.', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `parameters` mediumtext COLLATE utf8_unicode_ci COMMENT 'JSON serialized array/object passed into translation.', PRIMARY KEY (`id`), - UNIQUE INDEX `email_UNIQUE` (`email` ASC)) -ENGINE = InnoDB -COMMENT = 'Table with users who are allowed to log into system'; + KEY `fk_action_log_action_log_type1_idx` (`type_id`), + KEY `fk_action_log_user1_idx` (`user_id`), + CONSTRAINT `fk_action_log_action_log_type1` FOREIGN KEY (`type_id`) REFERENCES `action_log_types` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT `fk_action_log_user1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION +) ENGINE=InnoDB AUTO_INCREMENT=284 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='This table contains log of all actions, which happened in administration.'; +/*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `action_log_types` +-- --- ----------------------------------------------------- --- Table `modules` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `modules` ( - `handler` VARCHAR(255) NOT NULL, - `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `name` VARCHAR(64) NOT NULL, - `description` TEXT NULL DEFAULT NULL, - PRIMARY KEY (`handler`)) -ENGINE = InnoDB; - - --- ----------------------------------------------------- --- Table `files` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `files` ( - `id` INT NOT NULL AUTO_INCREMENT, - `user_id` INT NOT NULL COMMENT 'User, who uploaded this file', - `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `filename` VARCHAR(128) NOT NULL, - `sha256_hash` VARCHAR(64) NOT NULL COMMENT 'SHA hash of file content.', - `mime_type` VARCHAR(64) NOT NULL COMMENT 'MIME type of uploaded file', +DROP TABLE IF EXISTS `action_log_types`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `action_log_types` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `string_id` varchar(64) COLLATE utf8_unicode_ci NOT NULL COMMENT 'String ID used for better readability in loggers', + `name` varchar(128) COLLATE utf8_unicode_ci NOT NULL, + `value` varchar(128) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Log content - represented as translation string.', PRIMARY KEY (`id`), - INDEX `fk_file_user1_idx` (`user_id` ASC), - CONSTRAINT `fk_file_user1` - FOREIGN KEY (`user_id`) - REFERENCES `users` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION) -ENGINE = InnoDB -COMMENT = 'Table containing files which were uploaded into system.'; + UNIQUE KEY `action_log_types_string_id_unique` (`string_id`) +) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='List of all types which can be logged.'; +/*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `categories` +-- --- ----------------------------------------------------- --- Table `contents` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `contents` ( - `id` INT NOT NULL AUTO_INCREMENT, - `user_id` INT NOT NULL, - `module_handler` VARCHAR(255) NOT NULL, - `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `published_from` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, - `published_to` TIMESTAMP NULL DEFAULT NULL, - `title_photo` INT NULL DEFAULT NULL, - `title` VARCHAR(64) NOT NULL, - `url` VARCHAR(64) NOT NULL, - `meta_keywords` VARCHAR(128) NULL DEFAULT NULL, - `meta_description` MEDIUMTEXT NULL DEFAULT NULL, - `content` TEXT NULL DEFAULT NULL, - `module_settings` TEXT NULL COMMENT 'JSON serialized object passed as settings to the module.', - `status` ENUM('deleted', 'draft', 'protected', 'public') NULL DEFAULT 'draft' COMMENT 'This photo is supposed to be displayed e.g. on list of all pages.', +DROP TABLE IF EXISTS `categories`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `categories` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `deleted_at` timestamp NULL DEFAULT NULL, + `parent_id` int(10) unsigned DEFAULT NULL, + `position` int(10) unsigned NOT NULL, + `real_depth` int(10) unsigned NOT NULL, + `user_id` int(11) NOT NULL COMMENT 'Author of this category', + `module_id` int(11) NOT NULL COMMENT 'ID of module, where this category is used', + `name` varchar(255) NOT NULL COMMENT 'Human readable name of category', + `url` varchar(255) NOT NULL COMMENT 'URL slug of category', + `description` text NOT NULL, PRIMARY KEY (`id`), - INDEX `fk_content_user1_idx` (`user_id` ASC), - INDEX `url` (`url` ASC), - FULLTEXT INDEX `content_fulltext` (`content` ASC), - UNIQUE INDEX `url_UNIQUE` (`url` ASC), - INDEX `fk_content_module1_idx` (`module_handler` ASC), - INDEX `fk_content_file1_idx` (`title_photo` ASC), - CONSTRAINT `fk_content_user1` - FOREIGN KEY (`user_id`) - REFERENCES `users` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION, - CONSTRAINT `fk_content_module1` - FOREIGN KEY (`module_handler`) - REFERENCES `modules` (`handler`) - ON DELETE NO ACTION - ON UPDATE NO ACTION, - CONSTRAINT `fk_content_file1` - FOREIGN KEY (`title_photo`) - REFERENCES `files` (`id`) - ON DELETE SET NULL - ON UPDATE CASCADE) -ENGINE = InnoDB -COMMENT = 'Table with all content pages.'; + UNIQUE KEY `categories_url_module_id_unique` (`url`,`module_id`), + KEY `categories_url_index` (`url`), + KEY `categories_parent_id_foreign` (`parent_id`), + KEY `categories_user_id_foreign` (`user_id`), + KEY `categories_module_id_foreign` (`module_id`), + CONSTRAINT `categories_module_id_foreign` FOREIGN KEY (`module_id`) REFERENCES `modules` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `categories_parent_id_foreign` FOREIGN KEY (`parent_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL, + CONSTRAINT `categories_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `category_closure` +-- --- ----------------------------------------------------- --- Table `content_history` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `content_history` ( - `id` INT NOT NULL AUTO_INCREMENT, - `user_id` INT NOT NULL, - `content_id` INT NOT NULL, - `changed_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `edit_batch` VARCHAR(16) NOT NULL COMMENT 'This field holds identical values for all historical entries edited as one update.', - `column` VARCHAR(64) NOT NULL, - `new_value` TEXT NULL DEFAULT NULL, - `old_value` TEXT NULL DEFAULT NULL, +DROP TABLE IF EXISTS `category_closure`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `category_closure` ( + `closure_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `ancestor` int(10) unsigned NOT NULL, + `descendant` int(10) unsigned NOT NULL, + `depth` int(10) unsigned NOT NULL, + PRIMARY KEY (`closure_id`), + KEY `category_closure_ancestor_foreign` (`ancestor`), + KEY `category_closure_descendant_foreign` (`descendant`), + CONSTRAINT `category_closure_ancestor_foreign` FOREIGN KEY (`ancestor`) REFERENCES `categories` (`id`) ON DELETE CASCADE, + CONSTRAINT `category_closure_descendant_foreign` FOREIGN KEY (`descendant`) REFERENCES `categories` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `comments` +-- + +DROP TABLE IF EXISTS `comments`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `comments` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `user_id` int(11) DEFAULT NULL COMMENT 'When comment was inserted by logged user, save his ID here', + `content_id` int(11) NOT NULL, + `email` varchar(255) NOT NULL, + `name` varchar(255) NOT NULL, + `content` text NOT NULL, + `approved` tinyint(1) NOT NULL, PRIMARY KEY (`id`), - INDEX `edit_batch` (`edit_batch` ASC), - INDEX `column` (`column` ASC), - INDEX `fk_content_history_users_idx` (`user_id` ASC), - INDEX `fk_content_history_content1_idx` (`content_id` ASC), - CONSTRAINT `fk_content_history_users` - FOREIGN KEY (`user_id`) - REFERENCES `users` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION, - CONSTRAINT `fk_content_history_content1` - FOREIGN KEY (`content_id`) - REFERENCES `contents` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION) -ENGINE = InnoDB -COMMENT = 'Table for holding history of changes on some content.'; + KEY `comments_content_id_foreign` (`content_id`), + KEY `comments_user_id_foreign` (`user_id`), + CONSTRAINT `comments_content_id_foreign` FOREIGN KEY (`content_id`) REFERENCES `contents` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `comments_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `content_has_custom_fields` +-- --- ----------------------------------------------------- --- Table `widget_types` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `widget_types` ( - `handler` VARCHAR(255) NOT NULL COMMENT 'Class wich handles everything about this widget type', - `name` VARCHAR(64) NOT NULL COMMENT 'Name is used only in admin for better readability.', - `description` TEXT NULL DEFAULT NULL COMMENT 'This field also improves readability.', - PRIMARY KEY (`handler`)) -ENGINE = InnoDB -COMMENT = 'Widget type table contains information about all installed widgets. This is something like \"class\" to widgets.'; +DROP TABLE IF EXISTS `content_has_custom_fields`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `content_has_custom_fields` ( + `contents_id` int(11) NOT NULL, + `custom_field_id` int(11) NOT NULL, + `value` text COLLATE utf8_unicode_ci, + PRIMARY KEY (`contents_id`,`custom_field_id`), + KEY `fk_contents_has_custom_fields_custom_fields1_idx` (`custom_field_id`), + KEY `fk_contents_has_custom_fields_contents1_idx` (`contents_id`), + CONSTRAINT `fk_contents_has_custom_fields_contents1` FOREIGN KEY (`contents_id`) REFERENCES `contents` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT `fk_contents_has_custom_fields_custom_fields1` FOREIGN KEY (`custom_field_id`) REFERENCES `custom_fields` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `content_history` +-- --- ----------------------------------------------------- --- Table `widgets` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `widgets` ( - `id` INT NOT NULL AUTO_INCREMENT, - `widget_type_handler` VARCHAR(255) NOT NULL, - `user_id` INT NOT NULL COMMENT 'User ID who created this widget', - `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `name` VARCHAR(64) NOT NULL, - `settings` TEXT NOT NULL COMMENT 'JSON serialized values used for initializing given widget.', - `description` MEDIUMTEXT NULL, +DROP TABLE IF EXISTS `content_history`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `content_history` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `content_id` int(11) NOT NULL, + `changed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `edit_batch` varchar(16) COLLATE utf8_unicode_ci NOT NULL COMMENT 'This field holds identical values for all historical entries edited as one update.', + `column` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + `new_value` text COLLATE utf8_unicode_ci, + `old_value` text COLLATE utf8_unicode_ci, PRIMARY KEY (`id`), - INDEX `fk_widget_user1_idx` (`user_id` ASC), - INDEX `fk_widget_widget_type1_idx` (`widget_type_handler` ASC), - CONSTRAINT `fk_widget_user1` - FOREIGN KEY (`user_id`) - REFERENCES `users` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION, - CONSTRAINT `fk_widget_widget_type1` - FOREIGN KEY (`widget_type_handler`) - REFERENCES `widget_types` (`handler`) - ON DELETE NO ACTION - ON UPDATE NO ACTION) -ENGINE = InnoDB -COMMENT = 'Widget is a small piece of content, which can be inserted into widget area. Example of widgets: menu (with links), banner, some static html code. This table contains real \"instances\" of widgets.'; + KEY `edit_batch` (`edit_batch`), + KEY `column` (`column`), + KEY `fk_content_history_users_idx` (`user_id`), + KEY `fk_content_history_content1_idx` (`content_id`), + CONSTRAINT `fk_content_history_content1` FOREIGN KEY (`content_id`) REFERENCES `contents` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT `fk_content_history_users` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION +) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table for holding history of changes on some content.'; +/*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `contents` +-- --- ----------------------------------------------------- --- Table `widget_areas` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `widget_areas` ( - `id` INT NOT NULL, - `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `name` VARCHAR(64) NOT NULL COMMENT 'Used for user overview.', - `code` VARCHAR(32) NOT NULL COMMENT 'Value of this field is used for replacing placeholder text in templates with content of real widget area.', - `description` TEXT NULL DEFAULT NULL, +DROP TABLE IF EXISTS `contents`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `contents` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `parent_id` int(11) DEFAULT NULL COMMENT 'ID of parent page', + `user_id` int(11) NOT NULL, + `module_handler` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `category_id` int(10) unsigned DEFAULT NULL COMMENT 'FK to table categories', + `template` varchar(128) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'Template used for rendering this content page.', + `language` varchar(8) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'cs', + `is_homepage` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is this content page the homepage?', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `viewed` int(11) NOT NULL COMMENT 'How many times has been this content page displayed?', + `published_from` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + `published_to` timestamp NULL DEFAULT NULL, + `title_photo` varchar(128) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'This photo is supposed to be displayed e.g. on list of all pages.', + `title` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + `url` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + `meta_keywords` varchar(128) COLLATE utf8_unicode_ci DEFAULT NULL, + `meta_description` mediumtext COLLATE utf8_unicode_ci, + `content` text COLLATE utf8_unicode_ci, + `module_settings` text COLLATE utf8_unicode_ci COMMENT 'JSON serialized object passed as settings to the module.', + `status` enum('draft','protected','public','deleted') COLLATE utf8_unicode_ci DEFAULT 'draft' COMMENT 'This photo is supposed to be displayed e.g. on list of all pages.', PRIMARY KEY (`id`), - UNIQUE INDEX `code_UNIQUE` (`code` ASC)) -ENGINE = InnoDB -COMMENT = 'Widget area is container for widgets. This container is inserted into templates/content and populated by widgets.'; + UNIQUE KEY `contents_language_url_unique` (`language`,`url`), + KEY `fk_content_user1_idx` (`user_id`), + KEY `url` (`url`), + KEY `fk_content_module1_idx` (`module_handler`), + KEY `fk_content_file1_idx` (`title_photo`), + KEY `contents_is_homepage_index` (`is_homepage`), + KEY `contents_template_index` (`template`), + KEY `contents_parent_id_index` (`parent_id`), + KEY `contents_language_index` (`language`), + KEY `contents_category_id_foreign` (`category_id`), + CONSTRAINT `contents_category_id_foreign` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `contents_language_foreign` FOREIGN KEY (`language`) REFERENCES `languages` (`code`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `contents_module_handler_foreign` FOREIGN KEY (`module_handler`) REFERENCES `modules` (`handler`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `contents_parent_id_foreign` FOREIGN KEY (`parent_id`) REFERENCES `contents` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `contents_template_foreign` FOREIGN KEY (`template`) REFERENCES `templates` (`template_name`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `contents_title_photo_foreign` FOREIGN KEY (`title_photo`) REFERENCES `files` (`filename`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `fk_content_user1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION +) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table with all content pages.'; +/*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `contents_has_files` +-- --- ----------------------------------------------------- --- Table `widget_in_widget_area` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `widget_in_widget_area` ( - `widget_id` INT NOT NULL, - `widget_area_id` INT NOT NULL, - `position` INT NOT NULL COMMENT 'Which position should the widget take?', - PRIMARY KEY (`widget_id`, `widget_area_id`), - INDEX `fk_widget_has_widget_area_widget_area1_idx` (`widget_area_id` ASC), - INDEX `fk_widget_has_widget_area_widget1_idx` (`widget_id` ASC), - INDEX `position` (`position` ASC), - CONSTRAINT `fk_widget_has_widget_area_widget1` - FOREIGN KEY (`widget_id`) - REFERENCES `widgets` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION, - CONSTRAINT `fk_widget_has_widget_area_widget_area1` - FOREIGN KEY (`widget_area_id`) - REFERENCES `widget_areas` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION) -ENGINE = InnoDB; +DROP TABLE IF EXISTS `contents_has_files`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `contents_has_files` ( + `content_id` int(11) NOT NULL, + `file_id` int(11) NOT NULL, + PRIMARY KEY (`content_id`,`file_id`), + KEY `contents_has_files_file_id_foreign` (`file_id`), + CONSTRAINT `contents_has_files_content_id_foreign` FOREIGN KEY (`content_id`) REFERENCES `contents` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `contents_has_files_file_id_foreign` FOREIGN KEY (`file_id`) REFERENCES `files` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `custom_field_resource` +-- --- ----------------------------------------------------- --- Table `custom_field_types` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `custom_field_types` ( - `id` INT NOT NULL AUTO_INCREMENT, - `name` VARCHAR(64) NOT NULL COMMENT 'Name of custom field. Given as a translation string (e.g. as dir/section.translation)', - `handler` VARCHAR(255) NOT NULL COMMENT 'Class for handling this type of custom field', +DROP TABLE IF EXISTS `custom_field_resource`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `custom_field_resource` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(64) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Name of resource for custom fields. Saved as translation string.', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `custom_field_resources` +-- + +DROP TABLE IF EXISTS `custom_field_resources`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `custom_field_resources` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(64) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Name of resource for custom fields. Saved as translation string.', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `custom_field_types` +-- + +DROP TABLE IF EXISTS `custom_field_types`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `custom_field_types` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(64) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Name of custom field. Given as a translation string (e.g. as dir/section.translation)', + `handler` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Class for handling this type of custom field', PRIMARY KEY (`id`), - UNIQUE INDEX `name_UNIQUE` (`name` ASC), - UNIQUE INDEX `handler_UNIQUE` (`handler` ASC)) -ENGINE = InnoDB -COMMENT = 'List of all custom field types.'; + UNIQUE KEY `name_UNIQUE` (`name`), + UNIQUE KEY `handler_UNIQUE` (`handler`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='List of all custom field types.'; +/*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `custom_fields` +-- --- ----------------------------------------------------- --- Table `custom_field_resource` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `custom_field_resource` ( - `id` INT NOT NULL AUTO_INCREMENT, - `name` VARCHAR(64) NOT NULL COMMENT 'Name of resource for custom fields. Saved as translation string.', - PRIMARY KEY (`id`)) -ENGINE = InnoDB; - - --- ----------------------------------------------------- --- Table `custom_fields` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `custom_fields` ( - `id` INT NOT NULL AUTO_INCREMENT, - `name` VARCHAR(128) NOT NULL, - `type_id` INT NOT NULL COMMENT 'Foreign key to custom_field_types table', - `resource_id` INT NOT NULL, - `description` MEDIUMTEXT NULL DEFAULT NULL COMMENT 'Description used for detailed information about meaning of given custom field', - `default_value` TEXT NULL DEFAULT NULL, - `possible_values` TEXT NULL DEFAULT NULL COMMENT 'Possible values separated with pipeline', - `regexp` MEDIUMTEXT NULL DEFAULT NULL, - `min_length` INT(11) NULL DEFAULT NULL, - `max_length` INT(11) NULL DEFAULT NULL, - `required` TINYINT(1) NOT NULL DEFAULT 0, +DROP TABLE IF EXISTS `custom_fields`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `custom_fields` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(128) COLLATE utf8_unicode_ci NOT NULL, + `type_id` int(11) NOT NULL COMMENT 'Foreign key to custom_field_types table', + `resource_id` int(11) NOT NULL, + `description` mediumtext COLLATE utf8_unicode_ci COMMENT 'Description used for detailed information about meaning of given custom field', + `default_value` text COLLATE utf8_unicode_ci, + `possible_values` text COLLATE utf8_unicode_ci COMMENT 'Possible values separated with pipeline', + `regexp` mediumtext COLLATE utf8_unicode_ci, + `min_length` int(11) DEFAULT NULL, + `max_length` int(11) DEFAULT NULL, + `required` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), - INDEX `fk_custom_fields_custom_field_types1_idx` (`type_id` ASC), - INDEX `fk_custom_fields_custom_field_resource1_idx` (`resource_id` ASC), - CONSTRAINT `fk_custom_fields_custom_field_types1` - FOREIGN KEY (`type_id`) - REFERENCES `custom_field_types` (`id`) - ON DELETE CASCADE - ON UPDATE CASCADE, - CONSTRAINT `fk_custom_fields_custom_field_resource1` - FOREIGN KEY (`resource_id`) - REFERENCES `custom_field_resource` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION) -ENGINE = InnoDB -COMMENT = 'List of all custom fields which may be used for extending some part of content on webpage'; + KEY `fk_custom_fields_custom_field_types1_idx` (`type_id`), + KEY `fk_custom_fields_custom_field_resource1_idx` (`resource_id`), + CONSTRAINT `fk_custom_fields_custom_field_resource1` FOREIGN KEY (`resource_id`) REFERENCES `custom_field_resources` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT `fk_custom_fields_custom_field_types1` FOREIGN KEY (`type_id`) REFERENCES `custom_field_types` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='List of all custom fields which may be used for extending some part of content on webpage'; +/*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `directories` +-- --- ----------------------------------------------------- --- Table `user_has_custom_field` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `user_has_custom_field` ( - `user_id` INT NOT NULL, - `custom_field_id` INT NOT NULL, - `value` TEXT NOT NULL, - PRIMARY KEY (`user_id`, `custom_field_id`), - INDEX `fk_user_has_custom_field_custom_field1_idx` (`custom_field_id` ASC), - INDEX `fk_user_has_custom_field_user1_idx` (`user_id` ASC), - CONSTRAINT `fk_user_has_custom_field_user1` - FOREIGN KEY (`user_id`) - REFERENCES `users` (`id`) - ON DELETE CASCADE - ON UPDATE CASCADE, - CONSTRAINT `fk_user_has_custom_field_custom_field1` - FOREIGN KEY (`custom_field_id`) - REFERENCES `custom_fields` (`id`) - ON DELETE CASCADE - ON UPDATE CASCADE) -ENGINE = InnoDB; - - --- ----------------------------------------------------- --- Table `action_log_types` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `action_log_types` ( - `id` INT NOT NULL AUTO_INCREMENT, - `name` VARCHAR(128) NOT NULL, - `value` VARCHAR(128) NOT NULL COMMENT 'Log content - represented as translation string.', - PRIMARY KEY (`id`)) -ENGINE = InnoDB -COMMENT = 'List of all types which can be logged.'; - - --- ----------------------------------------------------- --- Table `action_log` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `action_log` ( - `id` INT NOT NULL AUTO_INCREMENT, - `type_id` INT NOT NULL, - `user_id` INT NOT NULL, - `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `parameters` MEDIUMTEXT NULL DEFAULT NULL COMMENT 'JSON serialized array/object passed into translation.', +DROP TABLE IF EXISTS `directories`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `directories` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `user_id` int(11) DEFAULT NULL COMMENT 'ID of user, who created this directory.', + `parent_id` int(11) DEFAULT NULL COMMENT 'ID of parent directory', + `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Directory name used for better navigation', + `description` mediumtext COLLATE utf8_unicode_ci COMMENT 'Description of directory', + `hidden` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Hide this directory from view of all directories?', PRIMARY KEY (`id`), - INDEX `fk_action_log_action_log_type1_idx` (`type_id` ASC), - INDEX `fk_action_log_user1_idx` (`user_id` ASC), - CONSTRAINT `fk_action_log_action_log_type1` - FOREIGN KEY (`type_id`) - REFERENCES `action_log_types` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION, - CONSTRAINT `fk_action_log_user1` - FOREIGN KEY (`user_id`) - REFERENCES `users` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION) -ENGINE = InnoDB -COMMENT = 'This table contains log of all actions, which happened in administration.'; + UNIQUE KEY `directories_parent_id_name_unique` (`parent_id`,`name`), + KEY `directories_user_id_index` (`user_id`), + KEY `directories_parent_id_index` (`parent_id`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `files` +-- + +DROP TABLE IF EXISTS `files`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `files` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL COMMENT 'User, who uploaded this file', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NULL DEFAULT NULL, + `filename` varchar(128) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Filename of localy stored file', + `directory_id` int(11) DEFAULT NULL COMMENT 'FK to directories table', + `original_filename` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Original filename of uploaded file', + `file_hash` varchar(64) COLLATE utf8_unicode_ci NOT NULL COMMENT 'SHA hash of file content.', + `hash_algorithm` varchar(16) COLLATE utf8_unicode_ci NOT NULL, + `mime_type` varchar(64) COLLATE utf8_unicode_ci NOT NULL COMMENT 'MIME type of uploaded file', + `file_size` int(11) NOT NULL COMMENT 'Filesize given in bytes', + `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Some human readable name of file. May be used in different ways', + `description` text COLLATE utf8_unicode_ci, + `keywords` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `fk_file_user1_idx` (`user_id`), + KEY `filename` (`filename`), + KEY `files_filename_index` (`filename`), + KEY `keywords_idx` (`keywords`), + KEY `files_directory_id_index` (`directory_id`), + CONSTRAINT `files_directory_id_foreign` FOREIGN KEY (`directory_id`) REFERENCES `directories` (`id`) ON UPDATE CASCADE, + CONSTRAINT `fk_file_user1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION +) ENGINE=InnoDB AUTO_INCREMENT=75 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table containing files which were uploaded into system.'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `languages` +-- + +DROP TABLE IF EXISTS `languages`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `languages` ( + `code` varchar(8) COLLATE utf8_unicode_ci NOT NULL, + `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Translated name for given language', + `script` varchar(16) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'Latn' COMMENT 'ISO code for locale script (Latn, Arab, ...)', + `native` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Native name for language', + PRIMARY KEY (`code`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `migrations` +-- + +DROP TABLE IF EXISTS `migrations`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `migrations` ( + `migration` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `batch` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `modules` +-- + +DROP TABLE IF EXISTS `modules`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `modules` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `parent_id` int(11) DEFAULT NULL COMMENT 'Parent module ID (when module is submodule)', + `handler` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `name` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + `description` text COLLATE utf8_unicode_ci, + `alias` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + `enabled` tinyint(1) NOT NULL COMMENT 'Is this module in use?', + `module_settings` text COLLATE utf8_unicode_ci COMMENT 'JSON serialized object with configuration of given module.', + `manages_content` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Does this module support content management?', + `support_categories` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Does this module support categories?', + `has_submodules` tinyint(1) NOT NULL DEFAULT '0', + `is_template` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Templates can be treated as modules.', + PRIMARY KEY (`id`), + UNIQUE KEY `modules_handler_unique` (`handler`), + KEY `modules_enabled_index` (`enabled`), + KEY `modules_parent_id_foreign` (`parent_id`), + CONSTRAINT `modules_parent_id_foreign` FOREIGN KEY (`parent_id`) REFERENCES `modules` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `password_resets` +-- + +DROP TABLE IF EXISTS `password_resets`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `password_resets` ( + `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `token` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY `password_resets_email_index` (`email`), + KEY `password_resets_token_index` (`token`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `settings` +-- + +DROP TABLE IF EXISTS `settings`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `settings` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `language` varchar(8) COLLATE utf8_unicode_ci DEFAULT NULL, + `key` varchar(64) COLLATE utf8_unicode_ci NOT NULL COMMENT 'User readable key for accessing value of this config. Config will be available under this key also in views.', + `value` text COLLATE utf8_unicode_ci NOT NULL COMMENT 'Value of config.', + `load_into_views` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Use in views directly?', + PRIMARY KEY (`id`), + UNIQUE KEY `settings_key_language_unique` (`key`,`language`), + KEY `settings_load_into_views_index` (`load_into_views`), + KEY `settings_language_foreign` (`language`), + CONSTRAINT `settings_language_foreign` FOREIGN KEY (`language`) REFERENCES `languages` (`code`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=59 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `template_paths` +-- + +DROP TABLE IF EXISTS `template_paths`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `template_paths` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `parent_id` int(11) DEFAULT NULL COMMENT 'ID of parent directory', + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `path` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Path on filesystem', + `namespace` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'Namespace used for all templates in this path', + `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Human readable name', + PRIMARY KEY (`id`), + KEY `template_paths_parent_id_index` (`parent_id`), + CONSTRAINT `template_paths_parent_id_foreign` FOREIGN KEY (`parent_id`) REFERENCES `template_paths` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `templates` +-- + +DROP TABLE IF EXISTS `templates`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `templates` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `module_id` int(11) DEFAULT NULL COMMENT 'Template belongs to module specified with this ID.', + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `template_name` varchar(128) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Template name used for rendering page. Use namespace::dir.template format.', + `path` int(11) NOT NULL COMMENT 'ID of template directory', + `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Human readable name of given template', + `description` mediumtext COLLATE utf8_unicode_ci NOT NULL COMMENT 'Description of this template.', + `editable` tinyint(1) NOT NULL COMMENT 'Can this template be edited?', + PRIMARY KEY (`id`), + KEY `templates_template_name_index` (`template_name`), + KEY `templates_path_index` (`path`), + KEY `templates_module_id_index` (`module_id`), + CONSTRAINT `templates_module_id_foreign` FOREIGN KEY (`module_id`) REFERENCES `modules` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `templates_path_foreign` FOREIGN KEY (`path`) REFERENCES `template_paths` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=111 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `user_has_custom_field` +-- + +DROP TABLE IF EXISTS `user_has_custom_field`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `user_has_custom_field` ( + `user_id` int(11) NOT NULL, + `custom_field_id` int(11) NOT NULL, + `value` text COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`user_id`,`custom_field_id`), + KEY `fk_user_has_custom_field_custom_field1_idx` (`custom_field_id`), + KEY `fk_user_has_custom_field_user1_idx` (`user_id`), + CONSTRAINT `fk_user_has_custom_field_custom_field1` FOREIGN KEY (`custom_field_id`) REFERENCES `custom_fields` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_user_has_custom_field_user1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `users` +-- + +DROP TABLE IF EXISTS `users`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `users` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `email` varchar(64) COLLATE utf8_unicode_ci NOT NULL COMMENT 'E-mail is used as a contact and also as a username', + `username` varchar(64) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Username can be used also for login. Default value is users email.', + `password` varchar(128) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Password hash', + `role` enum('visitor','editor','admin') COLLATE utf8_unicode_ci NOT NULL DEFAULT 'visitor' COMMENT 'User''s role determines his permissions in the system.', + `remember_token` varchar(128) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'Token used for "remember me" feature', + `name` varchar(128) COLLATE utf8_unicode_ci DEFAULT NULL, + `status` enum('deleted','blocked','active') COLLATE utf8_unicode_ci NOT NULL DEFAULT 'active', + PRIMARY KEY (`id`), + UNIQUE KEY `email_UNIQUE` (`email`) +) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table with users who are allowed to log into system'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `widget_areas` +-- + +DROP TABLE IF EXISTS `widget_areas`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `widget_areas` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `name` varchar(64) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Used for user overview.', + `code` varchar(32) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Value of this field is used for replacing placeholder text in templates with content of real widget area.', + `description` text COLLATE utf8_unicode_ci, + `deletable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Can this widget area be deleted?', + PRIMARY KEY (`id`), + UNIQUE KEY `code_UNIQUE` (`code`) +) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Widget area is container for widgets. This container is inserted into templates/content and populated by widgets.'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `widget_in_widget_area` +-- + +DROP TABLE IF EXISTS `widget_in_widget_area`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `widget_in_widget_area` ( + `widget_id` int(11) NOT NULL, + `widget_area_id` int(11) NOT NULL, + `position` int(11) NOT NULL COMMENT 'Which position should the widget take?', + PRIMARY KEY (`widget_id`,`widget_area_id`), + KEY `fk_widget_has_widget_area_widget_area1_idx` (`widget_area_id`), + KEY `fk_widget_has_widget_area_widget1_idx` (`widget_id`), + KEY `position` (`position`), + CONSTRAINT `fk_widget_has_widget_area_widget1` FOREIGN KEY (`widget_id`) REFERENCES `widgets` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT `widget_in_widget_area_widget_area_id_foreign` FOREIGN KEY (`widget_area_id`) REFERENCES `widget_areas` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `widget_types` +-- + +DROP TABLE IF EXISTS `widget_types`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `widget_types` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `handler` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Class wich handles everything about this widget type', + `template_id` int(11) DEFAULT NULL COMMENT 'Commes this widget with template?', + `module_id` int(11) DEFAULT NULL COMMENT 'Commes this widget with some module?', + `name` varchar(64) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Name is used only in admin for better readability.', + `description` text COLLATE utf8_unicode_ci COMMENT 'This field also improves readability.', + PRIMARY KEY (`id`), + UNIQUE KEY `widget_types_handler_unique` (`handler`), + KEY `widget_types_template_id_foreign` (`template_id`), + KEY `widget_types_module_id_foreign` (`module_id`), + CONSTRAINT `widget_types_module_id_foreign` FOREIGN KEY (`module_id`) REFERENCES `modules` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `widget_types_template_id_foreign` FOREIGN KEY (`template_id`) REFERENCES `modules` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Widget type table contains information about all installed widgets. This is something like "class" to widgets.'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `widgets` +-- + +DROP TABLE IF EXISTS `widgets`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `widgets` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `widget_type_handler` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `language` varchar(8) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'cs', + `user_id` int(11) NOT NULL COMMENT 'User ID who created this widget', + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `name` varchar(64) COLLATE utf8_unicode_ci NOT NULL, + `settings` text COLLATE utf8_unicode_ci NOT NULL COMMENT 'JSON serialized values used for initializing given widget.', + `description` mediumtext COLLATE utf8_unicode_ci, + `deletable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Can this widget be deleted?', + PRIMARY KEY (`id`), + KEY `fk_widget_user1_idx` (`user_id`), + KEY `fk_widget_widget_type1_idx` (`widget_type_handler`), + KEY `widgets_language_foreign` (`language`), + CONSTRAINT `fk_widget_user1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT `widgets_language_foreign` FOREIGN KEY (`language`) REFERENCES `languages` (`code`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `widgets_widget_type_handler_foreign` FOREIGN KEY (`widget_type_handler`) REFERENCES `widget_types` (`handler`) ON DELETE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Widget is a small piece of content, which can be inserted into widget area. Example of widgets: menu (with links), banner, some static html code. This table contains real "instances" of widgets.'; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; --- ----------------------------------------------------- --- Table `content_has_custom_fields` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `content_has_custom_fields` ( - `contents_id` INT NOT NULL, - `custom_field_id` INT NOT NULL, - `value` TEXT NULL, - PRIMARY KEY (`contents_id`, `custom_field_id`), - INDEX `fk_contents_has_custom_fields_custom_fields1_idx` (`custom_field_id` ASC), - INDEX `fk_contents_has_custom_fields_contents1_idx` (`contents_id` ASC), - CONSTRAINT `fk_contents_has_custom_fields_contents1` - FOREIGN KEY (`contents_id`) - REFERENCES `contents` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION, - CONSTRAINT `fk_contents_has_custom_fields_custom_fields1` - FOREIGN KEY (`custom_field_id`) - REFERENCES `custom_fields` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION) -ENGINE = InnoDB; + +INSERT INTO `languages` (`code`, `name`, `script`, `native`) VALUES + ('cs', 'common.lang.czech', 'Latn', 'Cestina'), + ('en', 'common.lang.english', 'Latn', 'English'); + +INSERT INTO `migrations` (`migration`, `batch`) VALUES + ('2014_10_12_100000_create_password_resets_table', 1), + ('2016_01_28_213204_AddStringIdForActionTypes', 2), + ('2016_02_08_105722_AddExtendedFileInfo', 3), + ('2016_02_08_134354_AddFileSizeTofiles', 4), + ('2016_02_08_234544_FixAutoIncrementForWidgetAreas', 5), + ('2016_02_14_142507_AddIdToWidgetTypes', 6), + ('2016_02_15_155126_AddHomepageMark', 7), + ('2016_02_17_131804_AddTemplateSupport', 8), + ('2016_02_18_100743_CreateTemplateDirectoriesTable', 9), + ('2016_02_18_122645_RemoveModuleIdFromTemplates', 10), + ('2016_02_18_132248_AddNameAndParentIdToTemplatePaths', 11), + ('2016_02_18_141703_AddNamespaceToTemplatePaths', 12), + ('2016_03_08_100656_AddModuleIdToTemplatesAgain', 13), + ('2016_03_13_131109_AddFileFirectoriesSupport', 14), + ('2016_03_13_225224_CreateSettingsTable', 15), + ('2016_03_14_000228_ExtendModulesInfo', 16), + ('2016_03_14_102343_AddModuleSettings', 17), + ('2016_03_31_175822_AddSubpages', 18), + ('2016_03_31_192238_ChangeContentTitleImageIdForFilename', 19), + ('2016_03_31_214730_MakeUserIdNotMandatoryInDirectories', 20), + ('2016_03_31_225203_AddNameForFiles', 21), + ('2016_04_02_233316_AddIpAndUserAgentIntoLog', 22), + ('2016_07_21_205626_MakeLangEnumerations', 23), + ('2016_07_21_221547_AddFieldsIntoLanguages', 24), + ('2016_07_22_095923_AddLanguageToContentPages', 25), + ('2016_07_22_130324_AddLanguageIntoWidgets', 26), + ('2016_07_22_145402_AddLanguageIntoSettings', 27), + ('2016_07_28_192556_assignFilesToPages', 28), + ('2016_08_01_211830_ChangeCollationOfTables', 29), + ('2016_08_08_204055_AddFKToContenctAndTemplates', 30), + ('2016_08_08_223700_MarkContentManagementModules', 31), + ('2016_08_08_225613_MarkCategorizableModules', 31), + ('2016_08_10_204045_create_categories_table', 32), + ('2016_08_10_204046_create_category_closures_table', 32), + ('2016_08_13_150322_AddCategorySupport', 33), + ('2016_08_14_165211_MakeModuleHandlerNullable', 34), + ('2016_08_15_183157_AddParentModuleIdToModules', 35), + ('2016_09_05_085644_create_comments', 36), + ('2016_09_19_192132_SaveTemplatesIntoModulesTable', 37), + ('2016_09_19_225527_AddNumberOfViewsOfPage', 38), + ('2016_09_20_113806_AddTemplateSupportToWidgets', 39), + ('2016_09_20_114930_MakeWidgetsAndWidgetAreasNotDeletable', 40), + ('2016_09_20_122700_AddTemplateIdIntoTemplates', 41); -SET SQL_MODE=@OLD_SQL_MODE; + + + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; -SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2016-09-20 21:58:09 diff --git a/modules/Blog/Article/Entities/Comment.php b/modules/Blog/Article/Entities/Comment.php new file mode 100644 index 00000000..4b42d711 --- /dev/null +++ b/modules/Blog/Article/Entities/Comment.php @@ -0,0 +1,26 @@ +belongsTo(User::class, 'user_id'); + } + + public function article() + { + return $this->belongsTo(Content::class, 'content_id'); + } +} \ No newline at end of file diff --git a/modules/Blog/Article/Events/ApprovedComment.php b/modules/Blog/Article/Events/ApprovedComment.php new file mode 100644 index 00000000..3fab72bc --- /dev/null +++ b/modules/Blog/Article/Events/ApprovedComment.php @@ -0,0 +1,20 @@ +comment = $comment; + } +} \ No newline at end of file diff --git a/modules/Blog/Article/Events/CreatedComment.php b/modules/Blog/Article/Events/CreatedComment.php new file mode 100644 index 00000000..d21f685b --- /dev/null +++ b/modules/Blog/Article/Events/CreatedComment.php @@ -0,0 +1,20 @@ +comment = $comment; + } +} \ No newline at end of file diff --git a/modules/Blog/Article/Events/DeletedComment.php b/modules/Blog/Article/Events/DeletedComment.php new file mode 100644 index 00000000..048341ab --- /dev/null +++ b/modules/Blog/Article/Events/DeletedComment.php @@ -0,0 +1,20 @@ +comment = $comment; + } +} \ No newline at end of file diff --git a/modules/Blog/Article/Handlers/Article.php b/modules/Blog/Article/Handlers/Article.php index 90d0f39d..bc5481b9 100644 --- a/modules/Blog/Article/Handlers/Article.php +++ b/modules/Blog/Article/Handlers/Article.php @@ -2,14 +2,17 @@ namespace Modules\Blog\Article\Handlers; use App\Enumerations\ModulesHandlerToId; +use App\Handlers\Contracts\ContentTitles; use App\Handlers\Handler; -use App\Http\Controllers\Admin\EditContent; +use App\Http\Controllers\Admin\Contents\EditContent; use App\Http\Controllers\Controller; use App\Http\Requests\Request; +use App\Models\ActionLogType; use App\Models\Content; use App\Models\User; +use Modules\Blog\Article\Entities\Comment; -class Article extends Handler +class Article extends Handler implements ContentTitles { /** * Render given module into visitor area. @@ -21,7 +24,15 @@ class Article extends Handler */ public function render(Content $content, $parameters) { - return Controller::VisitorsView($content->template, ['page' => $content]); + $content->comments = []; + if (isset($content->module_settings->allowComments) && $content->module_settings->allowComments) { + $content->comments = Comment::where('content_id', $content->id) + ->where('approved', 1) + ->orderBy('created_at') + ->get(); + } + + return Controller::VisitorsView($content->template, ['page' => $content, 'extra' => $parameters]); } public static function getExtendedFormInputsCreate() @@ -80,6 +91,7 @@ class Article extends Handler } /** + * Register admin menu * * @param User $user */ @@ -89,10 +101,20 @@ class Article extends Handler $menu = \Menu::instance('mainMenu'); - $menu->dropdown(trans('article::module.name'), function ($sub) { + $blogSubmenu = $menu->whereTitle(trans('article::module.blog')); + if (!$blogSubmenu) { + $blogSubmenu = $menu->dropdown(trans('article::module.blog'), function ($sub) { + }, 20, ['icon' => 'fa fa-fw fa-book']); + } + + $blogSubmenu->dropdown(trans('article::module.name'), function ($sub) { $sub->route('content.new', trans('article::article.new'), ['id' => self::getModuleId()], 10, ['icon' => 'fa fa-fw fa-pencil']); $sub->route('content.overview', trans('article::article.overview'), ['id' => self::getModuleId()], 30, ['icon' => 'fa fa-fw fa-book']); }, 10, ['icon' => 'fa fa-fw fa-book']); + + $blogSubmenu->dropdown(trans('article::module.comments'), function ($sub) { + $sub->route('comments.overview', trans('article::module.commentsOverview'), [], 10, ['icon' => 'fa fa-fw fa-commenting-o']); + }, 20, ['icon' => 'fa fa-fw fa-comments-o']); } public static function getModuleId() @@ -106,10 +128,63 @@ class Article extends Handler /** * Install module - * - * @return mixed */ public static function install() { + $types = [ + [ + 'string_id' => 'commentCreated', + 'name' => 'article::comments.events.names.commentCreated', + 'value' => 'article::comments.events.descriptions.commentCreated', + ], + [ + 'string_id' => 'commentDeleted', + 'name' => 'article::comments.events.names.commentDeleted', + 'value' => 'article::comments.events.descriptions.commentDeleted', + ], + [ + 'string_id' => 'commentApproved', + 'name' => 'article::comments.events.names.commentApproved', + 'value' => 'article::comments.events.descriptions.commentApproved', + ], + + ]; + foreach ($types as $type) { + ActionLogType::create($type); + } + } + + /** + * Get translated title for pages overview of given type. + * + * This value is used for rendering list of all content pages of given type in administration. + * + * @return string + */ + public static function getOverviewTitle() + { + return trans('article::module.allArticles'); + } + + /** + * Title displayed above for for creating new content page + * + * @return string + */ + public static function getCreateNewTitle() + { + return trans('article::module.newArticle'); + } + + /** + * Title for form displayed for editin content page + * + * @param Content $content + * + * @return mixed + */ + public static function getEditTitle(Content $content) + { + return trans('article::module.editArticle'); } } \ No newline at end of file diff --git a/modules/Blog/Article/Http/Controllers/Admin/CommentsOverview.php b/modules/Blog/Article/Http/Controllers/Admin/CommentsOverview.php new file mode 100644 index 00000000..9293d2b1 --- /dev/null +++ b/modules/Blog/Article/Http/Controllers/Admin/CommentsOverview.php @@ -0,0 +1,19 @@ +with('author') + ->orderBy('approved') + ->orderBy('created_at', 'DESC') + ->paginate(25); + + return view('article::admin/comments.overview', ['comments' => $comments]); + } +} \ No newline at end of file diff --git a/modules/Blog/Article/Http/Controllers/Admin/EditComment.php b/modules/Blog/Article/Http/Controllers/Admin/EditComment.php new file mode 100644 index 00000000..969f845d --- /dev/null +++ b/modules/Blog/Article/Http/Controllers/Admin/EditComment.php @@ -0,0 +1,37 @@ +approved = 1; + $comment->save(); + + Event::fire(new ApprovedComment($comment)); + + return redirect() + ->back() + ->with('success', trans('article::comments.approved')); + } + + public function delete($id) + { + $comment = Comment::findOrFail($id); + + Event::fire(new DeletedComment($comment)); + + $comment->delete(); + + return redirect() + ->back() + ->with('success', trans('article::comments.deleted')); + } +} \ No newline at end of file diff --git a/modules/Blog/Article/Http/Controllers/Visitor/CreateNewComment.php b/modules/Blog/Article/Http/Controllers/Visitor/CreateNewComment.php new file mode 100644 index 00000000..7c68891a --- /dev/null +++ b/modules/Blog/Article/Http/Controllers/Visitor/CreateNewComment.php @@ -0,0 +1,53 @@ +where('module_handler', Article::class) + ->firstOrFail(); + + $article->module_settings = json_decode($article->module_settings); + + if (!isset($article->module_settings->allowComments) || !$article->module_settings->allowComments) { + return redirect() + ->route('page.content', ['url' => $article->url]) + ->with('error', trans('article::comments.disabledComments')); + } + + $content = $request->message; + $user = Auth::user(); + if ($user) { + $name = $user->name; + $email = $user->email; + } else { + $name = $request->name; + $email = $request->email; + } + + $comment = Comment::create([ + 'user_id' => $user ? $user->id : null, + 'content_id' => $article->id, + 'name' => $name, + 'email' => $email, + 'content' => $content + ]); + + // fire event about new comment + Event::fire(new CreatedComment($comment)); + + return redirect() + ->route('page.content', ['url' => $article->url]) + ->with('success', trans('article::comments.saved')); + } +} \ No newline at end of file diff --git a/modules/Blog/Article/Http/Requests/CreateNewComment.php b/modules/Blog/Article/Http/Requests/CreateNewComment.php new file mode 100644 index 00000000..18044f43 --- /dev/null +++ b/modules/Blog/Article/Http/Requests/CreateNewComment.php @@ -0,0 +1,28 @@ + 'required']; + + if (!$user) { + $required['name'] = 'required'; + $required['email'] = 'required|email'; + } + + return $required; + } +} diff --git a/modules/Blog/Article/Http/routes.php b/modules/Blog/Article/Http/routes.php index b9ad36f4..9bb13c27 100644 --- a/modules/Blog/Article/Http/routes.php +++ b/modules/Blog/Article/Http/routes.php @@ -1,25 +1,28 @@ 'web', - 'prefix' => 'article', - 'namespace' => 'Modules\Blog\Article\Http\Controllers' + 'prefix' => LaravelLocalization::setLocale(), + 'middleware' => ['localeSessionRedirect', 'localizationRedirect'], + 'namespace' => 'Modules\Blog\Article\Http\Controllers\Admin' ], function () { - Route::get('/', 'CategoriesOverview@getOverview') - ->name('article.overview'); + Route::group([ + 'middleware' => ['web', 'auth', 'adminMenu'], + 'prefix' => 'admin/comments', + ], function () { - Route::get('/new', 'CreateNewCategory@getNew') - ->name('article.new'); + Route::get('/', 'CommentsOverview@getOverview') + ->name('comments.overview'); - Route::post('/new', 'CreateNewCategory@postNew') - ->name('article.postNew'); + Route::get('/delete/{id}', 'EditComment@delete') + ->name('comments.delete'); - Route::get('/edit/{id}', 'EditCategory@getEdit') - ->name('article.edit'); + Route::get('/approve/{id}', 'EditComment@approve') + ->name('comments.approve'); + }); - Route::post('/edit/{id}', 'EditCategory@postEdit') - ->name('article.postEdit'); + // public routes + Route::group(['middleware' => ['web']], function () { - Route::get('/delete/{id}', 'EditCategory@delete') - ->name('article.delete'); + Route::post('comments/new/{id}', 'CreateNewComment@postNew') + ->name('comments.new'); + }); }); \ No newline at end of file diff --git a/modules/Blog/Article/Listeners/LogCommentApproval.php b/modules/Blog/Article/Listeners/LogCommentApproval.php new file mode 100644 index 00000000..24a9e049 --- /dev/null +++ b/modules/Blog/Article/Listeners/LogCommentApproval.php @@ -0,0 +1,35 @@ + $event->comment->name, + 'email' => $event->comment->email, + 'content' => $event->comment->content + ]; + + $this->saveLog($parameters); + } + + /** + * Getter for action name. Used only internally + * + * @return mixed + */ + protected function getName() + { + return 'commentApproved'; + } +} \ No newline at end of file diff --git a/modules/Blog/Article/Listeners/LogDeletedComment.php b/modules/Blog/Article/Listeners/LogDeletedComment.php new file mode 100644 index 00000000..d4cb97ea --- /dev/null +++ b/modules/Blog/Article/Listeners/LogDeletedComment.php @@ -0,0 +1,36 @@ + $event->comment->name, + 'email' => $event->comment->email, + 'content' => $event->comment->content + ]; + + $this->saveLog($parameters); + } + + /** + * Getter for action name. Used only internally + * + * @return mixed + */ + protected function getName() + { + return 'commentDeleted'; + } +} \ No newline at end of file diff --git a/modules/Blog/Article/Listeners/LogNewComment.php b/modules/Blog/Article/Listeners/LogNewComment.php new file mode 100644 index 00000000..672a5748 --- /dev/null +++ b/modules/Blog/Article/Listeners/LogNewComment.php @@ -0,0 +1,39 @@ +comment->user_id) + return; + + $parameters = [ + 'author' => $event->comment->name, + 'email' => $event->comment->email, + 'content' => $event->comment->content + ]; + + $this->saveLog($parameters); + } + + /** + * Getter for action name. Used only internally + * + * @return mixed + */ + protected function getName() + { + return 'commentCreated'; + } +} \ No newline at end of file diff --git a/modules/Blog/Article/Providers/ArticleEventServiceProvider.php b/modules/Blog/Article/Providers/ArticleEventServiceProvider.php new file mode 100644 index 00000000..a1eaa128 --- /dev/null +++ b/modules/Blog/Article/Providers/ArticleEventServiceProvider.php @@ -0,0 +1,30 @@ + [LogCommentApproval::class], + DeletedComment::class => [LogDeletedComment::class], + CreatedComment::class => [LogNewComment::class], + ]; + + public function boot(DispatcherContract $events) + { + parent::boot($events); // TODO: Change the autogenerated stub + } +} \ No newline at end of file diff --git a/modules/Blog/Article/Providers/ArticleServiceProvider.php b/modules/Blog/Article/Providers/ArticleServiceProvider.php index 0d52f03c..188e2fef 100644 --- a/modules/Blog/Article/Providers/ArticleServiceProvider.php +++ b/modules/Blog/Article/Providers/ArticleServiceProvider.php @@ -25,6 +25,8 @@ class ArticleServiceProvider extends ServiceProvider { $this->registerTranslations(); $this->registerConfig(); $this->registerViews(); + + $this->app->register(ArticleEventServiceProvider::class); } /** @@ -57,27 +59,11 @@ class ArticleServiceProvider extends ServiceProvider { $viewPath = base_path('resources/views/modules/article'); $sourcePath = __DIR__.'/../Resources/views'; - $this->publishes([$sourcePath => $viewPath]); + $this->publishes([$sourcePath => $viewPath], 'module-views'); $this->loadViewsFrom(array_merge(array_map(function ($path) { return $path . '/modules/article'; }, \Config::get('view.paths')), [$sourcePath]), 'article'); - - /** - * @var $registrator TemplateRegistrator - */ - try { - // setup some global variables - $registrator = $this->app->make('registerTemplateDir'); - $registrator->setBaseDirectory('resources/views/modules/article/visitor'); - $registrator->setNamespace('article'); - $registrator->setModuleId(Article::getModuleId()); - - // register path with current template name - $registrator->register(config('app.theme'), 'article::admin/adminMenu.templatePath'); - } catch (InvalidArgumentException $e) { - // ignore this exception - } } /** diff --git a/modules/Blog/Article/Resources/lang/cs/article.php b/modules/Blog/Article/Resources/lang/cs/article.php new file mode 100644 index 00000000..fd1c65c6 --- /dev/null +++ b/modules/Blog/Article/Resources/lang/cs/article.php @@ -0,0 +1,6 @@ + "Povolit komentáře?", +"new" => "Napsat", +"overview" => "Všechny články", +]; diff --git a/modules/Blog/Article/Resources/lang/cs/comments.php b/modules/Blog/Article/Resources/lang/cs/comments.php new file mode 100644 index 00000000..12062efe --- /dev/null +++ b/modules/Blog/Article/Resources/lang/cs/comments.php @@ -0,0 +1,8 @@ + "Schválit komentář a zobrazovat ho u článku", +"approved" => "Komentář úspěšně schválen", +"commentedArticle" => "Komentovaný článek", +"createdAt" => "Vloženo :date", +"deleted" => "Komentář smazán", +]; diff --git a/modules/Blog/Article/Resources/lang/cs/module.php b/modules/Blog/Article/Resources/lang/cs/module.php new file mode 100644 index 00000000..f000fdb7 --- /dev/null +++ b/modules/Blog/Article/Resources/lang/cs/module.php @@ -0,0 +1,11 @@ + "Všechny články", +"blog" => "Blog", +"comments" => "Komentáře", +"commentsOverview" => "Všechny komentáře", +"description" => "Základní submodul pro blog. Tento modul umožňuje psát články.", +"editArticle" => "Upravuji článek", +"name" => "Články", +"newArticle" => "Napsat nový článek", +]; diff --git a/modules/Blog/Article/Resources/lang/en/comments.php b/modules/Blog/Article/Resources/lang/en/comments.php new file mode 100644 index 00000000..9d8818cd --- /dev/null +++ b/modules/Blog/Article/Resources/lang/en/comments.php @@ -0,0 +1,21 @@ + 'Created at :date', + 'commentedArticle' => 'Article', + 'approve' => 'Approve comment', + 'approved' => 'Comment successfully approved', + 'deleted' => 'Comment deleted', + 'noComments' => 'No comments available', + 'events' => [ + 'names' => [ + 'commentCreated' => 'Added comment', + 'commentApproved' => 'Approved comment', + 'commentDeleted' => 'Deleted comment', + ], + 'descriptions' => [ + 'commentCreated' => 'Added comment from author :author with e-mail :email and following content: :content', + 'commentApproved' => 'Approved comment from author :author with e-mail :email and following content: :content', + 'commentDeleted' => 'Deleted comment from author :author with e-mail :email and following content: :content', + ], + ] +]; \ No newline at end of file diff --git a/modules/Blog/Article/Resources/lang/en/module.php b/modules/Blog/Article/Resources/lang/en/module.php index a448c978..8b9f7e18 100644 --- a/modules/Blog/Article/Resources/lang/en/module.php +++ b/modules/Blog/Article/Resources/lang/en/module.php @@ -1,5 +1,11 @@ 'Article', - 'description' => 'Base submodule for blog' + 'name' => 'Articles', + 'description' => 'Base submodule for blog', + 'comments' => 'Comments', + 'commentsOverview' => 'All comments', + 'blog' => 'Blog', + 'allArticles' => 'All articles', + 'newArticle' => 'Write new article', + 'editArticle' => 'Editing article' ]; \ No newline at end of file diff --git a/modules/Blog/Article/Resources/views/admin/comments/overview.blade.php b/modules/Blog/Article/Resources/views/admin/comments/overview.blade.php new file mode 100644 index 00000000..61a8c751 --- /dev/null +++ b/modules/Blog/Article/Resources/views/admin/comments/overview.blade.php @@ -0,0 +1,47 @@ +@extends('admin.layoutWithNavigation') + +@section('pageHeading', trans('article::module.comments')) + +@section('content') + @if (count($comments)) + @foreach($comments as $comment) + +
+
+
+

+ + {{$comment->author->name}} ({{$comment->author->email}}) +

+
+
+
+

+ {{$comment->content}} +

+
+ +
+ @endforeach + @else +
+ @lang('article::comments.noComments') +
+ @endif +@endsection \ No newline at end of file diff --git a/modules/Blog/Article/Resources/views/admin/content/basicInfo.blade.php b/modules/Blog/Article/Resources/views/admin/content/basicInfo.blade.php index e150c58f..ec02b51e 100644 --- a/modules/Blog/Article/Resources/views/admin/content/basicInfo.blade.php +++ b/modules/Blog/Article/Resources/views/admin/content/basicInfo.blade.php @@ -1,89 +1,89 @@
-
-