diff --git a/Plugin.php b/Plugin.php index 18a1480..ec83915 100644 --- a/Plugin.php +++ b/Plugin.php @@ -65,7 +65,7 @@ public function boot() { * If Blog plugin exists but there is no custom_repeater column, create it * Mostly because Rainlab Blog plugin was installed after Small Extensions */ - if (Schema::hasTable('rainlab_blog_posts') and !Schema::hasColumn('rainlab_blog_posts', 'custom_repeater')) + if (Schema::hasTable('rainlab_blog_posts') and !Schema::hasColumn('rainlab_blog_posts', 'custom_repeater')) { Schema::table('rainlab_blog_posts', function($table) { @@ -145,21 +145,21 @@ public function boot() { if(Settings::get('custom_repeater_allow', null) and Settings::get('custom_repeater_fields', null)) { - + $fields = Settings::get('custom_repeater_fields', []); - + $columnTypesMap = [ 'number' => 'number', 'datepicker' => 'date', ]; - foreach($fields as $field) + foreach($fields as $field) { $columns = []; $fieldType = 'sme_json_field'; - if(isset($field['custom_repeater_field_type']) + if(isset($field['custom_repeater_field_type']) and $field['custom_repeater_field_type'] and $field['custom_repeater_field_type'] != 'section') { @@ -251,11 +251,11 @@ public function boot() { // Check for Rainlab.User plugin $pluginManagerUser = PluginManager::instance()->findByIdentifier('Rainlab.User'); - if ( ($pluginManager && !$pluginManager->disabled) and + if ( ($pluginManager && !$pluginManager->disabled) and ($pluginManagerUser && !$pluginManagerUser->disabled) ){ \RainLab\Blog\Models\Post::extend(function($model) { - + $usersFormated = []; if( Settings::get('blog_rainlab_user') ) { @@ -266,12 +266,12 @@ public function boot() { $usersFormated[$user->id] = ($user->surname . ' ' . $user->name); } - } - + } + $model->addDynamicMethod('listRainlabUsers', function() use($usersFormated) { return $usersFormated; }); - + }); @@ -514,8 +514,8 @@ public function boot() { * Check the Rainlab.Translate plugin is installed */ $pluginManager = PluginManager::instance()->findByIdentifier('Rainlab.Translate'); - - if ($pluginManager && !$pluginManager->disabled) + + if ($pluginManager && !$pluginManager->disabled) { $fields['custom_fields[api_code]']['type'] = 'text'; } else { @@ -693,7 +693,7 @@ public function boot() { 'tab' => 'rainlab.blog::lang.post.tab_manage' ]; - if(empty(Settings::get('blog_featured_image_both', null))) + if(empty(Settings::get('blog_featured_image_both', null))) { $widget->removeField('featured_images'); } @@ -703,7 +703,7 @@ public function boot() { 'custom_fields[featured_image]' => $featuredImage, ]); - if(Settings::get('blog_featured_image_meta')) + if(Settings::get('blog_featured_image_meta')) { $widget->addSecondaryTabFields([ 'custom_fields[featured_image_title]' => $featuredImageTitle, @@ -787,14 +787,14 @@ public function boot() { /** * Custom repeater builder (new repeater) */ - if(Settings::get('custom_repeater_allow', null) and Settings::get('custom_repeater_fields', null)) + if(Settings::get('custom_repeater_allow', null) and Settings::get('custom_repeater_fields', null)) { $fields = []; $counter = 0; foreach(Settings::get('custom_repeater_fields', null) as $field) { - + if(empty($field['custom_repeater_field_name'])) { $fieldName = 'field' . $counter; } else { @@ -807,7 +807,7 @@ public function boot() { { $fieldName = 'custom_repeater['.$field['custom_repeater_field_name'].']'; } - else + else { $fieldName = 'custom_repeater['.$counter.']'; } @@ -822,9 +822,9 @@ public function boot() { ]; - if(!empty($field['custom_repeater_field_attributes'])) + if(!empty($field['custom_repeater_field_attributes'])) { - foreach($field['custom_repeater_field_attributes'] as $value) + foreach($field['custom_repeater_field_attributes'] as $value) { $fields[$fieldName][$value['attribute_name']] = $value['attribute_value']; } @@ -840,14 +840,14 @@ public function boot() { $options = []; - if(!empty($field['custom_repeater_field_options'])) + if(!empty($field['custom_repeater_field_options'])) { - foreach($field['custom_repeater_field_options'] as $value) + foreach($field['custom_repeater_field_options'] as $value) { $options[$value['option_key']] = $value['option_value']; } } - + $fields[$fieldName]['options'] = $options; } @@ -892,7 +892,7 @@ public function boot() { /** * Repeater fields (old repeater) */ - if(Settings::get('blog_custom_fields_repeater')) + if(Settings::get('blog_custom_fields_repeater')) { $repeaterFields = []; @@ -1016,7 +1016,7 @@ public function boot() { /** * Blog preview btn */ - if(Settings::get('blog_add_preview_btn') and $widget->model->slug) + if(Settings::get('blog_add_preview_btn') and $widget->model->slug) { $widget->addFields([ 'blog_add_preview_btn' => [ @@ -1200,7 +1200,7 @@ public function boot() { } - if (Settings::get('allow_grouprepeater_titlefrom')) + if (Settings::get('allow_grouprepeater_titlefrom')) { // If Rainlab.Translate is not present, bypass translate filters $pluginManager = PluginManager::instance()->findByIdentifier('Rainlab.Translate'); @@ -1210,7 +1210,7 @@ public function boot() { $widget->addViewPath(plugins_path().'/janvince/smallextensions/formwidgets/repeater/partials'); }); - if ($pluginManager and !$pluginManager->disabled) + if ($pluginManager and !$pluginManager->disabled) { // MLRepeater has hardcoded viewPath, so this is only simple workaround // This looks for a first input and get its value. Can't access titleFrom @@ -1221,6 +1221,17 @@ public function boot() { }); } } + + // RainLab.Pages Duplicating tool + if (Settings::get('static_pages_enable_duplicating')) + { + if (PluginManager::instance()->hasPlugin('RainLab.Pages') && !PluginManager::instance()->isDisabled('RainLab.Pages')) + { + \RainLab\Pages\Controllers\Index::extend(function ($controller) { + $controller->implement[] = 'JanVince.SmallExtensions.Classes.Behaviors.StaticPageCloneController'; + }); + } + } } public function registerSettings() { @@ -1284,7 +1295,7 @@ public function registerMarkupTags() $pluginManager = PluginManager::instance()->findByIdentifier('Rainlab.Translate'); if (!$pluginManager or ($pluginManager and $pluginManager->disabled)) { - + $twigExtensions['filters'] = [ '_' => ['Lang', 'get'], @@ -1306,22 +1317,22 @@ public function registerMarkupTags() public function twigFilterTruncate($text, $length, $preserveWords = false, $separator = '...') { - if (mb_strlen($text) <= $length) + if (mb_strlen($text) <= $length) { return $text; } $text = $text." "; - + $text = mb_substr($text, 0, $length); - + if($preserveWords) { $text = mb_substr($text, 0, mb_strrpos($text,' ')); } - + $text = $text.$separator; - + return $text; } @@ -1358,14 +1369,14 @@ public function registerListColumnTypes() 'sme_image_preview' => function($value) { if($value){ return ""; } }, - 'sme_json_field' => function($value, $column, $record) - { + 'sme_json_field' => function($value, $column, $record) + { $values = []; if(is_array($record->custom_repeater) and isset($column->config['repeaterValue'])) { foreach($record->custom_repeater as $field) - + if(isset($field[$column->config['repeaterValue']])) { $values[] = $field[$column->config['repeaterValue']]; @@ -1378,7 +1389,7 @@ public function registerListColumnTypes() } public function registerFormWidgets() { - + return [ 'JanVince\SmallExtensions\FormWidgets\PostPreview' => 'postpreview', ]; diff --git a/README.md b/README.md index 08908cd..f099379 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,10 @@ Rainlab Blog is a great plugin, but none of my clients is happy with MarkDown sy * If on, new tab Notes and a field Note is added to Menu items editing popup window. * text is then accessible from page/layout from {{item.viewBag.note}}. +* **Enable Page duplicating** + + * If on, Static Pages can be duplicated by backend user. + ## System Extension > *OctoberCMS > Backend > Settings > Small Extensions > System* @@ -89,7 +93,7 @@ Rainlab Blog is a great plugin, but none of my clients is happy with MarkDown sy * If Rainlab Translate plugin is not present, bypass trans and choice functions * **truncate** - * {{ text|truncate(20, true, '*') }} + * {{ text|truncate(20, true, '*') }} * length, preserve words (default false), separator (default is '...') ## Report Widgets @@ -121,9 +125,9 @@ Useful when you need to limit access to several pages or whole site to only admi ---- -> My special thanks goes to: -> [OctoberCMS](http://www.octobercms.com) team members and supporters for this great system. -> [Joel kyber](https://unsplash.com/@jtkyber1) for his photo I have used in the plugin banner. +> My special thanks goes to: +> [OctoberCMS](http://www.octobercms.com) team members and supporters for this great system. +> [Joel kyber](https://unsplash.com/@jtkyber1) for his photo I have used in the plugin banner. > [Font Awesome](http://www.fontawesome.io) for Universal access symbol. diff --git a/classes/behaviors/StaticPageCloneController.php b/classes/behaviors/StaticPageCloneController.php new file mode 100644 index 0000000..a5e0950 --- /dev/null +++ b/classes/behaviors/StaticPageCloneController.php @@ -0,0 +1,87 @@ +controller = $controller; + $this->controller->addJs('/plugins/janvince/smallextensions/classes/behaviors/staticpageclonecontroller/assets/js/staticpageclone.js'); + + Event::listen('backend.form.extendFields', function (Form $widget) { + if (!$widget->model instanceof Page) { + return; + } + if ($widget->isNested) { + return; + } + $widget->addFields(['custom_toolbar' => [ + 'type' => 'partial', + 'path' => '$/janvince/smallextensions/classes/behaviors/staticpageclonecontroller/partials/_page_toolbar.htm', + 'cssClass' => 'collapse-visible', + ]]); + }); + + } + + public function index_onDuplicate() + { + $type = Request::input('objectType'); + if ($type != 'page') { + throw new ApplicationException(trans('rainlab.pages::lang.object.unauthorized_type', ['type' => $type])); + } + + $objectPath = trim(Request::input('objectPath')); + if (($object = call_user_func([Page::class, 'load'], Theme::getEditTheme(), $objectPath)) === false) { + throw new ApplicationException(trans('rainlab.pages::lang.object.not_found')); + } + + // Save it at first and remember return response + $response = $this->controller->onSave(); + + // Try to find free URL/objectPath + $i = 0; + do { + $newObjectPath = $objectPath . '-' . ++$i; + } while (call_user_func([Page::class, 'load'], Theme::getEditTheme(), $newObjectPath)); + + // Rewrite values to force create new copy + $_POST['objectPath'] = null; + $_POST['viewBag']['url'] .= '-' . $i; + $_POST['parentFileName'] = $object->getParent() ? preg_replace('#\.htm$#i', '', $object->getParent()->getFileName()) : null; + Request::merge(array_only($_POST, ['objectPath', 'viewBag', 'parentFileName'])); + + // Dtto for RainLab Translate plugin + if (PluginManager::instance()->hasPlugin('RainLab.Translate') && !PluginManager::instance()->isDisabled('RainLab.Translate')) { + if (isset($_POST['RLTranslate']) && is_array($_POST['RLTranslate'])) { + foreach ($_POST['RLTranslate'] as &$rlTranslate) { + $rlTranslate['viewBag']['url'] .= '-' . $i; + } + Request::merge(array_only($_POST, ['RLTranslate'])); + } + } + + // Call original save with modified request + $this->controller->onSave(); + + // Return previous response + return $response; + } +} \ No newline at end of file diff --git a/classes/behaviors/staticpageclonecontroller/assets/js/staticpageclone.js b/classes/behaviors/staticpageclonecontroller/assets/js/staticpageclone.js new file mode 100644 index 0000000..a0a4a49 --- /dev/null +++ b/classes/behaviors/staticpageclonecontroller/assets/js/staticpageclone.js @@ -0,0 +1,17 @@ +function onDuplicatePage(el) { + var $el = $(el); + $el.request('onDuplicate', { + complete: function() { + $.oc.pagesPage.updateObjectList('page'); + } + }); +}; + ++function ($) { "use strict"; + $(document).on('ajaxSuccess', '#pages-master-tabs form', function (event, context, data) { + var $form = $(event.currentTarget); + if (data.objectPath !== undefined) { + $('[data-control=duplicate-button]', $form).removeClass('oc-hide hide') + } + }); +}(window.jQuery); \ No newline at end of file diff --git a/classes/behaviors/staticpageclonecontroller/partials/_page_toolbar.htm b/classes/behaviors/staticpageclonecontroller/partials/_page_toolbar.htm new file mode 100644 index 0000000..083269f --- /dev/null +++ b/classes/behaviors/staticpageclonecontroller/partials/_page_toolbar.htm @@ -0,0 +1,10 @@ +
+ +
diff --git a/lang/cs/lang.php b/lang/cs/lang.php index 06b65fb..7aaece7 100755 --- a/lang/cs/lang.php +++ b/lang/cs/lang.php @@ -23,6 +23,7 @@ 'categories' => 'Kategorie', 'deprecated' => 'Zastaralé', 'static_menu' => 'Rozšíření menu', + 'static_pages' => 'Rozšíření stránek', 'blog_editor' => 'Editor obsahu (zastaralé)', 'october_admins' => 'Administrátoři', 'twig' => 'Rozšíření pro Twig', @@ -65,6 +66,9 @@ 'enable_menu_color' => 'Povolit barvu u položky menu', 'enable_menu_color_description' => 'Umožní nastavit barvu položky.', + 'enable_duplicating' => 'Povolit duplikování stránek', + 'enable_duplicating_description' => 'Umožní duplikování statických stránek.', + 'enable_blog_author' => 'Umožnit změnu autora příspěvku', 'enable_blog_author_description' => 'Zobrazí seznam administrátorů pro výběr autora příspěvku. Původní (systémový) výběr bude skrytý.', @@ -113,7 +117,7 @@ 'custom_fields_api_code' => 'API kód', 'custom_fields_api_code_description' => '', - + 'enable_custom_fields_api_code' => 'Přidat pole pro Add API kód', 'enable_custom_fields_api_code_description' => 'Twig: {{post.custom_fields.api_code}}', @@ -331,14 +335,15 @@ ], 'components' => [ - 'force_login' => [ - 'name' => 'Vynutit přihlášení', 'description' => 'Povolit přístup na stránku pouze přihlášeným administrátorům', - ], + ], + 'staticpageclone' => [ + 'duplicate' => 'Duplikovat stránku', + 'duplicate_confirm' => 'Opravdu chcete tuto stránku zkopírovat?', ], ]; diff --git a/lang/en/lang.php b/lang/en/lang.php index 053529f..877fc7d 100755 --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -23,6 +23,7 @@ 'categories' => 'Categories', 'deprecated' => 'Deprecated', 'static_menu' => 'Menu extensions', + 'static_pages' => 'Pages extensions', 'blog_editor' => 'Content editor (deprecated)', 'october_admins' => 'Administrators', 'twig' => 'Twig extensions', @@ -54,7 +55,7 @@ 'enable_featured_image_upload_description' => 'Add option to upload one preview image.
Twig: {{post.featured_image.getPath}}', 'enable_featured_image_meta' => 'Add title and description to image', 'enable_featured_image_meta_description' => 'Twig: Title:{{ post.custom_fields.featured_image_title }}, Description: {{ post.custom_fields.featured_image_alt }}.', - + 'enable_featured_image_both' => 'Display both widgets', 'enable_featured_image_both_description' => 'Display both original images widget and a new Media selection', @@ -66,6 +67,9 @@ 'enable_menu_color' => 'Enable Menu color', 'enable_menu_color_description' => 'Allows to set color.', + 'enable_duplicating' => 'Enable Page duplicating', + 'enable_duplicating_description' => 'Allows to duplicate static pages.', + 'enable_blog_author' => 'Enable change of post author', 'enable_blog_author_description' => 'Dropdown with list of activated administrators will be added to blog post form (and the original dropdown will be removed)', @@ -281,7 +285,7 @@ ] ], ], - + 'author' => 'Author', ], 'blog' => [ @@ -339,14 +343,15 @@ ], 'components' => [ - 'force_login' => [ - 'name' => 'Force login', 'description' => 'Allow access to page to logged in administrators only', - ], + ], + 'staticpageclone' => [ + 'duplicate' => 'Duplicate page', + 'duplicate_confirm' => 'Do you really want to duplicate this page?', ], ]; diff --git a/models/settings/fields.yaml b/models/settings/fields.yaml index 0359abc..d079efb 100755 --- a/models/settings/fields.yaml +++ b/models/settings/fields.yaml @@ -2,7 +2,7 @@ fields: { } tabs: fields: - + # Rainlab.Blog editor_section: @@ -399,7 +399,7 @@ tabs: type: text span: right required: true - preset: + preset: field: custom_repeater_field_label type: camel custom_repeater_field_type: @@ -736,6 +736,19 @@ tabs: comment: 'janvince.smallextensions::lang.labels.enable_menu_color_description' tab: 'janvince.smallextensions::lang.tabs.pages' + pages_section: + type: section + label: 'janvince.smallextensions::lang.sections.static_pages' + tab: 'janvince.smallextensions::lang.tabs.pages' + + static_pages_enable_duplicating: + label: 'janvince.smallextensions::lang.labels.enable_duplicating' + span: full + default: false + type: checkbox + comment: 'janvince.smallextensions::lang.labels.enable_duplicating_description' + tab: 'janvince.smallextensions::lang.tabs.pages' + deprecated_section: type: section label: 'janvince.smallextensions::lang.sections.deprecated' diff --git a/updates/version.yaml b/updates/version.yaml index a904b75..cc9b88f 100644 --- a/updates/version.yaml +++ b/updates/version.yaml @@ -182,4 +182,7 @@ - WYSIWYG option for Blog is now deprecated 1.20.0: - Added meta_title and meta_description fields to Blog Categories - - smallextensions_tables_update_08.php \ No newline at end of file + - smallextensions_tables_update_08.php + +1.21.0: + - Static pages duplicating tool \ No newline at end of file