├── assets └── plugins │ └── templatesedit │ ├── lang │ ├── en.php │ ├── es.php │ ├── nl.php │ ├── ru.php │ ├── english.php │ ├── russian-UTF8.php │ ├── nederlands-utf8.php │ └── spanish.php │ ├── .htaccess │ ├── tpl │ ├── button.tpl.php │ ├── option.tpl.php │ ├── element.tpl.php │ ├── select.tpl.php │ ├── datalist.tpl.php │ ├── textarea.tpl.php │ ├── input.tpl.php │ ├── tab.tpl.php │ ├── thumb.tpl.php │ ├── b_field_category.tpl.php │ ├── date.tpl.php │ ├── b_field.tpl.php │ ├── builder_code.tpl.php │ ├── builder.tpl.php │ └── document.tpl.php │ ├── configs │ ├── custom_fields.example.php │ ├── template__default.php │ ├── template_default.php │ └── fields.php │ ├── plugin.templatesedit.php │ ├── css │ └── builder.css │ ├── js │ ├── Sortable.min.js │ └── TemplatesEditBuilder.js │ └── class │ ├── templateseditbuilder.class.php │ └── templatesedit.class.php ├── README.md └── install └── assets └── plugins └── templatesedit.tpl /assets/plugins/templatesedit/lang/en.php: -------------------------------------------------------------------------------- 1 | 3 | Order Deny,Allow 4 | Deny from all 5 | 6 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/tpl/button.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/tpl/option.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # templatesedit3 2 | [EVO] templatesEdit3 — плагин для изменения вида документов в админ панели Evolution CMS 3 | 4 | Demo: https://www.youtube.com/watch?v=WRWa8jR7sbc 5 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/tpl/element.tpl.php: -------------------------------------------------------------------------------- 1 | < id="" class="" >> 2 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/tpl/select.tpl.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/tpl/datalist.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/tpl/textarea.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/tpl/input.tpl.php: -------------------------------------------------------------------------------- 1 | /> 2 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/tpl/tab.tpl.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 | 5 | 6 |
7 | 8 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/tpl/thumb.tpl.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/tpl/b_field_category.tpl.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/tpl/date.tpl.php: -------------------------------------------------------------------------------- 1 | /> 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/tpl/b_field.tpl.php: -------------------------------------------------------------------------------- 1 |
> 2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | -------------------------------------------------------------------------------- /install/assets/plugins/templatesedit.tpl: -------------------------------------------------------------------------------- 1 | // [ 16 | 'title' => $_lang['delete_resource'], 17 | 'default' => 0, 18 | 'save' => true 19 | ], 20 | 'custom_field' => [ 21 | 'title' => 'custom_field', 22 | 'help' => 'custom_field', 23 | 'default' => '', 24 | 'save' => true 25 | ], 26 | 'createdon' => [ 27 | 'default' => $modx->toDateFormat(time()), 28 | 'save' => true, 29 | 'prepareSave' => function ($data, $modx) { 30 | if (!empty($data)) { 31 | return $modx->toTimeStamp($data); 32 | } else { 33 | return time(); 34 | } 35 | } 36 | ], 37 | ]; 38 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/tpl/builder_code.tpl.php: -------------------------------------------------------------------------------- 1 |
2 |

Edit config:

3 | 4 |
5 | 7 |
8 |
9 | 10 | 30 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/plugin.templatesedit.php: -------------------------------------------------------------------------------- 1 | event; 13 | 14 | switch ($e->name) { 15 | case 'OnDocFormTemplateRender': 16 | global $content; 17 | $e->addOutput(templatesedit::getInstance($content) 18 | ->renderTemplate()); 19 | break; 20 | 21 | case 'OnDocFormRender': 22 | global $content; 23 | $e->addOutput(templatesedit::getInstance($content) 24 | ->renderAfterTemplate()); 25 | break; 26 | 27 | case 'OnDocFormSave': 28 | (new templatesedit())->OnDocFormSave((int) $id, (string) $mode); 29 | break; 30 | 31 | case 'OnTempFormRender': 32 | $e->addOutput((new templateseditbuilder())->renderTemplate()); 33 | break; 34 | 35 | case 'OnTempFormSave': 36 | (new templateseditbuilder())->saveTemplate(); 37 | break; 38 | 39 | case 'OnTempFormDelete': 40 | (new templateseditbuilder())->deleteTemplate((int) $id); 41 | break; 42 | } 43 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/lang/english.php: -------------------------------------------------------------------------------- 1 | 'Role', 5 | 'fields' => 'Base fields', 6 | 'tmplvars' => 'Parameters (TV)', 7 | 'categories' => 'Categories', 8 | // action 9 | 'action.empty' => 'Empty', 10 | 'action.del' => 'Delete', 11 | 'action.get' => 'Get default config', 12 | 'action.set' => 'Set', 13 | 'action.set_default' => 'Default for all', 14 | 'action.del_default' => 'Delete default config', 15 | // confirm 16 | 'confirm.del' => 'Attention! \nAre you sure you want to delete the configuration?', 17 | 'confirm.del_default' => 'Attention! \nThis will delete the global template.', 18 | 'confirm.set_default' => 'Attention! \nThis will overwrite the global template by default and will use for documents with a blank template.', 19 | // info 20 | 'info.used_default_config' => 'Config is used by default', 21 | 'info.used_config_role_admin' => 'Use admin role configuration', 22 | 'info.default_template' => 'This config is used as global.', 23 | 'info.there_is_config' => 'There is a saved config for this template.', 24 | // import / export 25 | 'import_export' => 'Import / Export', 26 | 'import.btn' => 'Import', 27 | 'export.btn' => 'Export', 28 | ]; 29 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/lang/russian-UTF8.php: -------------------------------------------------------------------------------- 1 | 'Роль', 5 | 'fields' => 'Основные поля', 6 | 'tmplvars' => 'Параметры (TV)', 7 | 'categories' => 'Категории', 8 | // action 9 | 'action.empty' => 'Очистить', 10 | 'action.del' => 'Удалить', 11 | 'action.get' => 'Загрузить основной конфиг', 12 | 'action.set' => 'Установить', 13 | 'action.set_default' => 'По умолчанию для всех', 14 | 'action.del_default' => 'Удалить конфиг по умолчанию', 15 | // confirm 16 | 'confirm.del' => 'Внимание! \nВы уверены что хотите удалить конфигурацию?', 17 | 'confirm.del_default' => 'Внимание! \nВы уверены что хотите удалить конфигурацию используемую по умолчанию для всех?', 18 | 'confirm.set_default' => 'Внимание! \nБудет перезаписан основной шаблон используемый по умолчанию и также будет использоваться для документов с шаблоном blank.', 19 | // info 20 | 'info.used_default_config' => 'Используется конфигурация по умолчанию', 21 | 'info.used_config_role_admin' => 'Использовать конфигурацию роли администратора', 22 | 'info.default_template' => 'Этот конфиг используется как основной', 23 | 'info.there_is_config' => 'Есть сохраненный конфиг', 24 | // import / export 25 | 'import_export' => 'Импорт / Экспорт', 26 | 'import.btn' => 'Импорт', 27 | 'export.btn' => 'Экспорт', 28 | ]; 29 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/lang/nederlands-utf8.php: -------------------------------------------------------------------------------- 1 | 'Role', 5 | 'fields' => 'Standaard velden', 6 | 'tmplvars' => 'Parameters (TV)', 7 | 'categories' => 'Categorieën', 8 | // action 9 | 'action.empty' => 'Legen', 10 | 'action.del' => 'Verwijderen', 11 | 'action.get' => 'Standaard configuratie gebruiken', 12 | 'action.set' => 'Instellen', 13 | 'action.set_default' => 'Standaard voor alle', 14 | 'action.del_default' => 'Verwijder standaard configuratie', 15 | // confirm 16 | 'confirm.del' => 'Let op! \nWeet u zeker dat u de configuratie wilt verwijderen?', 17 | 'confirm.del_default' => 'Let op! \nDit zal de standaard template verwijderen.', 18 | 'confirm.set_default' => 'Let op! \nDit zal standaard de algemene template overschrijven en worden gebruikt voor documenten met een lege template.', 19 | // info 20 | 'info.used_default_config' => 'Configuratie wordt standaard gebruikt', 21 | 'info.used_config_role_admin' => 'Gebruik beheerdersrol configuratie', 22 | 'info.default_template' => 'Deze configuratie wordt gebruikt als globaal.', 23 | 'info.there_is_config' => 'Er is een opgeslagen configuratie voor deze sjabloon.', 24 | // import / export 25 | 'import_export' => 'Importeren / Exporteren', 26 | 'import.btn' => 'Importeren', 27 | 'export.btn' => 'Exporteren', 28 | ]; -------------------------------------------------------------------------------- /assets/plugins/templatesedit/lang/spanish.php: -------------------------------------------------------------------------------- 1 | 'Rol', 5 | 'fields' => 'Campos base', 6 | 'tmplvars' => 'Parámetros (TV)', 7 | 'categories' => 'Categorías', 8 | // action 9 | 'action.empty' => 'Vacío', 10 | 'action.del' => 'Borrar', 11 | 'action.get' => 'Obtener la configuración predeterminada', 12 | 'action.set' => 'Establecer', 13 | 'action.set_default' => 'Predeterminado para todos', 14 | 'action.del_default' => 'Eliminar configuración predeterminada', 15 | // confirm 16 | 'confirm.del' => 'Atención! \n¿Estás seguro de que quieres eliminar la configuración?', 17 | 'confirm.del_default' => 'Atención! \nEsto eliminará la plantilla global.', 18 | 'confirm.set_default' => 'Atención! \nEsto sobrescribirá la plantilla global de forma predeterminada y se usará para documentos con una plantilla en blanco.', 19 | // info 20 | 'info.used_default_config' => 'La configuración se usa por defecto', 21 | 'info.used_config_role_admin' => 'Usar la configuración del rol de administrador', 22 | 'info.default_template' => 'Esta configuración se utiliza como global.', 23 | 'info.there_is_config' => 'Hay una configuración guardada para esta plantilla.', 24 | // import / export 25 | 'import_export' => 'Importar / Exportar', 26 | 'import.btn' => 'Importar', 27 | 'export.btn' => 'Exportar', 28 | ]; 29 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/configs/template__default.php: -------------------------------------------------------------------------------- 1 | [ 10 | 'title' => $_lang['settings_general'], 11 | 'default' => true, 12 | 'fields' => [ 13 | 'pagetitle' => [], 14 | 'longtitle' => [], 15 | 'description' => [], 16 | 'menutitle' => [], 17 | 'parent' => [], 18 | 'weblink' => [], 19 | 'template' => [] 20 | ] 21 | ], 22 | 'Content' => [ 23 | 'title' => $_lang['description'], 24 | 'fields' => [ 25 | 'introtext' => [ 26 | 'rows' => 5 27 | ], 28 | 'content' => [ 29 | 'rows' => 15 30 | ], 31 | 'richtext' => [], 32 | ] 33 | ], 34 | 'Seo' => [ 35 | 'title' => 'SEO', 36 | 'fields' => [ 37 | 'metaTitle' => [], 38 | 'titl' => [], 39 | 'metaDescription' => [], 40 | 'desc' => [], 41 | 'metaKeywords' => [], 42 | 'keyw' => [], 43 | 'alias' => [], 44 | 'link_attributes' => [], 45 | 'menuindex' => [], 46 | 'hidemenu' => [], 47 | 'noIndex' => [], 48 | 'sitemap_exclude' => [], 49 | 'sitemap_priority' => [], 50 | 'sitemap_changefreq' => [] 51 | ] 52 | ], 53 | 'Settings' => [ 54 | 'title' => $_lang['settings_page_settings'], 55 | 'fields' => [ 56 | 'published' => [], 57 | 'alias_visible' => [], 58 | 'isfolder' => [], 59 | 'donthit' => [], 60 | 'contentType' => [], 61 | 'type' => [], 62 | 'content_dispo' => [], 63 | 'pub_date' => [], 64 | 'unpub_date' => [], 65 | 'createdon' => [], 66 | 'editedon' => [], 67 | 'searchable' => [], 68 | 'cacheable' => [], 69 | 'syncsite' => [] 70 | ] 71 | ] 72 | ]; 73 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/configs/template_default.php: -------------------------------------------------------------------------------- 1 | [ 10 | 'default' => true, 11 | 'title' => $_lang['settings_general'], 12 | 'fields' => [ 13 | 'pagetitle' => [ 14 | 'class' => 'form-control-lg' 15 | ], 16 | 'longtitle' => [], 17 | 'description' => [], 18 | 'menutitle' => [], 19 | 'parent' => [], 20 | 'weblink' => [], 21 | 'template' => [] 22 | ] 23 | ], 24 | 'Content' => [ 25 | 'title' => $_lang['description'], 26 | 'fields' => [ 27 | 'introtext' => [ 28 | 'titleClass' => 'col-xs-12', 29 | 'fieldClass' => 'col-xs-12', 30 | 'rows' => 5 31 | ], 32 | 'content' => [ 33 | 'titleClass' => 'col-xs-12 form-row pt-1', 34 | 'fieldClass' => 'col-xs-12', 35 | 'selectClass' => 'float-xs-right', 36 | 'rows' => 15 37 | ], 38 | 'richtext' => [], 39 | ] 40 | ], 41 | 'Seo' => [ 42 | 'title' => 'SEO', 43 | 'fields' => [ 44 | 'metaTitle' => [], 45 | 'titl' => [], 46 | 'metaDescription' => [], 47 | 'desc' => [], 48 | 'metaKeywords' => [], 49 | 'keyw' => [], 50 | 'alias' => [], 51 | 'link_attributes' => [], 52 | 'menuindex' => [], 53 | 'hidemenu' => [], 54 | 'noIndex' => [], 55 | 'sitemap_exclude' => [], 56 | 'sitemap_priority' => [], 57 | 'sitemap_changefreq' => [] 58 | ] 59 | ], 60 | 'Settings' => [ 61 | 'title' => $_lang['settings_page_settings'], 62 | 'fields' => [ 63 | 'published' => [], 64 | 'alias_visible' => [], 65 | 'isfolder' => [], 66 | 'donthit' => [], 67 | 'contentType' => [], 68 | 'type' => [], 69 | 'content_dispo' => [], 70 | 'pub_date' => [], 71 | 'unpub_date' => [], 72 | 'createdon' => [], 73 | 'editedon' => [], 74 | 'searchable' => [], 75 | 'cacheable' => [], 76 | 'syncsite' => [] 77 | ] 78 | ] 79 | ]; 80 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/configs/fields.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'title' => $_lang['resource_title'], 7 | 'help' => $_lang['resource_title_help'] 8 | ], 9 | 'longtitle' => [ 10 | 'title' => $_lang['long_title'], 11 | 'help' => $_lang['resource_long_title_help'] 12 | ], 13 | 'description' => [ 14 | 'title' => $_lang['resource_description'], 15 | 'help' => $_lang['resource_description_help'] 16 | ], 17 | 'alias' => [ 18 | 'title' => $_lang['resource_alias'], 19 | 'help' => $_lang['resource_alias_help'] 20 | ], 21 | 'link_attributes' => [ 22 | 'title' => $_lang['link_attributes'], 23 | 'help' => htmlspecialchars($_lang['link_attributes_help'], ENT_QUOTES) 24 | ], 25 | 'published' => [ 26 | 'title' => $_lang['resource_opt_published'], 27 | 'help' => $_lang['resource_opt_published_help'] 28 | ], 29 | 'pub_date' => [ 30 | 'title' => $_lang['page_data_publishdate'], 31 | 'help' => $_lang['page_data_publishdate_help'] 32 | ], 33 | 'unpub_date' => [ 34 | 'title' => $_lang['page_data_unpublishdate'], 35 | 'help' => $_lang['page_data_unpublishdate_help'] 36 | ], 37 | 'parent' => [ 38 | 'title' => $_lang['resource_parent'], 39 | 'help' => $_lang['resource_parent_help'] 40 | ], 41 | 'isfolder' => [ 42 | 'title' => $_lang['resource_opt_folder'], 43 | 'help' => $_lang['resource_opt_folder_help'] 44 | ], 45 | 'introtext' => [ 46 | 'title' => $_lang['resource_summary'], 47 | 'help' => $_lang['resource_summary_help'] 48 | ], 49 | 'content' => [ 50 | 'title' => $_lang['resource_content'], 51 | 'help' => '' 52 | ], 53 | 'richtext' => [ 54 | 'title' => $_lang['resource_opt_richtext'], 55 | 'help' => $_lang['resource_opt_richtext_help'] 56 | ], 57 | 'weblink' => [ 58 | 'title' => $_lang['weblink'], 59 | 'help' => $_lang['resource_weblink_help'] 60 | ], 61 | 'template' => [ 62 | 'title' => $_lang['page_data_template'], 63 | 'help' => $_lang['page_data_template_help'] 64 | ], 65 | 'type' => [ 66 | 'title' => $_lang['resource_type'], 67 | 'help' => $_lang['resource_type_message'] 68 | ], 69 | 'contentType' => [ 70 | 'title' => $_lang['page_data_contentType'], 71 | 'help' => $_lang['page_data_contentType_help'] 72 | ], 73 | 'content_dispo' => [ 74 | 'title' => $_lang['resource_opt_contentdispo'], 75 | 'help' => $_lang['resource_opt_contentdispo_help'] 76 | ], 77 | 'menuindex' => [ 78 | 'title' => $_lang['resource_opt_menu_index'], 79 | 'help' => $_lang['resource_opt_menu_index_help'] 80 | ], 81 | 'searchable' => [ 82 | 'title' => $_lang['page_data_searchable'], 83 | 'help' => $_lang['page_data_searchable_help'] 84 | ], 85 | 'cacheable' => [ 86 | 'title' => $_lang['page_data_cacheable'], 87 | 'help' => $_lang['page_data_cacheable_help'] 88 | ], 89 | 'createdon' => [ 90 | 'title' => $_lang['createdon'], 91 | 'help' => $_lang['createdon'] 92 | ], 93 | 'editedon' => [ 94 | 'title' => $_lang['editedon'], 95 | 'help' => $_lang['editedon'] 96 | ], 97 | 'menutitle' => [ 98 | 'title' => $_lang['resource_opt_menu_title'], 99 | 'help' => $_lang['resource_opt_menu_title_help'] 100 | ], 101 | 'donthit' => [ 102 | 'title' => $_lang['track_visitors_title'], 103 | 'help' => $_lang['resource_opt_trackvisit_help'] 104 | ], 105 | 'hidemenu' => [ 106 | 'title' => $_lang['resource_opt_show_menu'], 107 | 'help' => $_lang['resource_opt_show_menu_help'] 108 | ], 109 | 'alias_visible' => [ 110 | 'title' => $_lang['resource_opt_alvisibled'], 111 | 'help' => $_lang['resource_opt_alvisibled_help'] 112 | ], 113 | 'syncsite' => [ 114 | 'title' => $_lang['resource_opt_emptycache'], 115 | 'help' => $_lang['resource_opt_emptycache_help'] 116 | ] 117 | ]; 118 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/tpl/builder.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
lang['role'] ?>
6 |
7 | renderSelectRole() ?> 8 |
9 |
10 | params['check_this_config'])) { 12 | ?> 13 | lang['info.there_is_config'] ?> 14 | params['check_base_config'])) { 17 | ?> 18 | lang['info.used_config_role_admin'] ?> 19 | params['check_default_config'])) { 21 | ?> 22 | lang['info.used_default_config'] ?> 23 | 27 |
28 |
29 | params['check_this_config'])) { 31 | ?> 32 | lang['action.del'] ?> 33 | params['templatesedit_builder_role'] == 1) { 35 | if ($this->params['config_is_default']) { 36 | ?> 37 | lang['action.del_default'] ?> 38 | 41 | lang['action.set_default'] ?> 42 | params['check_base_config'])) { 46 | ?> 47 | lang['action.set'] ?> 48 | 52 | lang['action.empty'] ?> 53 |
54 |
55 |
56 |
57 |
lang['fields'] ?>
58 |
59 | renderUnusedFields() ?> 60 |
61 |
lang['tmplvars'] ?>
62 |
63 | renderUnusedTvars() ?> 64 |
65 |
lang['categories'] ?>
66 |
67 | renderUnusedCategories() ?> 68 |
69 |
lang['import_export'] ?>
70 |
71 |
72 | 73 |
74 |
75 |
76 | lang['import.btn'] ?> 77 |
78 |
79 | lang['export.btn'] ?> 80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | 90 | 91 | 92 | 101 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/tpl/document.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 40 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/css/builder.css: -------------------------------------------------------------------------------- 1 | .builder .row { display: flex; flex-wrap: wrap; margin-left: -.25rem; margin-right: -.25rem; cursor: default } 2 | .builder .row::after { display: none } 3 | .builder .row.col { align-content: flex-start; margin: 0 0 .25rem; padding: 0 .25rem 0 0 } 4 | .builder .col-1, .builder .col-2, .builder .col-3, .builder .col-4, .builder .col-5, .builder .col-6, .builder .col-7, .builder .col-8, .builder .col-9, .builder .col-10, .builder .col-11, .builder .col-12, .builder .col, .builder .col-auto { position: relative; width: 100%; min-height: 0; padding-left: .25rem; padding-right: .25rem } 5 | .builder .col { flex-basis: 0; flex-grow: 1; max-width: 100% } 6 | .builder .col-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; width: auto; max-width: none } 7 | .builder .col-0 { max-width: 5px } 8 | .builder .col-1 { -ms-flex: 0 0 8.333333%; flex: 0 0 8.333333%; max-width: 8.333333% } 9 | .builder .col-2 { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667% } 10 | .builder .col-3 { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25% } 11 | .builder .col-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333% } 12 | .builder .col-5 { -ms-flex: 0 0 41.666667%; flex: 0 0 41.666667%; max-width: 41.666667% } 13 | .builder .col-6 { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50% } 14 | .builder .col-7 { -ms-flex: 0 0 58.333333%; flex: 0 0 58.333333%; max-width: 58.333333% } 15 | .builder .col-8 { -ms-flex: 0 0 66.666667%; flex: 0 0 66.666667%; max-width: 66.666667% } 16 | .builder .col-9 { -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75% } 17 | .builder .col-10 { -ms-flex: 0 0 83.333333%; flex: 0 0 83.333333%; max-width: 83.333333% } 18 | .builder .col-11 { -ms-flex: 0 0 91.666667%; flex: 0 0 91.666667%; max-width: 91.666667% } 19 | .builder .col-12 { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100% } 20 | .builder .align-items-center { align-items: center } 21 | .builder label { margin: 0; user-select: none } 22 | .builder input[type="text"] { cursor: auto } 23 | .builder .row-col { position: relative; padding: .25rem .25rem 0 .25rem !important; margin-right: -1px !important; min-height: 2.4rem; height: 100%; border: 1px solid } 24 | .builder .b-resize { position: absolute; top: 0; right: -1px; bottom: 0; width: .35rem; cursor: col-resize; transition: background-color .25s } 25 | .builder .fa { font-size: .75rem } 26 | .builder .b-btn-wrap { position: absolute; right: .25rem; bottom: 100%; padding: 0 .25rem; margin-bottom: -1px; opacity: 0; visibility: hidden; border: 1px solid; border-bottom-color: transparent !important; transition: opacity .5s, visibility .5s } 27 | .builder .b-btn-wrap-center { right: 50%; margin-right: -1rem } 28 | .builder .b-btn-wrap i { margin: 0 1px } 29 | .builder .row-col-wrap:hover .b-btn-wrap { opacity: 1; visibility: visible } 30 | .builder .b-header { margin-bottom: .5rem } 31 | .builder .b-content { } 32 | .builder .b-hidden { display: none } 33 | .builder .b-settings { position: absolute; overflow: hidden; z-index: 6; opacity: 0; visibility: hidden; left: .5rem; top: 0; padding: 0 !important; width: 18rem !important; max-width: 18rem !important; cursor: auto; transform: translateY(.5rem); transition: opacity .25s, visibility .25s, transform .25s } 34 | .builder .row-col-wrap > .b-settings, .builder .b-field > .b-settings { left: auto; right: 3rem } 35 | .builder .b-open > .b-settings { transform: translateX(0); opacity: 1; visibility: visible } 36 | .builder .b-settings .row { align-items: center; padding: .4rem 1rem; border-bottom: 1px solid } 37 | .builder .b-settings .b-settings-header { padding: 0; margin: 0 } 38 | .builder .b-settings .b-settings-header > div { padding: 0 1rem } 39 | .builder .b-settings .b-settings-header label { padding: 0 .5rem; height: 1.5rem; font-size: 2rem; line-height: .45rem; text-align: center; font-family: serif } 40 | .builder .b-settings .b-settings-header .b-btn-close { line-height: 1.45rem } 41 | .builder .b-settings .b-settings-body { font-size: 0; white-space: nowrap } 42 | .builder .b-settings .b-input-more:checked ~ .b-settings-body .b-settings-content { transform: translateX(-100%); } 43 | .builder .b-settings .b-settings-content { overflow: hidden; display: inline-block; width: 18rem; font-size: .75rem; vertical-align: top; transition: transform .5s } 44 | .builder .b-settings .b-settings-content .row:last-child { margin-bottom: .5rem; border: none } 45 | .builder .b-settings .b-btn-group { display: flex; flex-wrap: wrap; flex-direction: row } 46 | .builder .b-settings .b-btn-group label { flex-basis: 0; flex-grow: 1; max-width: 100%; display: block; border-left: 1px solid; } 47 | .builder .b-settings .b-btn-group label:first-child { border: none } 48 | .builder .b-settings input[type="radio"] + i, .builder .b-settings input[type="checkbox"] + i { display: block; width: 100%; padding: .1rem .25rem; font-style: normal; font-size: 0.875rem; line-height: 1.3rem; text-align: center; cursor: pointer } 49 | .builder .b-settings [data-name="position"][value="r"] + i { transform: rotate(180deg) } 50 | .builder .b-tab { display: flex; flex-wrap: wrap; flex-direction: row; position: relative; top: 1px; padding: .5rem .5rem .25rem .5rem; margin: 2.5rem 0 3rem; min-height: 3.5rem; border: 1px solid } 51 | .builder .b-tab-title { position: absolute; left: 0; bottom: 100%; border-bottom: none } 52 | .builder .b-tab-title-input { position: relative; width: 10.5rem } 53 | .builder .b-tab-check-default { position: absolute; z-index: 5; right: .5rem; top: .4rem; margin: 0; color: #bdbdbd } 54 | .builder .b-tab-default:checked ~ .b-tab-title .b-tab-check-default { color: #5cb85c } 55 | .builder .b-fields-wrap { padding-bottom: 1.5rem } 56 | .builder .b-items-header { } 57 | .builder .b-items { overflow: auto; overflow-x: hidden; padding: 0 !important; margin: 0 -1px 1rem; width: 15rem; max-height: 15rem } 58 | .builder .b-items .b-field, .builder .b-items .b-category { overflow: hidden; padding: .25rem .5rem; margin: -1px 0 0; border: none; border-top: 1px solid; white-space: nowrap; text-overflow: ellipsis } 59 | .builder .b-items .b-field .b-btn-settings, .builder .b-items .b-field .b-btn-del, .builder .b-items .b-field .b-field-title, .builder .b-items .b-category .b-btn-del { display: none } 60 | .builder .b-field { position: relative; padding: .25rem .5rem; margin-bottom: .25rem; height: 1.8rem; border: 1px solid } 61 | .builder .b-field.b-required-checked { border-color: #d9534f !important } 62 | .builder .b-field.b-readonly-checked { border-color: #638ee7 !important } 63 | .builder .b-field .b-btn-settings, .builder .b-field .b-btn-del { opacity: .5; transition: .5s opacity } 64 | .builder .b-field:hover .b-btn-settings, .builder .b-field:hover .b-btn-del { opacity: 1 } 65 | .builder .b-field:hover, .builder .b-category:hover { background-color: rgba(0, 0, 0, 0.04) } 66 | .builder .b-field-hidden::after { position: absolute; content: ""; left: 0; top: 0; right: 0; bottom: 0; background-color: rgba(239, 239, 239, 0.65) } 67 | .builder .b-field-hidden * { -webkit-user-select: none !important; user-select: none !important; } 68 | .builder .b-category { padding: .15rem .5rem 0; margin-bottom: .25rem; height: 1.8rem; border: 2px dashed #4caf50 } 69 | .builder .b-category .b-field-name { width: 11.15rem } 70 | .builder .b-items .b-category-title { margin-bottom: 0 } 71 | .builder .b-category .row-col { display: none } 72 | .builder .b-tab[data-id="#Static"] { margin-top: -2rem } 73 | .builder .b-tab[data-id="#Static"] .b-tab-title { display: none } 74 | /* theme colors */ 75 | .builder .b-settings input[type="radio"] + i, .builder .b-settings input[type="checkbox"] + i, .builder .b-settings .b-btn-more { background-color: #e0e0e0 } 76 | .builder .b-settings input[type="radio"]:checked + i, .builder .b-settings input[type="checkbox"]:checked + i { background-color: #1976d2; color: #fff } 77 | .builder .b-btn-wrap { background-color: #f3f3f3 } 78 | .builder .b-resize { background-color: #e0e0e0 } 79 | .builder .b-items .b-field, .builder .b-items .b-category, .builder .b-tab, .builder .b-field, .builder .row-col, .builder .b-resize, .builder .b-settings .row { border-color: #e0e0e0 } 80 | .builder .row-col-wrap:hover .row-col, .builder .row-col-wrap:hover .b-resize, .builder .b-btn-wrap, .builder .b-resize, .builder .b-settings .col-12:first-child { border-color: #ccc } 81 | .builder .row-col-wrap:hover .b-resize, .builder .b-settings .b-btn-group label { border-color: #ccc } 82 | .builder .b-item.active, .builder .b-item.active .b-tab-title, .builder .row-col-wrap.active { background-color: #fff4dc; color: #222 } 83 | .darkness .builder .b-items .b-field, .darkness .builder .b-items .b-category, .darkness .builder .b-tab, .darkness .builder .b-field, .darkness .builder .row-col, .darkness .builder .b-resize, .darkness .builder .b-settings .row, .darkness .builder .b-settings .b-btn-group label { border-color: #414449 } 84 | .darkness .builder .b-resize { background-color: #414449 } 85 | .darkness .builder .row-col-wrap:hover .b-resize { background-color: #65686d } 86 | .darkness .builder .row-col-wrap:hover .row-col, .darkness .builder .row-col-wrap:hover .b-resize, .darkness .builder .b-btn-wrap, .darkness .builder .b-settings > .row:first-child { border-color: #65686d } 87 | .darkness .builder .b-btn-wrap, .darkness .builder .b-settings input[type="radio"] + i, .darkness .builder .b-settings input[type="checkbox"] + i, .darkness .builder .b-settings .b-btn-more { background-color: #282c34 } 88 | .darkness .builder .b-settings input[type="radio"]:checked + i, .darkness .builder .b-settings input[type="checkbox"]:checked + i, .builder .b-settings .b-input-more:checked + .row .b-btn-more { background-color: #1976d2; color: #fff } 89 | .builder .row-col-wrap:hover .b-resize:hover, .builder .b-resize:hover, .builder .b-resize:active { background-color: #1976d2 } 90 | .darkness .builder .b-item.active, .darkness .builder .b-item.active .b-tab-title, .darkness .builder .row-col-wrap.active { background-color: #ffc107; color: #222 } 91 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/js/Sortable.min.js: -------------------------------------------------------------------------------- 1 | /*! Sortable 1.8.4 - MIT | git://github.com/SortableJS/Sortable.git */ 2 | 3 | !function(t){"use strict";"function"==typeof define&&define.amd?define(t):"undefined"!=typeof module&&void 0!==module.exports?module.exports=t():window.Sortable=t()}(function(){"use strict";if("undefined"==typeof window||!window.document)return function(){throw new Error("Sortable.js requires a window with a document")};var U,V,f,u,q,G,h,X,Y,A,K,n,Z,Q,l,s,c,p,k,J,$,tt,et,ot,g,nt,I=[],B=!1,v=!1,it=!1,d=[],rt=!1,at=!1,m=[],i=/\s+/g,lt="Sortable"+(new Date).getTime(),b=window,st=b.document,w=b.parseInt,ct=b.setTimeout,e=b.jQuery||b.Zepto,o=b.Polymer,r={capture:!1,passive:!1},dt=!!navigator.userAgent.match(/(?:Trident.*rv[ :]?11\.|msie|iemobile)/i),_=!!navigator.userAgent.match(/Edge/i),y=!!navigator.userAgent.match(/firefox/i),D=!(!navigator.userAgent.match(/safari/i)||navigator.userAgent.match(/chrome/i)||navigator.userAgent.match(/android/i)),S=!!navigator.userAgent.match(/iP(ad|od|hone)/i),T=_||dt?"cssFloat":"float",a="draggable"in st.createElement("div"),C=function(){if(dt)return!1;var t=st.createElement("x");return t.style.cssText="pointer-events:auto","auto"===t.style.pointerEvents}(),ht=!1,E=!1,ut=Math.abs,x=Math.min,N=Math.max,M=[],P=function(t,e){var o=Dt(t),n=w(o.width)-w(o.paddingLeft)-w(o.paddingRight)-w(o.borderLeftWidth)-w(o.borderRightWidth),i=Mt(t,0,e),r=Mt(t,1,e),a=i&&Dt(i),l=r&&Dt(r),s=a&&w(a.marginLeft)+w(a.marginRight)+Lt(i).width,c=l&&w(l.marginLeft)+w(l.marginRight)+Lt(r).width;if("flex"===o.display)return"column"===o.flexDirection||"column-reverse"===o.flexDirection?"vertical":"horizontal";if("grid"===o.display)return o.gridTemplateColumns.split(" ").length<=1?"vertical":"horizontal";if(i&&"none"!==a.float){var d="left"===a.float?"left":"right";return!r||"both"!==l.clear&&l.clear!==d?"horizontal":"vertical"}return i&&("block"===a.display||"flex"===a.display||"table"===a.display||"grid"===a.display||n<=s&&"none"===o[T]||r&&"none"===o[T]&&n=n.left-i&&t<=n.right+i,a=e>=n.top-i&&e<=n.bottom+i;if(r&&a)return d[o]}}(t.clientX,t.clientY);e&&e[lt]._onDragOver({clientX:t.clientX,clientY:t.clientY,target:e,rootEl:e})}};function mt(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be HTMLElement, not "+{}.toString.call(t);this.el=t,this.options=e=Bt({},e),t[lt]=this;var o={group:null,sort:!0,disabled:!1,store:null,handle:null,scroll:!0,scrollSensitivity:30,scrollSpeed:10,bubbleScroll:!0,draggable:/[uo]l/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return P(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,touchStartThreshold:w(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==mt.supportPointer&&("PointerEvent"in window||window.navigator&&"msPointerEnabled"in window.navigator),emptyInsertThreshold:5};for(var n in o)!(n in e)&&(e[n]=o[n]);for(var i in W(e),this)"_"===i.charAt(0)&&"function"==typeof this[i]&&(this[i]=this[i].bind(this));this.nativeDraggable=!e.forceFallback&&a,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?wt(t,"pointerdown",this._onTapStart):(wt(t,"mousedown",this._onTapStart),wt(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(wt(t,"dragover",this),wt(t,"dragenter",this)),d.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[])}function bt(t,e,o,n){if(t){o=o||st;do{if(null!=e&&(">"===e[0]&&t.parentNode===o&&kt(t,e.substring(1))||kt(t,e))||n&&t===o)return t;if(t===o)break}while(t=(i=t).host&&i!==st&&i.host.nodeType?i.host:i.parentNode)}var i;return null}function wt(t,e,o){t.addEventListener(e,o,r)}function _t(t,e,o){t.removeEventListener(e,o,r)}function yt(t,e,o){if(t&&e)if(t.classList)t.classList[o?"add":"remove"](e);else{var n=(" "+t.className+" ").replace(i," ").replace(" "+e+" "," ");t.className=(n+(o?" "+e:"")).replace(i," ")}}function Dt(t,e,o){var n=t&&t.style;if(n){if(void 0===o)return st.defaultView&&st.defaultView.getComputedStyle?o=st.defaultView.getComputedStyle(t,""):t.currentStyle&&(o=t.currentStyle),void 0===e?o:o[e];e in n||-1!==e.indexOf("webkit")||(e="-webkit-"+e),n[e]=o+("string"==typeof o?"":"px")}}function St(t){var e="";do{var o=Dt(t,"transform");o&&"none"!==o&&(e=o+" "+e)}while(t=t.parentNode);return window.DOMMatrix?new DOMMatrix(e):window.WebKitCSSMatrix?new WebKitCSSMatrix(e):window.CSSMatrix?new CSSMatrix(e):void 0}function Tt(t,e,o){if(t){var n=t.getElementsByTagName(e),i=0,r=n.length;if(o)for(;i=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){U&&xt(U),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;_t(t,"mouseup",this._disableDelayedDrag),_t(t,"touchend",this._disableDelayedDrag),_t(t,"touchcancel",this._disableDelayedDrag),_t(t,"mousemove",this._delayedDragTouchMoveHandler),_t(t,"touchmove",this._delayedDragTouchMoveHandler),_t(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||("touch"==t.pointerType?t:null),!this.nativeDraggable||e?this.options.supportPointer?wt(st,"pointermove",this._onTouchMove):wt(st,e?"touchmove":"mousemove",this._onTouchMove):(wt(U,"dragend",this),wt(q,"dragstart",this._onDragStart));try{st.selection?Ht(function(){st.selection.empty()}):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(t,e){if(v=!1,q&&U){this.nativeDraggable&&(wt(st,"dragover",this._handleAutoScroll),wt(st,"dragover",F));var o=this.options;!t&&yt(U,o.dragClass,!1),yt(U,o.ghostClass,!0),Dt(U,"transform",""),mt.active=this,t&&this._appendGhost(),Ct(this,q,"start",U,q,q,K,void 0,e)}else this._nulling()},_emulateDragOver:function(t){if(k){if(this._lastX===k.clientX&&this._lastY===k.clientY&&!t)return;this._lastX=k.clientX,this._lastY=k.clientY,z();for(var e=st.elementFromPoint(k.clientX,k.clientY),o=e;e&&e.shadowRoot;)o=e=e.shadowRoot.elementFromPoint(k.clientX,k.clientY);if(o)do{if(o[lt])if(o[lt]._onDragOver({clientX:k.clientX,clientY:k.clientY,target:e,rootEl:o})&&!this.options.dragoverBubble)break;e=o}while(o=o.parentNode);U.parentNode[lt]._computeIsAligned(k),j()}},_onTouchMove:function(t,e){if(p){var o=this.options,n=o.fallbackTolerance,i=o.fallbackOffset,r=t.touches?t.touches[0]:t,a=f&&St(f),l=f&&a&&a.a,s=f&&a&&a.d,c=S&&g&&Ft(g),d=(r.clientX-p.clientX+i.x)/(l||1)+(c?c[0]-m[0]:0)/(l||1),h=(r.clientY-p.clientY+i.y)/(s||1)+(c?c[1]-m[1]:0)/(s||1),u=t.touches?"translate3d("+d+"px,"+h+"px,0)":"translate("+d+"px,"+h+"px)";if(!mt.active&&!v){if(n&&x(ut(r.clientX-this._lastX),ut(r.clientY-this._lastY))evo = evolutionCMS(); 81 | $this->basePath = dirname(__DIR__) . '/'; 82 | 83 | $this->getTranslate(); 84 | $this->setParams(); 85 | $this->getConfig(); 86 | $this->setDefaultParams(); 87 | $this->getDefaultFields(); 88 | $this->getDefaultTvars(); 89 | $this->getDefaultCategories(); 90 | $this->getFieldTypes(); 91 | $this->fillData(); 92 | } 93 | 94 | /** 95 | * @return string 96 | */ 97 | public function renderTemplate(): string 98 | { 99 | if ($this->params['configFile']) { 100 | $content = $this->view('builder_code', [ 101 | 'config' => file_get_contents($this->basePath . $this->params['configFile']) ?: ' $this->params['configFile'], 103 | ]); 104 | } else { 105 | $content = $this->view('builder', [ 106 | 'config' => $this->json_encode($this->config), 107 | 'data_fields' => $this->json_encode($this->defaultFields), 108 | 'data_tvars' => $this->json_encode($this->defaultTvars), 109 | 'data_categories' => $this->json_encode($this->defaultCategories), 110 | 'data_types' => $this->json_encode($this->fieldTypes), 111 | ]); 112 | } 113 | 114 | return $this->view('tab', [ 115 | 'name' => 'templatesEditBuilder', 116 | 'title' => 'template Builder', 117 | 'tabsObject' => 'tp', 118 | 'content' => $content, 119 | ]); 120 | } 121 | 122 | /** 123 | * 124 | */ 125 | protected function setParams(): void 126 | { 127 | $this->params = $this->evo->event->params; 128 | $this->params['excludeTvCategory'] = !empty($this->params['excludeTvCategory']) ? array_map( 129 | 'trim', 130 | explode(',', $this->params['excludeTvCategory']) 131 | ) : []; 132 | $this->params['templatesedit_builder_role'] = (int) ($_REQUEST['templatesedit_builder_role'] ?? 1); 133 | $this->params['config'] = $this->params['id'] . '__' . $this->params['templatesedit_builder_role']; 134 | $this->params['action'] = 135 | isset($_POST['templatesedit_builder_action']) && $_POST['templatesedit_builder_action'] != '' 136 | ? $_POST['templatesedit_builder_action'] : ''; 137 | 138 | $this->params['config_default'] = []; 139 | $this->params['config_is_default'] = false; 140 | $this->params['check_this_config'] = false; 141 | $this->params['check_base_config'] = false; 142 | $this->params['check_default_config'] = 0; 143 | 144 | $this->params['configFile'] = null; 145 | } 146 | 147 | /** 148 | * 149 | */ 150 | protected function setDefaultParams(): void 151 | { 152 | if (!$this->params['configFile']) { 153 | $this->params['default.tab'] = 'General'; 154 | 155 | foreach ($this->config as $tabId => &$tab) { 156 | if (!isset($tab['title'])) { 157 | $tab['title'] = 'Tab'; 158 | } 159 | if (!empty($tab['default'])) { 160 | $this->params['default.tab'] = $tabId; 161 | } 162 | if (isset($tab['fields'])) { 163 | $tab['col:0:12']['fields'] = $tab['fields']; 164 | unset($tab['fields']); 165 | } 166 | } 167 | 168 | if (!isset($this->config['#Static'])) { 169 | $this->config['#Static'] = [ 170 | 'title' => 'Static', 171 | 'col:0:12' => [], 172 | ]; 173 | } 174 | } 175 | } 176 | 177 | /** 178 | * 179 | */ 180 | protected function fillData(): void 181 | { 182 | foreach ($this->config as &$tab) { 183 | if (is_array($tab)) { 184 | foreach ($tab as &$col) { 185 | if (is_array($col)) { 186 | foreach ($col as $fieldsId => &$fields) { 187 | [$type, $id] = explode(':', $fieldsId . '::'); 188 | if ($type == 'category') { 189 | $this->categories[$id] = $this->defaultCategories[$id]; 190 | } else { 191 | foreach ($fields as $fieldId => $field) { 192 | if (is_array($field)) { 193 | if (isset($this->defaultFields[$fieldId])) { 194 | $this->fields[$fieldId] = $field; 195 | } else { 196 | if (isset($this->defaultTvars[$fieldId])) { 197 | $this->tvars[$fieldId] = 198 | array_merge($this->defaultTvars[$fieldId], $field); 199 | } else { 200 | unset($fields[$fieldId]); 201 | } 202 | } 203 | } 204 | } 205 | } 206 | } 207 | } 208 | } 209 | } 210 | } 211 | } 212 | 213 | /** 214 | * @return string 215 | */ 216 | public function renderSelectRole(): string 217 | { 218 | $users = []; 219 | $sql = $this->evo->getDatabase()->select('id, name', $this->evo->getFullTableName('user_roles'), '', 'id asc'); 220 | 221 | while ($row = $this->evo->getDatabase()->getRow($sql)) { 222 | $pf = '    '; 223 | if (is_file($this->basePath . 'configs/template__' . $this->params['id'] . '__' . $row['id'] . '.json') || 224 | (!is_file($this->basePath . 'configs/template__' . $this->params['id'] . '__' . $row['id'] . '.json') && 225 | !empty($this->params['config_default'][$row['id']])) 226 | ) { 227 | $pf = '★ '; 228 | } 229 | $users[] = [ 230 | 'value' => $row['id'], 231 | 'title' => $pf . $row['name'], 232 | ]; 233 | } 234 | 235 | return $this->form('select', [ 236 | 'name' => 'templatesedit_builder_role', 237 | 'value' => $this->params['templatesedit_builder_role'], 238 | 'options' => $users, 239 | 'class' => 'form-control form-control-sm ' . $this->params['templatesedit_builder_role'], 240 | 'onchange' => 'document.location.href=\'?a=16&id=' . $this->params['id'] . 241 | '&templatesedit_builder_role=\'+this.value', 242 | ]); 243 | } 244 | 245 | /** 246 | * @return string 247 | */ 248 | protected function renderUnusedFields(): string 249 | { 250 | $out = ''; 251 | $items = array_diff_key($this->defaultFields, $this->fields); 252 | 253 | uksort($items, function ($a, $b) { 254 | return strcasecmp((string) $a, (string) $b); 255 | }); 256 | 257 | foreach ($items as $k => $v) { 258 | $out .= $this->view('b_field', [ 259 | 'name' => $k, 260 | 'type' => 'field', 261 | 'title' => $v['title'], 262 | ]); 263 | } 264 | 265 | return $out; 266 | } 267 | 268 | /** 269 | * @return string 270 | */ 271 | protected function renderUnusedTvars(): string 272 | { 273 | $out = ''; 274 | $items = $this->defaultTvars; 275 | 276 | uksort($items, function ($a, $b) { 277 | return strcasecmp((string) $a, (string) $b); 278 | }); 279 | 280 | foreach ($items as $k => $v) { 281 | $out .= $this->view('b_field', [ 282 | 'name' => $k, 283 | 'type' => 'tv', 284 | 'title' => $v['title'], 285 | 'category' => $v['category'], 286 | 'rowClass' => isset($this->tvars[$k]) ? ' b-add' : '', 287 | 'attr' => isset($this->tvars[$k]) || isset($this->categories[$v['category']]) ? ' hidden' : '', 288 | ]); 289 | } 290 | 291 | return $out; 292 | } 293 | 294 | /** 295 | * @return string 296 | */ 297 | protected function renderUnusedCategories(): string 298 | { 299 | $out = ''; 300 | $categories = []; 301 | 302 | foreach ($this->defaultTvars as $item) { 303 | $categories[$item['category']][$item['name']] = $item; 304 | } 305 | 306 | foreach ($categories as $k => $category) { 307 | foreach ($category as $key => $item) { 308 | if (isset($this->tvars[$key])) { 309 | unset($category[$key]); 310 | } 311 | } 312 | if (empty($category) || isset($this->categories[$k])) { 313 | unset($categories[$k]); 314 | } else { 315 | $categories[$k] = $this->defaultCategories[$k]; 316 | } 317 | } 318 | 319 | uksort($categories, function ($a, $b) { 320 | return strcasecmp((string) $a, (string) $b); 321 | }); 322 | 323 | foreach ($categories as $k => $v) { 324 | if (!in_array($k, $this->params['excludeTvCategory'])) { 325 | $out .= $this->view('b_field_category', [ 326 | 'name' => $k, 327 | 'title' => $v['category'], 328 | ]); 329 | } 330 | } 331 | 332 | return $out; 333 | } 334 | 335 | /** 336 | * @return array 337 | */ 338 | protected function getDefaultFields(): array 339 | { 340 | $this->defaultFields = require_once $this->basePath . 'configs/fields.php'; 341 | 342 | if (version_compare($this->evo->getConfig('settings_version'), '3.1.0') >= 0) { 343 | $position = array_search('donthit', array_keys($this->defaultFields)); 344 | $fieldsBefore = array_slice($this->defaultFields, 0, $position); 345 | $fieldsAfter = array_slice($this->defaultFields, $position + 1); 346 | $addedFields = [ 347 | 'hide_from_tree' => $this->defaultFields['donthit'], 348 | ]; 349 | $this->defaultFields = $fieldsBefore + $addedFields + $fieldsAfter; 350 | } 351 | 352 | if (file_exists($this->basePath . 'configs/custom_fields.php')) { 353 | $this->defaultFields += require_once $this->basePath . 'configs/custom_fields.php'; 354 | } 355 | 356 | return $this->defaultFields; 357 | } 358 | 359 | /** 360 | * @return array 361 | */ 362 | protected function getDefaultTvars(): array 363 | { 364 | global $_lang; 365 | 366 | if ($this->params['id']) { 367 | $sql = $this->evo->getDatabase()->query( 368 | ' 369 | SELECT tv.id, tv.name, tv.caption AS title, tv.description, tv.category 370 | FROM ' . $this->evo->getFullTableName('site_tmplvar_templates') . ' AS tt 371 | LEFT JOIN ' . $this->evo->getFullTableName('site_tmplvars') . ' AS tv ON tv.id=tt.tmplvarid 372 | WHERE templateid=' . $this->params['id'] . ' 373 | ORDER BY tt.rank DESC, tv.rank DESC, tv.caption DESC, tv.id DESC 374 | ' 375 | ); 376 | 377 | $this->defaultCategories = []; 378 | 379 | while ($row = $this->evo->getDatabase()->getRow($sql)) { 380 | $this->defaultTvars[$row['name']] = $row; 381 | $this->defaultCategories[$row['category']] = $row['category']; 382 | } 383 | 384 | $this->defaultCategories[0] = [ 385 | 'id' => 0, 386 | 'category' => $_lang['no_category'], 387 | 'title' => $_lang['no_category'], 388 | 'rank' => 0, 389 | ]; 390 | } 391 | 392 | return $this->defaultTvars; 393 | } 394 | 395 | /** 396 | * @return array 397 | */ 398 | protected function getDefaultCategories(): array 399 | { 400 | if (!empty($this->defaultCategories)) { 401 | $sql = $this->evo->getDatabase()->query( 402 | ' 403 | SELECT *, category AS title 404 | FROM ' . $this->evo->getFullTableName('categories') . ' 405 | ORDER BY category 406 | ' 407 | ); 408 | 409 | while ($row = $this->evo->getDatabase()->getRow($sql)) { 410 | $this->defaultCategories[$row['id']] = $row; 411 | } 412 | } 413 | 414 | return $this->defaultCategories; 415 | } 416 | 417 | /** 418 | * @return array 419 | */ 420 | protected function getFieldTypes(): array 421 | { 422 | $this->fieldTypes = [ 423 | 'Standard Type' => [ 424 | 'text' => 'Text', 425 | 'textarea' => 'Textarea', 426 | 'textareamini' => 'Textarea (Mini)', 427 | 'richtext' => 'RichText', 428 | 'dropdown' => 'DropDown List Menu', 429 | 'listbox' => 'Listbox (Single-Select)', 430 | 'listbox-multiple' => 'Listbox (Multi-Select)', 431 | 'option' => 'Radio Options', 432 | 'checkbox' => 'Check Box', 433 | 'image' => 'Image', 434 | 'file' => 'File', 435 | 'number' => 'Number', 436 | 'date' => 'Date', 437 | ], 438 | 'Custom Type' => [ 439 | 'custom_tv' => 'Custom Input', 440 | ], 441 | ]; 442 | 443 | $custom_tvs = scandir(MODX_BASE_PATH . 'assets/tvs'); 444 | foreach ($custom_tvs as $ctv) { 445 | if (strpos($ctv, '.') !== 0 && $ctv != 'index.html') { 446 | $this->fieldTypes['Custom Type']['custom_tv:' . $ctv] = $ctv; 447 | } 448 | } 449 | 450 | return $this->fieldTypes; 451 | } 452 | 453 | /** 454 | * @return array 455 | */ 456 | protected function getConfig(): array 457 | { 458 | $this->config = []; 459 | $file = ''; 460 | 461 | if ($files = glob($this->basePath . 'configs/template__*__default.json')) { 462 | preg_match('/template__(.*)__default\.json/', $files[0], $matches); 463 | if (!empty($matches[1])) { 464 | $this->setDefaultConfig((int) $matches[1]); 465 | } 466 | } 467 | 468 | if (!empty($_GET['d']) && file_exists($this->basePath . 'configs/template__' . $_GET['d'] . '__1.json')) { 469 | $this->params['id'] = $_GET['d']; 470 | } 471 | 472 | if (!empty($this->params['config_default']['default'])) { 473 | $file = $this->params['config_default']['default']; 474 | $this->params['config_is_default'] = $this->params['check_default_config'] == $this->params['id']; 475 | } 476 | 477 | if (!empty($this->params['config_default'][$this->params['templatesedit_builder_role']]) && 478 | file_exists($this->params['config_default'][$this->params['templatesedit_builder_role']]) 479 | ) { 480 | $file = $this->params['config_default'][$this->params['templatesedit_builder_role']]; 481 | } 482 | 483 | if (file_exists($this->basePath . 'configs/template__' . $this->params['id'] . '__1.json')) { 484 | $file = $this->params['check_base_config'] = 485 | $this->basePath . 'configs/template__' . $this->params['id'] . '__1.json'; 486 | } 487 | 488 | if (file_exists($this->basePath . 'configs/template__' . $this->params['config'] . '.json')) { 489 | $file = $this->params['check_this_config'] = 490 | $this->basePath . 'configs/template__' . $this->params['config'] . '.json'; 491 | } 492 | 493 | if ($file) { 494 | $this->config = json_decode(file_get_contents($file), true); 495 | } else { 496 | if (file_exists($this->basePath . 'configs/template__' . $this->params['id'] . '.php')) { 497 | $this->params['configFile'] = 'configs/template__' . $this->params['id'] . '.php'; 498 | } else { 499 | $this->config = require_once $this->basePath . 'configs/template__default.php'; 500 | } 501 | } 502 | 503 | return $this->config; 504 | } 505 | 506 | /** 507 | * @param int $id 508 | */ 509 | protected function setDefaultConfig(int $id = 0): void 510 | { 511 | if ($id) { 512 | $this->params['check_default_config'] = $id; 513 | if ($this->params['id'] != $id) { 514 | if (is_file($this->basePath . 'configs/template__' . $id . '__default.json')) { 515 | $this->params['config_default']['default'] = 516 | $this->basePath . 'configs/template__' . $id . '__default.json'; 517 | } 518 | } else { 519 | $files = glob($this->basePath . 'configs/template__' . $id . '__*.json'); 520 | foreach ($files as $file) { 521 | preg_match('/template__' . $id . '__(.*)\.json/', $file, $matches); 522 | if (!empty($matches[1])) { 523 | $this->params['config_default'][$matches[1]] = $file; 524 | } 525 | } 526 | } 527 | } 528 | } 529 | 530 | /** 531 | * 532 | */ 533 | public function saveTemplate(): void 534 | { 535 | if (isset($_POST['templatesedit_builder_data'])) { 536 | $data = $this->evo->removeSanitizeSeed($_POST['templatesedit_builder_data']); 537 | 538 | if (!empty($data)) { 539 | if ($this->params['check_default_config'] == $this->params['id'] || 540 | empty($this->params['config_default'][$this->params['templatesedit_builder_role']]) || 541 | (!empty($this->params['config_default'][$this->params['templatesedit_builder_role']]) && 542 | file_get_contents( 543 | $this->params['config_default'][$this->params['templatesedit_builder_role']] 544 | ) != $data) 545 | ) { 546 | file_put_contents( 547 | $this->basePath . 'configs/template__' . $this->params['config'] . '.json', 548 | $data 549 | ); 550 | } else { 551 | if (is_file($this->basePath . 'configs/template__' . $this->params['config'] . '.json')) { 552 | unlink($this->basePath . 'configs/template__' . $this->params['config'] . '.json'); 553 | } 554 | } 555 | } else { 556 | if (is_file($this->basePath . 'configs/template__' . $this->params['config'] . '.json')) { 557 | unlink($this->basePath . 'configs/template__' . $this->params['config'] . '.json'); 558 | } 559 | if (is_file($this->basePath . 'configs/template__' . $this->params['id'] . '__default.json')) { 560 | unlink($this->basePath . 'configs/template__' . $this->params['id'] . '__default.json'); 561 | } 562 | } 563 | 564 | switch ($this->params['action']) { 565 | case 'set_default': 566 | $this->setDefaultConfig((int) $this->params['id']); 567 | 568 | if (!empty($data)) { 569 | if ($files = glob($this->basePath . 'configs/template__*.json')) { 570 | foreach ($files as $file) { 571 | if (!in_array($file, $this->params['config_default'])) { 572 | unlink($file); 573 | } 574 | } 575 | } 576 | file_put_contents( 577 | $this->basePath . 'configs/template__' . $this->params['id'] . '__default.json', 578 | $data 579 | ); 580 | file_put_contents( 581 | $this->basePath . 'configs/template__' . $this->params['id'] . '__1.json', 582 | $data 583 | ); 584 | } 585 | break; 586 | 587 | case 'del_default': 588 | if (is_file($this->basePath . 'configs/template__' . $this->params['id'] . '__default.json')) { 589 | unlink($this->basePath . 'configs/template__' . $this->params['id'] . '__default.json'); 590 | } 591 | break; 592 | 593 | case 'set_base': 594 | file_put_contents( 595 | $this->basePath . 'configs/template__' . $this->params['config'] . '.json', 596 | file_get_contents($this->params['check_base_config']) 597 | ); 598 | break; 599 | } 600 | } elseif (isset($_POST['templatesedit_builder_code'])) { 601 | $data = $this->evo->removeSanitizeSeed($_POST['templatesedit_builder_code']); 602 | 603 | if ($data == '') { 604 | $data = 'basePath . 'configs/template__' . $this->params['id'] . '.php', $data); 608 | } 609 | 610 | if (!empty($this->params['action'])) { 611 | $_POST['stay'] = 2; 612 | } 613 | 614 | if ($this->params['templatesedit_builder_role'] != 1) { 615 | header( 616 | 'Location: index.php?a=16&id=' . $this->params['id'] . '&r=2&stay=2&templatesedit_builder_role=' . 617 | $this->params['templatesedit_builder_role'] 618 | ); 619 | exit; 620 | } 621 | } 622 | 623 | /** 624 | * @param int $id 625 | */ 626 | public function deleteTemplate(int $id = 0) 627 | { 628 | $files = glob($this->basePath . 'configs/template__' . $id . '__*.json'); 629 | foreach ($files as $file) { 630 | unlink($file); 631 | } 632 | } 633 | 634 | /** 635 | * @return array 636 | */ 637 | protected function getTranslate(): array 638 | { 639 | $manager_language = $this->evo->getConfig('manager_language'); 640 | if (file_exists($this->basePath . 'lang/' . $manager_language . '.php')) { 641 | $this->lang = require_once $this->basePath . 'lang/' . $manager_language . '.php'; 642 | } else { 643 | $this->lang = require_once $this->basePath . 'lang/english.php'; 644 | } 645 | 646 | return $this->lang; 647 | } 648 | 649 | /** 650 | * @param string $tpl 651 | * @param array $data 652 | * 653 | * @return string 654 | */ 655 | protected function form(string $tpl, array $data = []): string 656 | { 657 | if (!isset($data['name'])) { 658 | $data['name'] = ''; 659 | } 660 | 661 | if (!isset($data['type'])) { 662 | $data['type'] = 'text'; 663 | } 664 | 665 | if (!isset($data['id'])) { 666 | $data['id'] = $data['name']; 667 | } 668 | 669 | if (!isset($data['value'])) { 670 | $data['value'] = ''; 671 | } 672 | 673 | if (!isset($data['placeholder'])) { 674 | $data['placeholder'] = ''; 675 | } 676 | 677 | if (!isset($data['maxlength'])) { 678 | $data['maxlength'] = '255'; 679 | } 680 | 681 | if (empty($data['class'])) { 682 | $data['class'] = 'form-control'; 683 | } 684 | 685 | if (!isset($data['attr'])) { 686 | $data['attr'] = ''; 687 | } 688 | 689 | if (!isset($data['checked'])) { 690 | $data['checked'] = ''; 691 | } else { 692 | if (is_bool($data['checked']) || is_numeric($data['checked'])) { 693 | $data['checked'] = $data['checked'] ? 'checked' : ''; 694 | } 695 | } 696 | 697 | if (!isset($data['disabled'])) { 698 | $data['disabled'] = ''; 699 | } else { 700 | if (is_bool($data['disabled']) || is_numeric($data['disabled'])) { 701 | $data['disabled'] = $data['disabled'] ? 'disabled' : ''; 702 | } 703 | } 704 | 705 | $options = ''; 706 | 707 | if (!empty($data['options'])) { 708 | $tmp = $data['options']; 709 | foreach ($tmp as $k => $v) { 710 | if (is_array($v)) { 711 | $options .= $this->form('option', [ 712 | 'value' => $v['value'], 713 | 'title' => $v['title'], 714 | 'selected' => $data['value'] == $v['value'] ? 'selected' : '', 715 | ]); 716 | } else { 717 | $options .= $this->form('option', [ 718 | 'value' => $k, 719 | 'title' => $v, 720 | 'selected' => $k == $data['value'] ? 'selected' : '', 721 | ]); 722 | } 723 | } 724 | } 725 | 726 | if (!empty($data['optgroup'])) { 727 | foreach ($data['optgroup'] as $label => $group) { 728 | $options .= ''; 729 | foreach ($group as $value => $title) { 730 | $options .= $this->form('option', [ 731 | 'value' => $value, 732 | 'title' => $title, 733 | 'selected' => $value == $data['value'] ? 'selected' : '', 734 | ]); 735 | } 736 | $options .= ''; 737 | } 738 | } 739 | 740 | $data['options'] = $options; 741 | 742 | if (!isset($data['onchange'])) { 743 | $data['onchange'] = 'documentDirty=true;'; 744 | } 745 | 746 | return $this->view($tpl, $data); 747 | } 748 | 749 | /** 750 | * @param string $tpl 751 | * @param array $data 752 | * 753 | * @return string 754 | */ 755 | protected function view(string $tpl, array $data = []): string 756 | { 757 | $tpl = trim($tpl, '/'); 758 | $tpl = $this->basePath . 'tpl/' . $tpl . '.tpl.php'; 759 | if (file_exists($tpl)) { 760 | extract($data); 761 | ob_start(); 762 | @require($tpl); 763 | $out = ob_get_contents(); 764 | ob_end_clean(); 765 | } else { 766 | $out = 'Error: Could not load template ' . $tpl . '!
'; 767 | } 768 | 769 | return $out; 770 | } 771 | 772 | /** 773 | * @param array|null $array 774 | * 775 | * @return string 776 | */ 777 | protected function json_encode(?array $array = null): string 778 | { 779 | return is_array($array) ? json_encode($array, JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE) : ''; 780 | } 781 | } 782 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/js/TemplatesEditBuilder.js: -------------------------------------------------------------------------------- 1 | var TemplatesEditBuilder = function(el, config) { 2 | 'use strict'; 3 | 4 | function TemplatesEdit(el, config) 5 | { 6 | this.settings = { 7 | tab: {}, 8 | field: { 9 | title: '', 10 | description: '', 11 | help: '', 12 | default: '', 13 | elements: '', 14 | type: '', 15 | size: 'md', 16 | position: 'l', 17 | reverse: '', 18 | required: '', 19 | readonly:'', 20 | rows: '', 21 | pattern: '', 22 | choices: '', 23 | } 24 | }; 25 | this.el = el; 26 | this.dataEl = config.dataEl; 27 | this.data_fields = config.data_fields; 28 | this.data_tvars = config.data_tvars; 29 | this.data_categories = config.data_categories; 30 | this.data_types = config.data_types; 31 | this.elUnusedFields = document.querySelector('.builder .b-unused-fields'); 32 | this.elUnusedTvars = document.querySelector('.builder .b-unused-tvars'); 33 | this.elUnusedCategories = document.querySelector('.builder .b-unused-categories'); 34 | this.init(); 35 | this.initDraggable(); 36 | this.events(); 37 | } 38 | 39 | TemplatesEdit.prototype.templates = { 40 | tab: '' + 41 | '
\n' + 42 | ' \n' + 43 | '
\n' + 44 | '
\n' + 45 | '
\n' + 46 | '
\n' + 47 | ' \n' + 48 | ' \n' + 49 | '
\n' + 50 | '
\n' + 51 | '
\n' + 52 | '
\n' + 53 | '
\n' + 54 | ' {content}' + 55 | '
', 56 | col: '' + 57 | '
\n' + 58 | '
{content}
\n' + 59 | ' \n' + 60 | '
\n' + 61 | ' \n' + 62 | '
\n' + 63 | '
\n' + 64 | ' \n' + 65 | ' \n' + 66 | ' \n' + 67 | '
\n' + 68 | '
', 69 | fields: '' + 70 | '
{content}
', 71 | category: '' + 72 | '
\n' + 73 | '
\n' + 74 | '
\n' + 75 | '
{title}
\n' + 76 | '
\n' + 77 | '
\n' + 78 | '
', 79 | field: '' + 80 | '
\n' + 81 | '
\n' + 82 | '
\n' + 83 | '
{name}
\n' + 84 | '
\n' + 85 | '
\n' + 86 | '
\n' + 87 | '
', 88 | settings: '' + 89 | '
\n' + 90 | ' \n' + 91 | '
\n' + 92 | '
{name}
\n' + 93 | ' \n' + 94 | ' \n' + 95 | '
\n' + 96 | '
{content}
\n' + 97 | '
', 98 | settingsContent: '' + 99 | '
{content}
', 100 | setting: '' + 101 | '
\n' + 102 | '
{title}
\n' + 103 | '
{content}
\n' + 104 | '
', 105 | select: '' + 106 | '', 107 | selectOptgroup: '' + 108 | '{options}', 109 | selectOption: '' + 110 | '', 111 | textarea: '' + 112 | '', 113 | input: '' + 114 | '', 115 | checkbox: '' + 116 | '', 120 | radio: '' + 121 | '' 125 | }; 126 | 127 | TemplatesEdit.prototype.init = function() { 128 | this.data = this.dataEl.value ? JSON.parse(this.dataEl.value) : {}; 129 | this.el.innerHTML = this.renderTabs(this.data); 130 | }; 131 | 132 | TemplatesEdit.prototype.initDraggable = function() { 133 | this.draggable(document.querySelectorAll('.builder .b-content'), 'tabs'); 134 | this.draggable(document.querySelectorAll('.builder .row-col'), 'items'); 135 | this.draggable(document.querySelectorAll('.builder .b-tab'), 'row'); 136 | this.draggableItems(document.querySelectorAll('.builder .b-fields-wrap .b-items'), 'items'); 137 | this.resizable(document.querySelectorAll('.builder .b-resize')); 138 | }; 139 | 140 | TemplatesEdit.prototype.build = function() { 141 | this.data = {}; 142 | var self = this, 143 | checkFields = false, 144 | settings; 145 | 146 | this.el.querySelectorAll('.b-tab').forEach(function(tab) { 147 | var tabId = tab.getAttribute('data-id'); 148 | 149 | self.data[tabId] = { 150 | title: tab.querySelector('.b-tab-title .b-title').value 151 | }; 152 | if (tab.firstElementChild.checked) { 153 | self.data[tabId]['default'] = tab.firstElementChild.checked; 154 | } 155 | 156 | tab.querySelectorAll('.row-col-wrap').forEach(function(col, i) { 157 | var fields = 0, colId = 'col:' + i + ':' + col.className.match(/col-(\d+)/)[1]; 158 | self.data[tabId][colId] = {}; 159 | settings = JSON.parse(col.getAttribute('data-settings')); 160 | if (Object.keys(settings).length) { 161 | self.data[tabId][colId]['settings'] = settings; 162 | } 163 | 164 | col.querySelectorAll('.b-item').forEach(function(item) { 165 | if (item.classList.contains('b-category')) { 166 | self.data[tabId][colId][item.getAttribute('data-name')] = {}; 167 | fields++; 168 | } else { 169 | if (!self.data[tabId][colId]['fields:' + fields]) { 170 | self.data[tabId][colId]['fields:' + fields] = {}; 171 | } 172 | settings = JSON.parse(item.getAttribute('data-settings')); 173 | for (var k in settings) { 174 | if (settings.hasOwnProperty(k) && (typeof self.settings.field[k] === 'undefined' || settings[k] === self.settings.field[k])) { 175 | delete settings[k]; 176 | } 177 | } 178 | self.data[tabId][colId]['fields:' + fields][item.getAttribute('data-name')] = settings; 179 | checkFields = true; 180 | } 181 | }); 182 | 183 | }); 184 | }); 185 | 186 | this.dataEl.value = checkFields ? JSON.stringify(this.data) : ''; 187 | }; 188 | 189 | TemplatesEdit.prototype.events = function() { 190 | var self = this; 191 | 192 | document.addEventListener('click', function(e) { 193 | var target = e.target; 194 | self.hideSettings(target); 195 | if (target.classList.contains('b-btn-empty')) { 196 | self.onDelete(target); 197 | } 198 | if (target.id === 'builder_import') { 199 | self.import(document.getElementById('builder_file')); 200 | } 201 | if (target.id === 'builder_export') { 202 | self.export(); 203 | } 204 | }); 205 | 206 | this.el.addEventListener('click', function(e) { 207 | var target = e.target; 208 | if (target.classList.contains('b-btn-settings')) { 209 | self.onSettings(target); 210 | } 211 | if (target.classList.contains('b-btn-del')) { 212 | self.onDelete(target); 213 | } 214 | if (target.classList.contains('b-btn-add')) { 215 | self.onAdd(target); 216 | } 217 | }); 218 | 219 | this.el.addEventListener('change', function(e) { 220 | var target = e.target; 221 | if (target.classList.contains('b-tab-default')) { 222 | this.querySelectorAll('.b-tab-default:checked').forEach(function(el) { 223 | if (el !== target) { 224 | el.checked = false; 225 | } 226 | }); 227 | self.build(); 228 | } 229 | }); 230 | 231 | this.el.addEventListener('keyup', function(e) { 232 | var target = e.target, parent; 233 | if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') { 234 | clearTimeout(self.timer); 235 | self.timer = setTimeout(function() { 236 | parent = target.closest('.b-item').querySelector('.b-settings'); 237 | if (parent) { 238 | parent.closest('.b-item').setAttribute('data-settings', JSON.stringify(self.serialize(parent, 'data-name'))); 239 | } 240 | self.build(); 241 | }, 300); 242 | } 243 | }); 244 | }; 245 | 246 | TemplatesEdit.prototype.hideSettings = function(target) { 247 | var close = false; 248 | document.querySelectorAll('.b-open').forEach(function(el) { 249 | if (!target.closest('.b-settings') || target.classList.contains('b-btn-close')) { 250 | el.classList.remove('b-open'); 251 | } 252 | close = true; 253 | }); 254 | if (close) { 255 | this.build(); 256 | } 257 | }; 258 | 259 | TemplatesEdit.prototype.onDelete = function(target) { 260 | var self = this, parent = target.closest('.b-item'); 261 | if (target.classList.contains('b-btn-empty')) { 262 | this.el.querySelectorAll('.row-col .b-item').forEach(function(el) { 263 | self.delItem(el); 264 | }); 265 | this.el.querySelectorAll('.b-tab').forEach(function(el) { 266 | el.parentElement.removeChild(el); 267 | }); 268 | } else if (target.parentElement.classList.contains('b-btn-wrap')) { 269 | parent = target.closest('.row-col-wrap'); 270 | parent.firstElementChild.querySelectorAll('.b-item').forEach(function(el) { 271 | self.delItem(el); 272 | }); 273 | if (parent.parentNode.querySelectorAll('.row-col-wrap').length > 1) { 274 | parent.parentNode.removeChild(parent); 275 | } 276 | } else if (parent.classList.contains('b-tab')) { 277 | parent.querySelectorAll('.b-item').forEach(function(el) { 278 | self.delItem(el); 279 | }); 280 | parent.parentNode.removeChild(parent); 281 | } else { 282 | this.delItem(parent); 283 | } 284 | if (this.el.children.length === 1) { 285 | this.addTab(null, true, true); 286 | this.el.firstElementChild.querySelector('[name="b-tab-default"]').checked = true; 287 | } 288 | this.sortItems(this.elUnusedFields, 'name'); 289 | this.sortItems(this.elUnusedTvars, 'name'); 290 | this.sortItems(this.elUnusedCategories, 'title'); 291 | this.build(); 292 | }; 293 | 294 | TemplatesEdit.prototype.onAdd = function(target) { 295 | var parent = target.closest('.b-item'); 296 | if (target.parentElement.classList.contains('b-btn-wrap')) { 297 | this.addRow(target.parentElement.closest('.row-col-wrap'), false); 298 | } else if (parent.classList.contains('b-tab')) { 299 | this.addTab(parent, false); 300 | } 301 | this.build(); 302 | }; 303 | 304 | TemplatesEdit.prototype.onSettings = function(target) { 305 | var parent = target.closest('.b-draggable'), 306 | settingsBlock = parent.lastElementChild.classList.contains('b-settings') && parent.lastElementChild; 307 | if (parent.classList.contains('b-open')) { 308 | parent.classList.remove('b-open'); 309 | } else { 310 | if (!settingsBlock) { 311 | if (parent.classList.contains('b-field')) { 312 | settingsBlock = this.getFieldSettings(parent.getAttribute('data-name'), JSON.parse(parent.getAttribute('data-settings'))); 313 | } else if (parent.classList.contains('row-col-wrap')) { 314 | settingsBlock = this.getColSettings(parent.getAttribute('data-name'), JSON.parse(parent.getAttribute('data-settings'))); 315 | } else if (parent.classList.contains('b-tab')) { 316 | settingsBlock = false; 317 | } 318 | if (settingsBlock) { 319 | parent.appendChild(settingsBlock); 320 | } 321 | } 322 | if (settingsBlock) { 323 | setTimeout(function() { 324 | parent.classList.add('b-open'); 325 | }, 20); 326 | } 327 | } 328 | this.build(); 329 | }; 330 | 331 | TemplatesEdit.prototype.getColSettings = function(name, data) { 332 | var self = this, 333 | settings, 334 | settingsTabs, 335 | settingsBlock; 336 | 337 | for (var k in this.settings.field) { 338 | if (this.settings.field.hasOwnProperty(k)) { 339 | if (typeof data[k] === 'undefined') { 340 | data[k] = this.settings.field[k]; 341 | } 342 | } 343 | } 344 | 345 | settings = this.tpl(this.templates.setting, { 346 | title: 'Title', 347 | content: this.tpl(this.templates.input, { 348 | name: 'title', 349 | value: this.escapeHtml(data['title']) 350 | }) 351 | }); 352 | 353 | settings += this.tpl(this.templates.setting, { 354 | title: 'Size', 355 | content: this.tpl(this.templates.radio, { 356 | name: 'size', 357 | parent: name, 358 | value: 'sm', 359 | checked: data['size'] === 'sm' ? 'checked' : '', 360 | title: 'S' 361 | }) + this.tpl(this.templates.radio, { 362 | name: 'size', 363 | parent: name, 364 | value: 'md', 365 | checked: data['size'] === 'md' ? 'checked' : '', 366 | title: 'M' 367 | }) + this.tpl(this.templates.radio, { 368 | name: 'size', 369 | parent: name, 370 | value: 'lg', 371 | checked: data['size'] === 'lg' ? 'checked' : '', 372 | title: 'L' 373 | }) 374 | }); 375 | 376 | settings += this.tpl(this.templates.setting, { 377 | title: 'Position', 378 | content: this.tpl(this.templates.radio, { 379 | name: 'position', 380 | parent: name, 381 | value: 'l', 382 | checked: data['position'] === 'l' ? 'checked' : '', 383 | icon: 'fa fa-list' 384 | }) + this.tpl(this.templates.radio, { 385 | name: 'position', 386 | parent: name, 387 | value: 'c', 388 | checked: data['position'] === 'c' ? 'checked' : '', 389 | icon: 'fa fa-align-justify' 390 | }) + this.tpl(this.templates.radio, { 391 | name: 'position', 392 | parent: name, 393 | value: 'r', 394 | checked: data['position'] === 'r' ? 'checked' : '', 395 | icon: 'fa fa-list' 396 | }) + this.tpl(this.templates.radio, { 397 | name: 'position', 398 | parent: name, 399 | value: 'a', 400 | checked: data['position'] === 'a' ? 'checked' : '', 401 | title: 'A' 402 | }) 403 | }); 404 | 405 | settings += this.tpl(this.templates.setting, { 406 | title: 'Reverse', 407 | content: this.tpl(this.templates.checkbox, { 408 | name: 'reverse', 409 | parent: name, 410 | value: 1, 411 | checked: data['reverse'] ? 'checked' : '', 412 | icon: 'fa fa-exchange' 413 | }) 414 | }); 415 | 416 | settingsTabs = this.tpl(this.templates.settingsContent, { 417 | content: settings 418 | }); 419 | 420 | settingsBlock = this.tpl(this.templates.settings, { 421 | name: name, 422 | classBtnMore: 'b-hidden', 423 | content: settingsTabs, 424 | settings: this.escapeHtml(JSON.stringify(data)) 425 | }, true); 426 | 427 | settingsBlock.addEventListener('change', function(e) { 428 | var target = e.target, 429 | parent, 430 | name, 431 | settings; 432 | if (target.tagName === 'INPUT' || target.tagName === 'SELECT') { 433 | parent = target.closest('.b-settings'); 434 | name = target.getAttribute('data-name'); 435 | if (parent && name) { 436 | settings = self.serialize(parent, 'data-name'); 437 | for (var k in settings) { 438 | if (settings.hasOwnProperty(k)) { 439 | if (settings[k] === self.settings.field[k]) { 440 | delete settings[k]; 441 | } 442 | } 443 | } 444 | parent.closest('.row-col-wrap').setAttribute('data-settings', JSON.stringify(settings)); 445 | self.build(); 446 | } 447 | } 448 | }); 449 | 450 | return settingsBlock; 451 | }; 452 | 453 | TemplatesEdit.prototype.getFieldSettings = function(name, data) { 454 | var self = this, 455 | settingsBlock, 456 | fieldType, 457 | title, 458 | help, 459 | description, 460 | settingsTabs, 461 | settings, 462 | _t = ~['textarea', 'textareamini', 'richtext'].indexOf(data['type']); 463 | 464 | self.timer = 0; 465 | 466 | if (this.data_fields[name]) { 467 | fieldType = 'field'; 468 | title = this.data_fields[name]['title']; 469 | help = this.data_fields[name]['help']; 470 | description = this.data_fields[name]['description']; 471 | } else { 472 | fieldType = 'tv'; 473 | title = this.data_tvars[name]['title']; 474 | help = this.data_tvars[name]['help']; 475 | description = this.data_tvars[name]['description']; 476 | } 477 | 478 | settingsTabs = ''; 479 | settings = ''; 480 | 481 | for (var k in this.settings.field) { 482 | if (this.settings.field.hasOwnProperty(k)) { 483 | if (typeof data[k] === 'undefined') { 484 | data[k] = this.settings.field[k]; 485 | } 486 | } 487 | } 488 | 489 | settings += this.tpl(this.templates.setting, { 490 | title: 'Title', 491 | content: this.tpl(this.templates.input, { 492 | name: 'title', 493 | value: this.escapeHtml(data['title']), 494 | placeholder: title 495 | }) 496 | }); 497 | settings += this.tpl(this.templates.setting, { 498 | title: 'Description', 499 | content: this.tpl(this.templates.input, { 500 | name: 'description', 501 | value: this.escapeHtml(data['description']), 502 | placeholder: description 503 | }) 504 | }); 505 | settings += this.tpl(this.templates.setting, { 506 | title: 'Help', 507 | content: this.tpl(this.templates.input, { 508 | name: 'help', 509 | value: this.escapeHtml(data['help']), 510 | placeholder: help 511 | }) 512 | }); 513 | settings += this.tpl(this.templates.setting, { 514 | title: 'Required', 515 | content: this.tpl(this.templates.checkbox, { 516 | name: 'required', 517 | value: 1, 518 | checked: data['required'] ? 'checked' : '', 519 | icon: 'fa fa-check-circle' 520 | }) 521 | }); 522 | settings += this.tpl(this.templates.setting, { 523 | title: 'Readonly', 524 | content: this.tpl(this.templates.checkbox, { 525 | name: 'readonly', 526 | value: 1, 527 | checked: data['readonly'] ? 'checked' : '', 528 | icon: 'fa fa-check-circle' 529 | }) 530 | }); 531 | settings += this.tpl(this.templates.setting, { 532 | title: 'Size', 533 | content: this.tpl(this.templates.radio, { 534 | name: 'size', 535 | parent: name, 536 | value: 'sm', 537 | checked: data['size'] === 'sm' ? 'checked' : '', 538 | title: 'S' 539 | }) + this.tpl(this.templates.radio, { 540 | name: 'size', 541 | parent: name, 542 | value: 'md', 543 | checked: data['size'] === 'md' ? 'checked' : '', 544 | title: 'M' 545 | }) + this.tpl(this.templates.radio, { 546 | name: 'size', 547 | parent: name, 548 | value: 'lg', 549 | checked: data['size'] === 'lg' ? 'checked' : '', 550 | title: 'L' 551 | }) 552 | }); 553 | settings += this.tpl(this.templates.setting, { 554 | title: 'Position', 555 | content: this.tpl(this.templates.radio, { 556 | name: 'position', 557 | parent: name, 558 | value: 'l', 559 | checked: data['position'] === 'l' ? 'checked' : '', 560 | icon: 'fa fa-list' 561 | }) + this.tpl(this.templates.radio, { 562 | name: 'position', 563 | parent: name, 564 | value: 'c', 565 | checked: data['position'] === 'c' ? 'checked' : '', 566 | icon: 'fa fa-align-justify' 567 | }) + this.tpl(this.templates.radio, { 568 | name: 'position', 569 | parent: name, 570 | value: 'r', 571 | checked: data['position'] === 'r' ? 'checked' : '', 572 | icon: 'fa fa-list' 573 | }) 574 | }); 575 | settings += this.tpl(this.templates.setting, { 576 | title: 'Reverse', 577 | content: this.tpl(this.templates.checkbox, { 578 | name: 'reverse', 579 | parent: name, 580 | value: 1, 581 | checked: data['reverse'] ? 'checked' : '', 582 | icon: 'fa fa-exchange' 583 | }) 584 | }); 585 | settingsTabs += this.tpl(this.templates.settingsContent, { 586 | content: settings 587 | }); 588 | 589 | settings = ''; 590 | if (fieldType === 'field') { 591 | settings += this.tpl(this.templates.setting, { 592 | title: 'Default value', 593 | content: this.tpl(this.templates.input, { 594 | name: 'default', 595 | value: this.escapeHtml(data['default']) 596 | }) 597 | }); 598 | settings += this.tpl(this.templates.setting, { 599 | title: 'Type', 600 | content: this.tpl(this.templates.select, { 601 | name: 'type', 602 | options: (function(a) { 603 | var optgroup, options; 604 | optgroup = a.tpl(a.templates.selectOption, { 605 | value: '', 606 | title: 'default' 607 | }); 608 | for (var g in a.data_types) { 609 | if (a.data_types.hasOwnProperty(g)) { 610 | options = ''; 611 | for (var o in a.data_types[g]) { 612 | if (a.data_types[g].hasOwnProperty(o)) { 613 | options += a.tpl(a.templates.selectOption, { 614 | title: a.data_types[g][o], 615 | value: o, 616 | selected: o === data['type'] ? 'selected' : '' 617 | }); 618 | } 619 | } 620 | optgroup += a.tpl(a.templates.selectOptgroup, { 621 | label: g, 622 | options: options 623 | }); 624 | } 625 | } 626 | return optgroup; 627 | })(this) 628 | }) 629 | }); 630 | settings += this.tpl(this.templates.setting, { 631 | title: 'Rows', 632 | class: _t ? '' : 'b-hidden', 633 | content: this.tpl(this.templates.input, { 634 | name: 'rows', 635 | value: _t ? data['rows'] : '' 636 | }) 637 | }); 638 | settings += this.tpl(this.templates.setting, { 639 | title: 'Possible values', 640 | content: this.tpl(this.templates.textarea, { 641 | name: 'elements', 642 | value: data['elements'], 643 | rows: 2 644 | }) 645 | }); 646 | } 647 | settings += this.tpl(this.templates.setting, { 648 | title: 'Pattern', 649 | content: this.tpl(this.templates.input, { 650 | name: 'pattern', 651 | value: data['pattern'] 652 | }) 653 | }); 654 | settings += this.tpl(this.templates.setting, { 655 | title: 'Choices (tags)', 656 | content: this.tpl(this.templates.input, { 657 | name: 'choices', 658 | value: data['choices'], 659 | placeholder: 'delimiter' 660 | }) 661 | }); 662 | settingsTabs += this.tpl(this.templates.settingsContent, { 663 | content: settings 664 | }); 665 | 666 | settingsBlock = this.tpl(this.templates.settings, { 667 | name: name, 668 | content: settingsTabs 669 | }, true); 670 | 671 | settingsBlock.addEventListener('change', function(e) { 672 | var target = e.target, 673 | parent, 674 | settings, 675 | _t = ~['textarea', 'textareamini', 'richtext'].indexOf(target.value); 676 | 677 | if (target.tagName === 'INPUT' || target.tagName === 'SELECT') { 678 | parent = target.closest('.b-settings'); 679 | name = target.getAttribute('data-name'); 680 | if (parent && name) { 681 | settings = self.serialize(parent, 'data-name'); 682 | for (var k in settings) { 683 | if (settings.hasOwnProperty(k)) { 684 | if (settings[k] === self.settings.field[k]) { 685 | delete settings[k]; 686 | } 687 | } 688 | } 689 | parent.closest('.b-item').setAttribute('data-settings', JSON.stringify(settings)); 690 | switch (name) { 691 | case 'required': 692 | parent.closest('.b-item').classList.toggle('b-required-checked', target.checked); 693 | break; 694 | case 'readonly': 695 | parent.closest('.b-item').classList.toggle('b-readonly-checked', target.checked); 696 | break; 697 | case 'type': 698 | parent.querySelector('[data-name="rows"]').closest('.b-setting').classList.toggle('b-hidden', !_t); 699 | break; 700 | } 701 | self.build(); 702 | } 703 | } 704 | }); 705 | 706 | return settingsBlock; 707 | }; 708 | 709 | TemplatesEdit.prototype.sortItems = function(el, name) { 710 | var list = [].slice.call(el.children).sort(function(a, b) { 711 | return a.getAttribute('data-' + name).toLowerCase().localeCompare(b.getAttribute('data-' + name).toLowerCase()); 712 | }); 713 | for (var i = 0; i < list.length; i++) { 714 | list[i].parentNode.appendChild(list[i]); 715 | } 716 | }; 717 | 718 | TemplatesEdit.prototype.addRow = function(row, d) { 719 | if (d) { 720 | row.insertAdjacentHTML('beforeend', this.tpl(this.templates.col, { 721 | col: 12, 722 | content: '', 723 | settings: '{}' 724 | })); 725 | } else { 726 | row.insertAdjacentHTML('afterend', this.tpl(this.templates.col, { 727 | col: 12, 728 | content: '', 729 | settings: '{}' 730 | })); 731 | } 732 | this.initDraggable(); 733 | }; 734 | 735 | TemplatesEdit.prototype.addTab = function(tab, d, f) { 736 | var tabs = {}, id = 'tab' + new Date().getTime(); 737 | f = typeof f !== 'undefined' ? f : false; 738 | tabs[id] = { 739 | id: id, 740 | title: 'New tab', 741 | default: d, 742 | 'col:0:12': {} 743 | }; 744 | if (f) { 745 | id = '#Static'; 746 | tabs[id] = { 747 | id: id, 748 | title: 'Static', 749 | default: false, 750 | 'col:0:12': {} 751 | }; 752 | } 753 | if (tab) { 754 | tab.insertAdjacentHTML('afterend', this.renderTabs(tabs)); 755 | } else { 756 | this.el.innerHTML = this.renderTabs(tabs); 757 | } 758 | this.initDraggable(); 759 | }; 760 | 761 | TemplatesEdit.prototype.delItem = function(el) { 762 | var name = el.getAttribute('data-name'); 763 | if (el.classList.contains('b-category')) { 764 | if (!this.elUnusedCategories.querySelectorAll('[data-name="' + name + '"]').length) { 765 | this.elUnusedCategories.appendChild(el); 766 | } 767 | this.elUnusedTvars.querySelectorAll('[data-category="' + el.getAttribute('data-id') + '"]:not(.b-add)').forEach(function(el) { 768 | el.hidden = false; 769 | }); 770 | } else { 771 | el.classList.remove('b-required-checked'); 772 | el.classList.remove('b-readonly-checked'); 773 | if (el.getAttribute('data-type') === 'tv') { 774 | if (!this.elUnusedTvars.querySelectorAll('[data-name="' + name + '"]:not([hidden])').length) { 775 | this.elUnusedTvars.appendChild(el); 776 | } 777 | } else { 778 | if (!this.elUnusedFields.querySelectorAll('[data-name="' + name + '"]').length) { 779 | this.elUnusedFields.appendChild(el); 780 | } 781 | } 782 | } 783 | }; 784 | 785 | TemplatesEdit.prototype.draggable = function(els, group) { 786 | var self = this; 787 | if (els.length) { 788 | els.forEach(function(el) { 789 | Sortable.create(el, { 790 | animation: 150, 791 | draggable: '.b-draggable', 792 | dragClass: 'placeholder', 793 | ghostClass: 'active', 794 | selectedClass: 'placeholder', 795 | filter: '.b-settings, .b-tab-title-input, .b-btn-settings, .b-btn-add', 796 | preventOnFilter: false, 797 | //handle: '.b-move', 798 | group: group, 799 | onEnd: function(e) { 800 | if (e.pullMode) { 801 | if (e.from.classList.contains('b-tab') && !e.from.querySelectorAll('.row-col-wrap').length) { 802 | self.addRow(e.from, true); 803 | } 804 | } 805 | self.build(); 806 | } 807 | }); 808 | }); 809 | } 810 | }; 811 | 812 | TemplatesEdit.prototype.draggableItems = function(els, group) { 813 | var self = this; 814 | if (els.length) { 815 | els.forEach(function(el) { 816 | Sortable.create(el, { 817 | animation: 150, 818 | draggable: '.b-draggable', 819 | dragClass: 'placeholder', 820 | ghostClass: 'active', 821 | selectedClass: 'placeholder', 822 | //handle: '.b-move', 823 | group: { 824 | name: group, 825 | put: false 826 | }, 827 | sort: false, 828 | onEnd: function(e) { 829 | if (e.pullMode) { 830 | var name = e.item.getAttribute('data-name'), title = e.item.getAttribute('data-title'), id = e.item.getAttribute('data-id'); 831 | if (e.item.classList.contains('b-category')) { 832 | e.item.insertAdjacentHTML('afterend', self.renderCategory({ 833 | title: title 834 | }, id)); 835 | } else { 836 | e.item.insertAdjacentHTML('afterend', self.renderField({}, name)); 837 | } 838 | if (id) { 839 | self.elUnusedTvars.querySelectorAll('[data-category="' + id + '"]').forEach(function(el) { 840 | el.hidden = true; 841 | }); 842 | } 843 | e.item.parentNode.removeChild(e.item); 844 | } 845 | self.build(); 846 | } 847 | }); 848 | }); 849 | } 850 | }; 851 | 852 | TemplatesEdit.prototype.resizable = function(els) { 853 | if (els.length) { 854 | var self = this, cols, widthCol; 855 | els.forEach(function(el) { 856 | var _w, parent, drag = false; 857 | 858 | el.onmousedown = function(e) { 859 | if (e.button) { 860 | return true; 861 | } 862 | e.preventDefault(); 863 | e.stopPropagation(); 864 | window.getSelection().removeAllRanges(); 865 | 866 | parent = e.target.parentElement; 867 | parent.pos = parent.getBoundingClientRect(); 868 | widthCol = self.el.offsetWidth / 12; 869 | cols = Math.round(parent.offsetWidth / widthCol); 870 | _w = e.clientX - parent.pos.left; 871 | 872 | document.onmousemove = function(e) { 873 | _w = e.clientX - parent.pos.left; 874 | if (Math.round(_w / widthCol) !== cols) { 875 | cols = Math.round(_w / widthCol); 876 | if (cols >= 12) { 877 | cols = 12; 878 | }/* else if (cols < 4) { 879 | cols = 4; 880 | }*/ 881 | drag = true; 882 | parent.className = 'row col row-col-wrap b-draggable col-' + cols; 883 | } 884 | }; 885 | 886 | document.onmouseup = function(e) { 887 | if (!drag) { 888 | return false; 889 | } 890 | drag = false; 891 | cols = Math.round(parent.offsetWidth / widthCol); 892 | parent.className = 'row col row-col-wrap b-draggable col-' + cols; 893 | var name = parent.getAttribute('data-name').split(':'); 894 | name[2] = cols; 895 | parent.setAttribute('data-name', name.join(':')); 896 | if (parent.lastElementChild.classList.contains('b-settings') && parent.lastElementChild) { 897 | parent.removeChild(parent.lastElementChild); 898 | } 899 | self.build(); 900 | document.onmousemove = null; 901 | e.preventDefault(); 902 | e.stopPropagation(); 903 | }; 904 | }; 905 | }); 906 | } 907 | }; 908 | 909 | TemplatesEdit.prototype.renderTabs = function(data) { 910 | var out = ''; 911 | for (var k in data) { 912 | if (data.hasOwnProperty(k)) { 913 | var tab = data[k], 914 | title = tab['title'] || ''; 915 | out += this.tpl(this.templates.tab, { 916 | id: k, 917 | title: title, 918 | class: k === '#Static' ? '' : 'b-draggable', 919 | checked: tab['default'] ? 'checked' : '', 920 | content: this.renderTab(data[k]), 921 | settings: this.escapeHtml(JSON.stringify({ 922 | id: k, 923 | title: title, 924 | default: tab['default'] 925 | })) 926 | }); 927 | } 928 | } 929 | return out; 930 | }; 931 | 932 | TemplatesEdit.prototype.renderTab = function(data) { 933 | var out = '', type, id; 934 | for (var k in data) { 935 | if (data.hasOwnProperty(k)) { 936 | [type, id] = k.split(':'); 937 | switch (type) { 938 | case 'category': 939 | out += this.renderCategory(data[k], id); 940 | break; 941 | 942 | case 'col': 943 | out += this.renderCol(data[k], k); 944 | break; 945 | 946 | case 'fields': 947 | out += this.renderFields(data[k]); 948 | break; 949 | 950 | default: 951 | break; 952 | } 953 | } 954 | } 955 | return out; 956 | }; 957 | 958 | TemplatesEdit.prototype.renderCategory = function(data, id) { 959 | return this.tpl(this.templates.category, { 960 | name: 'category:' + id, 961 | id: id, 962 | title: this.data_categories[id]['title'] 963 | }); 964 | }; 965 | 966 | TemplatesEdit.prototype.renderCol = function(data, name) { 967 | var type, id, col, settings = {}; 968 | [type, id, col] = name.split(':'); 969 | if (typeof data['settings'] !== 'undefined') { 970 | settings = data['settings']; 971 | delete data['settings']; 972 | } 973 | return this.tpl(this.templates.col, { 974 | col: col, 975 | name: name, 976 | settings: this.escapeHtml(JSON.stringify(settings)), 977 | content: this.renderTab(data, settings) 978 | }); 979 | }; 980 | 981 | TemplatesEdit.prototype.renderFields = function(data) { 982 | var out = ''; 983 | for (var k in data) { 984 | if (data.hasOwnProperty(k)) { 985 | out += this.renderField(data[k], k); 986 | } 987 | } 988 | return out; 989 | }; 990 | 991 | TemplatesEdit.prototype.renderField = function(data, name) { 992 | var title, help, category; 993 | if (this.data_fields[name]) { 994 | title = this.data_fields[name]['title']; 995 | help = this.data_fields[name]['help']; 996 | category = ''; 997 | } 998 | if (this.data_tvars[name]) { 999 | title = this.data_tvars[name]['title']; 1000 | help = this.data_tvars[name]['help']; 1001 | category = this.data_tvars[name]['category']; 1002 | } 1003 | return this.tpl(this.templates.field, { 1004 | name: this.escapeHtml(name), 1005 | type: this.data_tvars[name] ? 'tv' : 'field', 1006 | category: category, 1007 | title: this.escapeHtml(data['title'] && data['title'] !== title ? data['title'] : title), 1008 | help: this.escapeHtml(data['help'] && data['help'] !== help ? data['help'] : help), 1009 | default: { 1010 | title: this.escapeHtml(title), 1011 | help: this.escapeHtml(help), 1012 | value: this.escapeHtml(data['default']) 1013 | }, 1014 | required: data['required'] ? 'checked' : '', 1015 | readonly: data['readonly'] ? 'checked' : '', 1016 | settings: this.escapeHtml(JSON.stringify(data)) 1017 | }); 1018 | }; 1019 | 1020 | TemplatesEdit.prototype.import = function(el) { 1021 | if (typeof el.files !== 'undefined') { 1022 | var self = this, file = el.files[0], reader = new FileReader(); 1023 | if (file && ~file.name.indexOf('.json')) { 1024 | reader.onload = function() { 1025 | self.dataEl.value = reader.result; 1026 | self.init(); 1027 | self.initDraggable(); 1028 | }; 1029 | reader.readAsText(file); 1030 | } 1031 | } 1032 | }; 1033 | 1034 | TemplatesEdit.prototype.export = function() { 1035 | this.build(); 1036 | var blob = new Blob([this.dataEl.value]); 1037 | var a = document.createElement('a'); 1038 | a.href = URL.createObjectURL.call(this, blob, { 1039 | type: 'text/json;charset=utf-8;' 1040 | }); 1041 | a.download = 'template.json'; 1042 | document.body.appendChild(a); 1043 | a.click(); 1044 | document.body.removeChild(a); 1045 | }; 1046 | 1047 | TemplatesEdit.prototype.tpl = function(template, data, isDom, cleanKeys) { 1048 | data = data || {}; 1049 | isDom = isDom || false; 1050 | if (typeof cleanKeys === 'undefined') { 1051 | cleanKeys = true; 1052 | } 1053 | var html = template.replace(/\{([\w\.]*)\}/g, function(str, key) { 1054 | var keys = key.split('.'), value = data[keys.shift()]; 1055 | [].slice.call(keys).forEach(function(item) { 1056 | value = typeof value !== 'undefined' && value[item] || ''; 1057 | }); 1058 | return (value === null || value === undefined) ? (cleanKeys ? '' : str) : value; 1059 | }); 1060 | if (typeof data === 'boolean') { 1061 | isDom = data; 1062 | } 1063 | if (isDom) { 1064 | var fragment = document.createElement('div'); 1065 | fragment.innerHTML = html; 1066 | return fragment.children[0]; 1067 | } else { 1068 | return html; 1069 | } 1070 | }; 1071 | 1072 | TemplatesEdit.prototype.serialize = function(form, attrName, ignoreEmpty, string) { 1073 | var serialized = string ? [] : {}; 1074 | string = string || false; 1075 | attrName = attrName || 'name'; 1076 | ignoreEmpty = ignoreEmpty || true; 1077 | if (form.tagName !== 'FORM') { 1078 | form.elements = form.querySelectorAll('input, select, textarea, button'); 1079 | } 1080 | for (var i = 0; i < form.elements.length; i++) { 1081 | var field = form.elements[i], name = field.getAttribute(attrName); 1082 | if (!name || field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') continue; 1083 | if (field.type === 'select-multiple') { 1084 | for (var n = 0; n < field.options.length; n++) { 1085 | if ((!field.options[n].selected) || (field.options[n].value === '' && ignoreEmpty)) continue; 1086 | if (string) { 1087 | serialized.push(encodeURIComponent(name) + '=' + encodeURIComponent(field.options[n].value)); 1088 | } else { 1089 | serialized[name] = field.options[n].value; 1090 | } 1091 | } 1092 | } else if ((field.type !== 'checkbox' && field.type !== 'radio') || field.checked) { 1093 | if (string) { 1094 | serialized.push(encodeURIComponent(name) + '=' + encodeURIComponent(field.value)); 1095 | } else { 1096 | if (field.value === '' && ignoreEmpty) { 1097 | continue; 1098 | } 1099 | serialized[name] = field.value; 1100 | } 1101 | } 1102 | } 1103 | return string ? serialized.join('&') : serialized; 1104 | }; 1105 | 1106 | TemplatesEdit.prototype.escapeHtml = function(text) { 1107 | text = text || ''; 1108 | return text.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); 1109 | }; 1110 | 1111 | return new TemplatesEdit(el, config); 1112 | }; 1113 | -------------------------------------------------------------------------------- /assets/plugins/templatesedit/class/templatesedit.class.php: -------------------------------------------------------------------------------- 1 | doc = $doc; 81 | $this->evo = evolutionCMS(); 82 | $this->basePath = dirname(__DIR__) . '/'; 83 | $this->params = $this->evo->event->params; 84 | $this->params['showTvImage'] = isset($this->params['showTvImage']) && $this->params['showTvImage'] == 'yes'; 85 | $this->params['excludeTvCategory'] = !empty($this->params['excludeTvCategory']) ? array_map( 86 | 'trim', 87 | explode(',', $this->params['excludeTvCategory']) 88 | ) : []; 89 | $this->params['showTvName'] = isset($this->params['showTvName']) && $this->params['showTvName'] == 'yes'; 90 | // default 91 | $this->params['default.tab'] = false; 92 | $this->params['role'] = $_SESSION['mgrRole']; 93 | $this->params['col.settings'] = []; 94 | } 95 | 96 | /** 97 | * @param array $doc 98 | * 99 | * @return static|null 100 | */ 101 | public static function getInstance(array $doc = []): ?templatesedit 102 | { 103 | if (self::$instance === null) { 104 | self::$instance = new static($doc); 105 | } 106 | 107 | return self::$instance; 108 | } 109 | 110 | /** 111 | * @return string 112 | */ 113 | public function renderTemplate(): string 114 | { 115 | $this->setDefaults(); 116 | $this->getConfig(); 117 | $this->getTemplateVariables(); 118 | 119 | return $this->view('document', [ 120 | 'content' => $this->renderTabs(), 121 | ]); 122 | } 123 | 124 | /** 125 | * @return string 126 | */ 127 | public function renderAfterTemplate(): string 128 | { 129 | global $richtexteditorIds, $richtexteditorOptions; 130 | 131 | $out = ''; 132 | 133 | if (isset($this->config['#Static']) && $tabContent = $this->renderTab($this->config['#Static'])) { 134 | $out .= $this->view('element', [ 135 | 'id' => 'Static', 136 | 'class' => 'col p-1', 137 | 'content' => $this->view('element', [ 138 | 'class' => 'row form-row', 139 | 'content' => $tabContent, 140 | ]), 141 | ]); 142 | } 143 | 144 | $default_fields = array_diff(array_keys($this->defaultFields), $this->addedFields); 145 | if ($default_fields) { 146 | $out .= ''; 147 | foreach ($default_fields as $fieldName) { 148 | if (in_array($fieldName, ['introtext', 'content'])) { 149 | continue; 150 | } 151 | $out .= $this->form('input', [ 152 | 'type' => 'hidden', 153 | 'name' => $fieldName, 154 | 'value' => isset($this->doc[$fieldName]) ? htmlspecialchars( 155 | (string) $this->doc[$fieldName], 156 | ENT_COMPAT 157 | ) : '', 158 | ]); 159 | } 160 | $out .= ''; 161 | } 162 | 163 | # fix https://github.com/64j/templatesedit3/issues/60 164 | $richtexteditorIds = array_map( 165 | function ($item) { 166 | if (in_array('ta', $item, true) && count($item) > 1) { 167 | $item = array_unique(array_merge(['ta'], $item)); 168 | } 169 | 170 | return $item; 171 | }, 172 | $this->richtexteditorIds 173 | ); 174 | $richtexteditorOptions = $this->richtexteditorOptions; 175 | 176 | return $out; 177 | } 178 | 179 | /** 180 | * 181 | */ 182 | protected function setDefaults(): void 183 | { 184 | $this->doc['template'] = $this->getTemplateId(); 185 | 186 | if (!isset($_REQUEST['id'])) { 187 | $this->doc['id'] = 0; 188 | } 189 | 190 | if (isset($_REQUEST['pid'])) { 191 | $this->doc['parent'] = $_REQUEST['pid']; 192 | } 193 | 194 | if (!isset($this->doc['pagetitle'])) { 195 | $this->doc['pagetitle'] = ''; 196 | } 197 | 198 | if (!isset($this->doc['longtitle'])) { 199 | $this->doc['longtitle'] = ''; 200 | } 201 | 202 | if (!isset($this->doc['description'])) { 203 | $this->doc['description'] = ''; 204 | } 205 | 206 | if (!isset($this->doc['menutitle'])) { 207 | $this->doc['menutitle'] = ''; 208 | } 209 | 210 | if (!isset($this->doc['introtext'])) { 211 | $this->doc['introtext'] = ''; 212 | } 213 | 214 | if (!isset($this->doc['content'])) { 215 | $this->doc['content'] = ''; 216 | } 217 | 218 | if (!isset($this->doc['alias'])) { 219 | $this->doc['alias'] = ''; 220 | } 221 | 222 | if (!isset($this->doc['link_attributes'])) { 223 | $this->doc['link_attributes'] = ''; 224 | } 225 | 226 | if (!isset($this->doc['contentType'])) { 227 | $this->doc['contentType'] = 'text/html'; 228 | } 229 | 230 | if ($this->evo->getManagerApi()->action == 85 || ($_REQUEST['isfolder'] ?? null) == 1) { 231 | $this->doc['isfolder'] = 1; 232 | } 233 | 234 | if (in_array($this->evo->getManagerApi()->action, [85, 4])) { 235 | $this->doc['type'] = 'document'; 236 | } 237 | 238 | if ($this->evo->getManagerApi()->action == 72) { 239 | $this->doc['type'] = 'reference'; 240 | } 241 | 242 | if (($this->doc['published'] ?? null) == 1 || 243 | (!isset($this->doc['published']) && $this->evo->getConfig('publish_default') == 1) 244 | ) { 245 | $this->doc['published'] = 1; 246 | } 247 | 248 | if (!isset($this->doc['alias_visible'])) { 249 | $this->doc['alias_visible'] = 1; 250 | } 251 | 252 | if (($this->doc['searchable'] ?? null) == 1 || 253 | (!isset($this->doc['searchable']) && $this->evo->getConfig('search_default') == 1) 254 | ) { 255 | $this->doc['searchable'] = 1; 256 | } 257 | 258 | if (($this->doc['cacheable'] ?? null) == 1 || 259 | (!isset($this->doc['cacheable']) && $this->evo->getConfig('cache_default') == 1) 260 | ) { 261 | $this->doc['cacheable'] = 1; 262 | } 263 | 264 | if (($this->doc['richtext'] ?? null) == 0 && $this->evo->getManagerApi()->action == 27) { 265 | $this->doc['richtext'] = 0; 266 | } else { 267 | $this->doc['richtext'] = 1; 268 | } 269 | 270 | $this->doc['syncsite'] = 1; 271 | 272 | $this->defaultFields = $this->getDefaultFields(); 273 | } 274 | 275 | /** 276 | * @return int 277 | */ 278 | protected function getTemplateId(): int 279 | { 280 | if (isset($_REQUEST['newtemplate'])) { 281 | $this->doc['template'] = $_REQUEST['newtemplate']; 282 | } elseif (!isset($this->doc['template'])) { 283 | $this->doc['template'] = getDefaultTemplate(); 284 | } 285 | 286 | $this->doc['template_alias'] = ''; 287 | if ($this->doc['template']) { 288 | $tpl = $this->evo->getDatabase()->getRow( 289 | $this->evo->getDatabase()->select( 290 | '*', 291 | $this->evo->getFullTableName('site_templates'), 292 | 'id = ' . (int) $this->doc['template'] 293 | ) 294 | ); 295 | if (!empty($tpl['templatealias'])) { 296 | $this->doc['template_alias'] = $tpl['templatealias']; 297 | } 298 | } 299 | 300 | return (int) $this->doc['template']; 301 | } 302 | 303 | /** 304 | * @return array 305 | */ 306 | protected function getDefaultFields(): array 307 | { 308 | $this->defaultFields = require_once $this->basePath . 'configs/fields.php'; 309 | 310 | if (version_compare($this->evo->getConfig('settings_version'), '3.1.0') >= 0) { 311 | $position = array_search('donthit', array_keys($this->defaultFields)); 312 | $fieldsBefore = array_slice($this->defaultFields, 0, $position); 313 | $fieldsAfter = array_slice($this->defaultFields, $position + 1); 314 | $addedFields = [ 315 | 'hide_from_tree' => $this->defaultFields['donthit'], 316 | ]; 317 | $this->defaultFields = $fieldsBefore + $addedFields + $fieldsAfter; 318 | } 319 | 320 | if (file_exists($this->basePath . 'configs/custom_fields.php')) { 321 | $this->defaultFields += require_once $this->basePath . 'configs/custom_fields.php'; 322 | } 323 | 324 | return $this->defaultFields; 325 | } 326 | 327 | /** 328 | * @return array 329 | */ 330 | protected function getConfig(): array 331 | { 332 | $this->config = []; 333 | $json = ''; 334 | 335 | if (file_exists( 336 | $this->basePath . 'configs/template__' . $this->doc['template'] . '__' . $this->params['role'] . '.json' 337 | ) 338 | ) { 339 | $json = $this->basePath . 'configs/template__' . $this->doc['template'] . '__' . $this->params['role'] . 340 | '.json'; 341 | } elseif (file_exists($this->basePath . 'configs/template__' . $this->doc['template'] . '__1.json')) { 342 | $json = $this->basePath . 'configs/template__' . $this->doc['template'] . '__1.json'; 343 | } elseif ($file = glob($this->basePath . 'configs/template_*_default.json')) { 344 | $json = $file[0]; 345 | } 346 | 347 | if ($json) { 348 | $this->config = json_decode(file_get_contents($json), true); 349 | } else { 350 | if (file_exists($this->basePath . 'configs/template__' . $this->doc['template_alias'] . '.php')) { 351 | $this->config = 352 | require_once $this->basePath . 'configs/template__' . $this->doc['template_alias'] . '.php'; 353 | } elseif (file_exists($this->basePath . 'configs/template__' . $this->doc['template'] . '.php')) { 354 | $this->config = require_once $this->basePath . 'configs/template__' . $this->doc['template'] . '.php'; 355 | } else { 356 | $this->config = require_once $this->basePath . 'configs/template__default.php'; 357 | } 358 | } 359 | 360 | return $this->config; 361 | } 362 | 363 | /** 364 | * @return array 365 | */ 366 | public function getTemplateVariables(): ?array 367 | { 368 | if (is_array($this->tvars)) { 369 | return $this->tvars; 370 | } 371 | 372 | $docgrp = ''; 373 | if (!empty($_SESSION['mgrDocgroups'])) { 374 | $docgrp = implode(',', $_SESSION['mgrDocgroups']); 375 | } 376 | 377 | $sql = $this->evo->getDatabase()->select( 378 | ' 379 | DISTINCT tv.*, tvc.value, tv.default_text, tvtpl.rank', 380 | $this->evo->getFullTableName('site_tmplvars') . ' AS tv 381 | INNER JOIN ' . $this->evo->getFullTableName('site_tmplvar_templates') . ' AS tvtpl ON tvtpl.tmplvarid = tv.id 382 | LEFT JOIN ' . $this->evo->getFullTableName('site_tmplvar_contentvalues') . 383 | ' AS tvc ON tvc.tmplvarid=tv.id AND tvc.contentid=\'' . $this->doc['id'] . '\' 384 | LEFT JOIN ' . $this->evo->getFullTableName('site_tmplvar_access') . ' AS tva ON tva.tmplvarid=tv.id', 385 | 'tvtpl.templateid=\'' . $this->doc['template'] . '\' AND (1=\'' . $_SESSION['mgrRole'] . 386 | '\' OR 1 = CASE WHEN tva.documentgroup IS NULL THEN 1 ELSE 0 END' . 387 | (!$docgrp ? '' : ' OR tva.documentgroup IN (' . $docgrp . ')') . ')', 388 | 'tvtpl.rank, tv.rank, tv.id' 389 | ); 390 | 391 | while ($row = $this->evo->getDatabase()->getRow($sql)) { 392 | if ($row['value'] == '') { 393 | $row['value'] = $row['default_text']; 394 | } 395 | $this->categories[$row['category']][$row['name']] = $row; 396 | $this->tvars[$row['name']] = $row; 397 | } 398 | 399 | $categories = $this->categories; 400 | 401 | foreach ($this->config as $tabId => &$tab) { 402 | if (!empty($tab['default'])) { 403 | $this->params['default.tab'] = $tabId; 404 | } 405 | 406 | if (isset($tab['fields'])) { 407 | $tab['col:0:12']['fields:0'] = $tab['fields']; 408 | unset($tab['fields']); 409 | } 410 | 411 | foreach ($tab as $colId => $col) { 412 | if (is_array($col)) { 413 | foreach ($col as $fieldsId => $fields) { 414 | if (substr((string) $fieldsId, 0, 7) == 'fields:') { 415 | foreach ($fields as $key => $field) { 416 | if (isset($this->tvars[$key])) { 417 | unset($categories[$this->tvars[$key]['category']][$key]); 418 | unset($this->categories[$this->tvars[$key]['category']][$key]); 419 | } 420 | } 421 | } 422 | if (substr((string) $fieldsId, 0, 9) == 'category:') { 423 | [, $categoryId] = explode(':', $fieldsId); 424 | unset($categories[$categoryId]); 425 | if (empty($this->categories[$categoryId])) { 426 | unset($this->config[$tabId][$colId][$fieldsId]); 427 | } 428 | } 429 | } 430 | if (empty($this->config[$tabId][$colId])) { 431 | unset($this->config[$tabId][$colId]); 432 | } 433 | } 434 | } 435 | 436 | if (empty($this->config[$tabId])) { 437 | unset($this->config[$tabId]); 438 | } 439 | } 440 | 441 | if (!empty($categories) && !empty($this->params['default.tab'])) { 442 | if (!isset($this->config[$this->params['default.tab']]['col:0:12']['fields:0'])) { 443 | $this->config[$this->params['default.tab']]['col:0:12']['fields:0'] = []; 444 | } 445 | foreach ($categories as $k => $fields) { 446 | if (!in_array($k, $this->params['excludeTvCategory'])) { 447 | foreach ($fields as $key => $field) { 448 | $this->config[$this->params['default.tab']]['col:0:12']['fields:0'] += [$key => $field]; 449 | } 450 | unset($this->config[$this->params['default.tab']]['col:0:12']['category:' . $k]); 451 | } 452 | } 453 | } 454 | 455 | return $this->tvars; 456 | } 457 | 458 | /** 459 | * @return string 460 | */ 461 | protected function renderTabs(): string 462 | { 463 | $out = ''; 464 | $this->addedFields = []; 465 | 466 | require_once MODX_MANAGER_PATH . 'includes/tmplvars.inc.php'; 467 | require_once MODX_MANAGER_PATH . 'includes/tmplvars.commands.inc.php'; 468 | 469 | foreach ($this->config as $tabName => $tab) { 470 | if ($tab['title'] && $tabName != '#Static') { 471 | $tabContent = $this->renderTab($tab); 472 | if ($tabContent) { 473 | $out .= $this->view('tab', [ 474 | 'name' => $tabName, 475 | 'title' => $tab['title'], 476 | 'tabsObject' => 'tpSettings', 477 | 'content' => $this->view('element', [ 478 | 'class' => 'row form-row', 479 | 'content' => $tabContent, 480 | ]), 481 | ]); 482 | } else { 483 | unset($this->config[$tabName]); 484 | } 485 | } 486 | } 487 | 488 | return $out; 489 | } 490 | 491 | /** 492 | * @param array $tab 493 | * @param array $settings 494 | * 495 | * @return string 496 | */ 497 | protected function renderTab(array $tab, array $settings = []): string 498 | { 499 | $out = ''; 500 | foreach ($tab as $k => $fields) { 501 | [$type, $id] = explode(':', $k . ':'); 502 | switch ($type) { 503 | case 'category': 504 | if (!empty($this->categories[$id])) { 505 | $out .= $this->renderTab([ 506 | 'fields' => $this->categories[$id], 507 | ], $settings); 508 | } 509 | break; 510 | 511 | case 'col': 512 | $out .= $this->renderCol($fields, $k); 513 | break; 514 | 515 | case 'fields': 516 | $out .= $this->renderFields($fields, $k, $settings); 517 | break; 518 | 519 | default: 520 | break; 521 | } 522 | } 523 | 524 | return $out; 525 | } 526 | 527 | /** 528 | * @param array $data 529 | * @param string $key 530 | * 531 | * @return string 532 | */ 533 | protected function renderCol(array $data = [], string $key = ''): string 534 | { 535 | $settings = []; 536 | $title = ''; 537 | [, , $col] = explode(':', $key); 538 | 539 | if (isset($data['settings'])) { 540 | $settings = $data['settings']; 541 | 542 | if (isset($settings['title']) && $settings['title'] != '') { 543 | $title = $this->view('element', [ 544 | 'class' => 'col-12', 545 | 'content' => $settings['title'], 546 | ]); 547 | } 548 | 549 | unset($data['settings']); 550 | } 551 | 552 | return $this->view('element', [ 553 | 'class' => 'row-col col-lg-' . $col . ($col > 3 && $col < 9 ? ' col-md-6 col-12' : ' col-12'), 554 | 'content' => $title . $this->renderTab($data, $settings), 555 | ]); 556 | } 557 | 558 | /** 559 | * @param array $fields 560 | * @param string $tabName 561 | * @param array $settings 562 | * 563 | * @return string 564 | */ 565 | protected function renderFields(array $fields, string $tabName, array $settings = []): string 566 | { 567 | $out = ''; 568 | 569 | foreach ($fields as $fieldName => $field) { 570 | if (isset($this->tvars[$fieldName])) { 571 | $field = array_merge($this->tvars[$fieldName], $field); 572 | } 573 | if (!isset($field['class'])) { 574 | $field['class'] = ''; 575 | } 576 | if (isset($field['type']) && ($field['type'] == 'split' || $field['type'] == 'splitter')) { 577 | $out .= $field['title']; 578 | } else { 579 | if (empty($this->doc['id']) && !isset($field['id'])) { 580 | if (isset($field['default'])) { 581 | $this->doc[$fieldName] = $field['default']; 582 | } 583 | } 584 | if (!isset($field['id']) && !isset($this->defaultFields[$fieldName])) { 585 | unset($this->config[$tabName]['fields'][$fieldName]); 586 | } else { 587 | if (isset($this->defaultFields[$fieldName])) { 588 | if (!isset($field['title'])) { 589 | $field['title'] = $this->defaultFields[$fieldName]['title']; 590 | } 591 | if (!isset($field['help'])) { 592 | $field['help'] = $this->defaultFields[$fieldName]['help']; 593 | } 594 | $this->addedFields[] = $fieldName; 595 | } 596 | $render_field = $this->renderField($fieldName, $field, $settings); 597 | if ($render_field) { 598 | $out .= $render_field; 599 | } 600 | } 601 | } 602 | } 603 | 604 | return $out; 605 | } 606 | 607 | /** 608 | * @param string $key 609 | * @param array $data 610 | * @param array $settings 611 | * 612 | * @return string 613 | */ 614 | protected function renderField(string $key, array $data, array $settings = []): string 615 | { 616 | global $_lang; 617 | 618 | $out = ''; 619 | $field = ''; 620 | $value = ''; 621 | $rowClass = ''; 622 | $leftClass = ''; 623 | $rightClass = ''; 624 | $labelFor = $key; 625 | $isTv = false; 626 | $name = $key; 627 | 628 | $data['id'] = $data['id'] ?? $key; 629 | $data['name'] = $data['name'] ?? $key; 630 | $data['title'] = $data['title'] ?? ''; 631 | $data['caption'] = $data['caption'] ?? ''; 632 | $data['help'] = 633 | isset($data['help']) && $data['help'] != '' ? '' : ''; 635 | $data['description'] = 636 | isset($data['description']) && $data['description'] != '' ? '' . 637 | $data['description'] . '' : ''; 638 | $data['pattern'] = isset($data['pattern']) ? ' pattern="' . $data['pattern'] . '"' : ''; 639 | $data['required'] = !empty($data['required']) ? ' required' : ''; 640 | $data['readonly'] = !empty($data['readonly']) ? ' readonly' : ''; 641 | $data['elements'] = $data['elements'] ?? ''; 642 | $data['type'] = $data['type'] ?? null; 643 | 644 | if (isset($this->defaultFields[$key])) { 645 | if (isset($data['type'])) { 646 | $rowClass .= ' form-row-' . $data['type']; 647 | $data['default'] = $data['default'] ?? ''; 648 | if (in_array($key, ['weblink', 'content'])) { 649 | if (($key == 'weblink' && $this->doc['type'] != 'reference') || 650 | ($key == 'content' && $this->doc['type'] == 'reference') 651 | ) { 652 | return ''; 653 | } 654 | $name = $key = 'ta'; 655 | $data['value'] = $this->doc['content'] ?? $data['default']; 656 | } else { 657 | $data['value'] = $this->doc[$key] ?? $data['default']; 658 | } 659 | $field = renderFormElement( 660 | $data['type'], 661 | $name, 662 | $data['default'], 663 | $data['elements'], 664 | $data['value'], 665 | '', 666 | $data 667 | ); 668 | $field = str_replace([' id="tv', ' name="tv'], [' id="', $data['required'] .$data['readonly'] . ' name="'], $field); 669 | if (!empty($data['rows']) && is_numeric($data['rows'])) { 670 | $field = preg_replace('/rows="(.*?)"/is', 'rows="' . $data['rows'] . '"', $field); 671 | } 672 | switch ($data['type']) { 673 | case 'date': 674 | $field = str_replace(['DatePicker', 'onclick="', '.elements[\'tv'], 675 | ['form-control DatePicker', 'class="form-control" onclick="', '.elements[\''], 676 | $field); 677 | break; 678 | 679 | case 'text': 680 | case 'textarea': 681 | case 'textareamini': 682 | case 'richtext': 683 | case 'listbox': 684 | case 'listbox-multiple': 685 | case 'option': 686 | case 'checkbox': 687 | case 'number': 688 | $field = str_replace('name="', 'class="form-control" name="', $field); 689 | break; 690 | 691 | case 'image': 692 | case 'file': 693 | $field = str_replace(['name="', 'type="button"', 'BrowseServer(\'tv', 'BrowseFileServer(\'tv'], 694 | [ 695 | 'class="form-control" name="', 696 | 'class="form-control" type="button"', 697 | 'BrowseServer(\'', 698 | 'BrowseFileServer(\'', 699 | ], 700 | $field); 701 | break; 702 | 703 | case 'dropdown': 704 | $field = str_replace(['name="', 'size="1"'], ['class="form-control" name="', ''], $field); 705 | break; 706 | 707 | default: 708 | break; 709 | } 710 | } else { 711 | switch ($key) { 712 | case 'published': 713 | case 'richtext': 714 | case 'donthit': 715 | case 'hide_from_tree': 716 | case 'searchable': 717 | case 'cacheable': 718 | case 'syncsite': 719 | case 'alias_visible': 720 | case 'isfolder': 721 | case 'hidemenu': 722 | $rowClass .= ' form-row-checkbox'; 723 | $labelFor .= 'check'; 724 | $value = empty($this->doc[$key]) ? 0 : 1; 725 | if ($key == 'donthit' || $key == 'hide_from_tree' || $key == 'hidemenu') { 726 | $checked = !$value ? 'checked' : ''; 727 | } else { 728 | $checked = $value ? 'checked' : ''; 729 | } 730 | $field .= $this->form('input', [ 731 | 'type' => 'checkbox', 732 | 'name' => $key . 'check', 733 | 'class' => 'form-checkbox form-control ' . $data['class'], 734 | 'attr' => 'onclick="changestate(document.mutate.' . $key . ');" ' . $checked . 735 | $data['required'].$data['readonly'], 736 | ]); 737 | $field .= $this->form('input', [ 738 | 'type' => 'hidden', 739 | 'name' => $key, 740 | 'value' => $value, 741 | ]); 742 | break; 743 | 744 | case 'pub_date': 745 | case 'unpub_date': 746 | case 'createdon': 747 | case 'editedon': 748 | $value = (isset($this->doc[$key]) && $this->doc[$key] == 0) || !isset($this->doc[$key]) ? '' 749 | : $this->doc[$key]; 750 | $rowClass .= ' form-row-date'; 751 | $field .= $this->form('date', [ 752 | 'name' => $key, 753 | 'value' => $value ? $this->evo->toDateFormat( 754 | !is_numeric($value) ? strtotime($value) : $value 755 | ) : '', 756 | 'class' => $data['class'], 757 | 'placeholder' => $this->evo->getConfig('datetime_format') . ' HH:MM:SS', 758 | 'icon' => 'fa fa-calendar-times-o', 759 | 'icon.title' => $_lang['remove_date'], 760 | ]); 761 | break; 762 | 763 | case 'menusort': 764 | case 'menuindex': 765 | $rightClass .= 'input-group'; 766 | $field .= $this->view('element', [ 767 | 'class' => 'input-group-prepend', 768 | 'content' => $this->view('element', [ 769 | 'tag' => 'span', 770 | 'class' => 'btn btn-secondary', 771 | 'attr' => 'onclick="var elm = document.mutate.menuindex;var v=parseInt(elm.value+\'\')-1;elm.value=v>0? v:0;elm.focus();documentDirty=true;return false;" style="cursor: pointer;"', 772 | 'content' => '', 773 | ]) . $this->view('element', [ 774 | 'tag' => 'span', 775 | 'class' => 'btn btn-secondary', 776 | 'attr' => 'onclick="var elm = document.mutate.menuindex;var v=parseInt(elm.value+\'\')+1;elm.value=v>0? v:0;elm.focus();documentDirty=true;return false;" style="cursor: pointer;"', 777 | 'content' => '', 778 | ]), 779 | ]) . $this->form('input', [ 780 | 'name' => $key, 781 | 'value' => $this->doc['menuindex'], 782 | 'maxlength' => 6, 783 | ]); 784 | break; 785 | 786 | case 'introtext': 787 | $field .= $this->form('textarea', [ 788 | 'name' => 'introtext', 789 | 'value' => htmlspecialchars(stripslashes($this->doc['introtext']), ENT_COMPAT), 790 | 'class' => $data['class'], 791 | 'rows' => empty($data['rows']) ? 3 : $data['rows'], 792 | ]); 793 | break; 794 | 795 | case 'content': 796 | if ($this->doc['type'] != 'reference') { 797 | $field .= $this->form('textarea', [ 798 | 'name' => 'ta', 799 | 'value' => htmlspecialchars(stripslashes($this->doc['content']), ENT_COMPAT), 800 | 'class' => $data['class'], 801 | 'rows' => empty($data['rows']) ? 20 : $data['rows'], 802 | ]); 803 | 804 | if ($this->doc['richtext']) { 805 | $data['type'] = 'richtext'; 806 | $key = 'ta'; 807 | } 808 | } 809 | break; 810 | 811 | case 'weblink': 812 | if ($this->doc['type'] == 'reference') { 813 | $field .= $this->view('element', [ 814 | 'tag' => 'i', 815 | 'id' => 'llock', 816 | 'class' => 'fa fa-chain', 817 | 'attr' => 'onclick="enableLinkSelection(!allowLinkSelection);"', 818 | ]); 819 | $field .= $this->form('input', [ 820 | 'name' => 'ta', 821 | 'value' => !empty($this->doc['content']) ? stripslashes($this->doc['content']) 822 | : 'https://', 823 | 'class' => $data['class'], 824 | ]); 825 | } 826 | break; 827 | 828 | case 'template': 829 | $rs = $this->evo->getDatabase()->select( 830 | 't.templatename, t.id, c.category', 831 | $this->evo->getFullTableName('site_templates') . ' AS t LEFT JOIN ' . 832 | $this->evo->getFullTableName('categories') . ' AS c ON t.category = c.id', 833 | 't.selectable=1', 834 | 'c.category, t.templatename ASC' 835 | ); 836 | $optgroup = []; 837 | while ($row = $this->evo->getDatabase()->getRow($rs)) { 838 | $category = !empty($row['category']) ? $row['category'] : $_lang['no_category']; 839 | $optgroup[$category][$row['id']] = $row['templatename'] . ' (' . $row['id'] . ')'; 840 | } 841 | $field .= $this->form('select', [ 842 | 'name' => 'template', 843 | 'value' => $this->doc['template'], 844 | 'options' => [ 845 | 0 => '(blank)', 846 | ], 847 | 'optgroup' => $optgroup, 848 | 'class' => $data['class'], 849 | 'onchange' => 'templateWarning();', 850 | ]); 851 | break; 852 | 853 | case 'parent': 854 | $parentLookup = false; 855 | $parentName = $this->evo->getConfig('site_name'); 856 | if (!empty($_REQUEST['id']) && !empty($this->doc['parent'])) { 857 | $parentLookup = $this->doc['parent']; 858 | } elseif (!empty($_REQUEST['pid'])) { 859 | $parentLookup = $_REQUEST['pid']; 860 | } elseif (!empty($_POST['parent'])) { 861 | $parentLookup = $_POST['parent']; 862 | } else { 863 | $this->doc['parent'] = 0; 864 | } 865 | if ($parentLookup !== false && is_numeric($parentLookup)) { 866 | $rs = $this->evo->getDatabase()->select( 867 | 'pagetitle', 868 | $this->evo->getFullTableName('site_content'), 869 | 'id=' . $parentLookup 870 | ); 871 | $parentName = $this->evo->getDatabase()->getValue($rs); 872 | if (!$parentName) { 873 | $this->evo->webAlertAndQuit($_lang["error_no_parent"]); 874 | } 875 | } 876 | $field .= $this->view('element', [ 877 | 'class' => 'form-control ' . $data['class'], 878 | 'content' => $this->view('element', [ 879 | 'tag' => 'i', 880 | 'id' => 'plock', 881 | 'class' => 'fa fa-folder-o', 882 | 'attr' => 'onclick="enableParentSelection(!allowParentSelection);"', 883 | ]) . $this->view('element', [ 884 | 'tag' => 'b', 885 | 'id' => 'parentName', 886 | 'content' => $this->doc['parent'] . ' (' . $parentName . ')', 887 | ]) . $this->view('input', [ 888 | 'type' => 'hidden', 889 | 'name' => 'parent', 890 | 'value' => $this->doc['parent'], 891 | ]), 892 | ]); 893 | break; 894 | 895 | case 'type': 896 | if ($_SESSION['mgrRole'] == 1 || $this->evo->getManagerApi()->action != 27 || 897 | $_SESSION['mgrInternalKey'] == $this->doc['createdby'] || 898 | $this->evo->hasPermission('change_resourcetype') 899 | ) { 900 | $field .= $this->form('select', [ 901 | 'name' => 'type', 902 | 'value' => $this->doc['type'], 903 | 'options' => [ 904 | 'document' => $_lang["resource_type_webpage"], 905 | 'reference' => $_lang["resource_type_weblink"], 906 | ], 907 | 'class' => $data['class'], 908 | ]); 909 | } else { 910 | $field .= $this->form('input', [ 911 | 'type' => 'hidden', 912 | 'name' => 'type', 913 | 'value' => $this->doc['type'] != 'reference' && 914 | $this->evo->getManagerApi()->action != 72 ? 'document' : 'reference', 915 | ]); 916 | } 917 | break; 918 | 919 | case 'contentType': 920 | if ($_SESSION['mgrRole'] == 1 || $this->evo->getManagerApi()->action != 27 || 921 | $_SESSION['mgrInternalKey'] == $this->doc['createdby'] 922 | ) { 923 | $custom_contenttype = $this->evo->getConfig('custom_contenttype') ? $this->evo->getConfig( 924 | 'custom_contenttype' 925 | ) : 'text/html,text/plain,text/xml'; 926 | $options = explode(',', $custom_contenttype); 927 | $field .= $this->form('select', [ 928 | 'name' => 'contentType', 929 | 'value' => $this->doc['contentType'] ?? 'text/html', 930 | 'options' => array_combine($options, $options), 931 | 'class' => $data['class'], 932 | ]); 933 | } else { 934 | $field .= $this->form('input', [ 935 | 'type' => 'hidden', 936 | 'name' => 'type', 937 | 'value' => $this->doc['type'] == 'reference' ? 'text/html' 938 | : ($this->doc['contentType'] ?? 'text/html'), 939 | ]); 940 | if ($this->doc['type'] == 'reference') { 941 | $field .= $this->form('input', [ 942 | 'type' => 'hidden', 943 | 'name' => 'contentType', 944 | 'value' => 'text/html', 945 | ]); 946 | } else { 947 | $field .= $this->form('input', [ 948 | 'type' => 'hidden', 949 | 'name' => 'contentType', 950 | 'value' => $this->doc['contentType'] ?? 'text/html', 951 | ]); 952 | } 953 | } 954 | break; 955 | 956 | case 'content_dispo': 957 | if ($_SESSION['mgrRole'] == 1 || $this->evo->getManagerApi()->action != 27 || 958 | $_SESSION['mgrInternalKey'] == $this->doc['createdby'] 959 | ) { 960 | $field .= $this->form('select', [ 961 | 'name' => 'content_dispo', 962 | 'value' => $this->doc['content_dispo'] ?? 0, 963 | 'options' => [ 964 | 0 => $_lang['inline'], 965 | 1 => $_lang['attachment'], 966 | ], 967 | 'class' => $data['class'], 968 | ]); 969 | } else { 970 | if ($this->doc['type'] != 'reference') { 971 | $field .= $this->form('input', [ 972 | 'type' => 'hidden', 973 | 'name' => 'content_dispo', 974 | 'value' => $this->doc['content_dispo'] ?? 0, 975 | ]); 976 | } 977 | } 978 | break; 979 | 980 | default: 981 | $field .= $this->form('input', [ 982 | 'name' => $key, 983 | 'value' => htmlspecialchars(stripslashes($this->doc[$key]), ENT_COMPAT), 984 | 'class' => 'form-control ' . $data['class'], 985 | 'attr' => 'spellcheck="true"' . $data['required'].$data['readonly'] . $data['pattern'], 986 | ]); 987 | break; 988 | } 989 | } 990 | } else { 991 | $isTv = true; 992 | $labelFor = 'tv' . $data['id']; 993 | $rowClass .= ' form-row-' . $data['type']; 994 | 995 | if ($data['title'] == '') { 996 | $data['title'] = $data['caption']; 997 | if (substr((string) $data['value'], 0, 8) == '@INHERIT') { 998 | $data['description'] .= '
(' . $_lang['tmplvars_inherited'] . 999 | ')
'; 1000 | } 1001 | } 1002 | 1003 | if (array_key_exists('tv' . $data['id'], $_POST)) { 1004 | if ($data['type'] == 'listbox-multiple') { 1005 | $data['value'] = implode('||', $_POST['tv' . $data['id']]); 1006 | } else { 1007 | $data['value'] = $_POST['tv' . $data['id']]; 1008 | } 1009 | } 1010 | 1011 | $field = renderFormElement( 1012 | $data['type'], 1013 | $data['id'], 1014 | $data['default_text'], 1015 | $data['elements'], 1016 | $data['value'], 1017 | '', 1018 | $data 1019 | ); 1020 | 1021 | $key = 'tv' . $data['id']; 1022 | 1023 | if (stripos($data['type'], 'custom_tv') === false) { 1024 | // add class form-control 1025 | if ($data['type'] == 'date') { 1026 | $field = str_replace('class="', 'class="form-control ', $field); 1027 | } else { 1028 | $field = str_replace(['name="', 'type="button"', 'size="1"'], 1029 | ['class="form-control" name="', 'class="form-control" type="button"', ''], 1030 | $field); 1031 | } 1032 | 1033 | // show required 1034 | if ($data['required']) { 1035 | $field = str_replace(' name="', $data['required'] . ' name="', $field); 1036 | } 1037 | if ($data['readonly']) { 1038 | $field = str_replace(' name="', $data['readonly'] . ' name="', $field); 1039 | } 1040 | } 1041 | } 1042 | 1043 | if ($this->evo->getConfig('use_editor') == 1 && ($data['type'] == 'richtext' || $data['type'] == 'htmlarea')) { 1044 | $tvOptions = $this->evo->parseProperties($data['elements']); 1045 | $editor = $this->evo->getConfig('which_editor'); 1046 | if (!empty($tvOptions)) { 1047 | $editor = $tvOptions['editor'] ?? $this->evo->getConfig('which_editor'); 1048 | } 1049 | $this->richtexteditorIds[$editor][] = $key; 1050 | $this->richtexteditorOptions[$editor][$key] = $tvOptions; 1051 | } 1052 | 1053 | if ($field) { 1054 | $title = ''; 1055 | $data['size'] = !empty($data['size']) 1056 | ? ' input-group-' . $data['size'] 1057 | : (!empty($settings['size']) ? ' input-group-' . $settings['size'] 1058 | : ''); 1059 | $data['position'] = !empty($data['position']) 1060 | ? $data['position'] : (!empty($settings['position']) ? $settings['position'] : ''); 1061 | $data['reverse'] = !empty($data['reverse']) 1062 | ? $data['reverse'] : (!empty($settings['reverse']) ? $settings['reverse'] : ''); 1063 | 1064 | if (trim($data['title'])) { 1065 | if ($isTv && $this->params['showTvName']) { 1066 | $data['title'] .= '
[*' . $data['name'] . '*]'; 1067 | } 1068 | $title = '' . $data['help'] . $data['description']; 1070 | if ($data['position'] == 'c') { 1071 | $leftClass .= ' col-xs-12 col-12'; 1072 | $rightClass .= ' col-xs-12 col-12'; 1073 | if ($data['reverse']) { 1074 | $rowClass .= ' column-reverse'; 1075 | } 1076 | } elseif ($data['position'] == 'r') { 1077 | $leftClass .= ' col'; 1078 | $rightClass .= ' col-auto'; 1079 | if ($data['reverse']) { 1080 | $rowClass .= ' row-reverse'; 1081 | } 1082 | } elseif ($data['position'] == 'a') { 1083 | $leftClass .= ' col-12'; 1084 | $rightClass .= ' col-12'; 1085 | $rowClass .= ' col-12 col-sm-6 col-md'; 1086 | if ($data['reverse']) { 1087 | $rowClass .= ' column-reverse'; 1088 | } 1089 | } else { 1090 | $leftClass .= ' col-auto col-title'; 1091 | $rightClass .= ' col'; 1092 | if ($data['reverse']) { 1093 | $rowClass .= ' row-reverse'; 1094 | } 1095 | } 1096 | $rightClass .= $data['size']; 1097 | } else { 1098 | if ($data['position'] == 'a') { 1099 | $leftClass .= ' col-12'; 1100 | $rightClass .= ' col-12'; 1101 | $rowClass .= ' col-12 col-sm-6 col-md'; 1102 | } else { 1103 | $leftClass .= ' col-xs-12 col-12'; 1104 | $rightClass .= ' col-xs-12 col-12' . $data['size']; 1105 | } 1106 | } 1107 | 1108 | // show tv image 1109 | if (!empty($data['type']) && $data['type'] == 'image' && $this->params['showTvImage']) { 1110 | $field .= $this->form('thumb', [ 1111 | 'name' => $isTv ? 'tv' . $data['id'] : $key, 1112 | 'value' => $isTv ? ($data['value'] ? MODX_SITE_URL . $data['value'] : '') 1113 | : ($value ? MODX_SITE_URL . $value : ''), 1114 | 'width' => $this->evo->getConfig('thumbWidth'), 1115 | ]); 1116 | } 1117 | 1118 | // show datalist 1119 | if (!empty($data['type']) && ($data['type'] == 'text' || $data['type'] == 'number') && $data['elements']) { 1120 | $options = explode('||', $data['elements']); 1121 | $field .= $this->form('datalist', [ 1122 | 'id' => $data['id'], 1123 | 'class' => '', 1124 | 'options' => array_combine($options, $options), 1125 | ]); 1126 | } 1127 | 1128 | // show choices 1129 | if (isset($data['choices']) && $data['choices'] != '') { 1130 | $field .= $this->showChoices((int) $data['id'], $data['value'], $data['choices']); 1131 | } 1132 | 1133 | // show select richtext 1134 | if ($key == 'content') { 1135 | $options = [ 1136 | 'none' => $_lang['none'], 1137 | ]; 1138 | 1139 | $evtOut = $this->evo->invokeEvent('OnRichTextEditorRegister'); 1140 | if (is_array($evtOut)) { 1141 | for ($i = 0; $i < count($evtOut); $i++) { 1142 | $editor = $evtOut[$i]; 1143 | $options[$editor] = $editor; 1144 | } 1145 | } 1146 | 1147 | $field = $this->view('element', [ 1148 | 'class' => 'select-which-editor', 1149 | 'content' => $this->form('select', [ 1150 | 'name' => 'which_editor', 1151 | 'value' => $this->evo->getConfig('which_editor'), 1152 | 'options' => $options, 1153 | 'class' => 'form-control form-control-sm', 1154 | 'onchange' => 'changeRTE();', 1155 | ]), 1156 | ]) . $field; 1157 | } 1158 | 1159 | if ($title) { 1160 | $title = $this->view('element', [ 1161 | 'class' => trim($leftClass), 1162 | 'content' => $title, 1163 | ]); 1164 | } 1165 | 1166 | $field = $this->view('element', [ 1167 | 'class' => trim($rightClass), 1168 | 'content' => $field, 1169 | ]); 1170 | 1171 | $out = $this->view('element', [ 1172 | 'class' => 'row form-row' . $rowClass, 1173 | 'content' => $title . $field, 1174 | ]); 1175 | } 1176 | 1177 | return $out; 1178 | } 1179 | 1180 | /** 1181 | * @param string $tpl 1182 | * @param array $data 1183 | * 1184 | * @return string 1185 | */ 1186 | protected function form(string $tpl, array $data = []): string 1187 | { 1188 | if (!isset($data['name'])) { 1189 | $data['name'] = ''; 1190 | } 1191 | 1192 | if (!isset($data['type'])) { 1193 | $data['type'] = 'text'; 1194 | } 1195 | 1196 | if (!isset($data['id'])) { 1197 | $data['id'] = $data['name']; 1198 | } 1199 | 1200 | if (!isset($data['value'])) { 1201 | $data['value'] = ''; 1202 | } 1203 | 1204 | if (!isset($data['placeholder'])) { 1205 | $data['placeholder'] = ''; 1206 | } 1207 | 1208 | if (!isset($data['maxlength'])) { 1209 | $data['maxlength'] = '255'; 1210 | } 1211 | 1212 | if (empty($data['class'])) { 1213 | $data['class'] = 'form-control'; 1214 | } 1215 | 1216 | if (!isset($data['attr'])) { 1217 | $data['attr'] = ''; 1218 | } 1219 | 1220 | if (!isset($data['checked'])) { 1221 | $data['checked'] = ''; 1222 | } else { 1223 | if (is_bool($data['checked']) || is_numeric($data['checked'])) { 1224 | $data['checked'] = $data['checked'] ? 'checked' : ''; 1225 | } 1226 | } 1227 | 1228 | if (!isset($data['disabled'])) { 1229 | $data['disabled'] = ''; 1230 | } else { 1231 | if (is_bool($data['disabled']) || is_numeric($data['disabled'])) { 1232 | $data['disabled'] = $data['disabled'] ? 'disabled' : ''; 1233 | } 1234 | } 1235 | 1236 | $options = ''; 1237 | 1238 | if (!empty($data['options'])) { 1239 | $tmp = $data['options']; 1240 | foreach ($tmp as $k => $v) { 1241 | $options .= $this->form('option', [ 1242 | 'value' => $k, 1243 | 'title' => $v, 1244 | 'selected' => $k == $data['value'] ? 'selected' : '', 1245 | ]); 1246 | } 1247 | } 1248 | 1249 | if (!empty($data['optgroup'])) { 1250 | foreach ($data['optgroup'] as $label => $group) { 1251 | $options .= ''; 1252 | foreach ($group as $value => $title) { 1253 | $options .= $this->form('option', [ 1254 | 'value' => $value, 1255 | 'title' => $title, 1256 | 'selected' => $value == $data['value'] ? 'selected' : '', 1257 | ]); 1258 | } 1259 | $options .= ''; 1260 | } 1261 | } 1262 | 1263 | $data['options'] = $options; 1264 | 1265 | if (!isset($data['onchange'])) { 1266 | $data['onchange'] = 'documentDirty=true;'; 1267 | } 1268 | 1269 | return $this->view($tpl, $data); 1270 | } 1271 | 1272 | /** 1273 | * @param string $tpl 1274 | * @param array $data 1275 | * 1276 | * @return string 1277 | */ 1278 | protected function view(string $tpl, array $data = []): string 1279 | { 1280 | $tpl = trim($tpl, '/'); 1281 | $tpl = $this->basePath . 'tpl/' . $tpl . '.tpl.php'; 1282 | if (file_exists($tpl)) { 1283 | extract($data); 1284 | ob_start(); 1285 | @require($tpl); 1286 | $out = ob_get_contents(); 1287 | ob_end_clean(); 1288 | } else { 1289 | $out = 'Error: Could not load template ' . $tpl . '!
'; 1290 | } 1291 | 1292 | return $out; 1293 | } 1294 | 1295 | /** 1296 | * @param int $id 1297 | * @param string $value 1298 | * @param string|bool $separator 1299 | * 1300 | * @return string 1301 | */ 1302 | protected function showChoices(int $id, string $value = '', $separator = ', '): string 1303 | { 1304 | $out = ''; 1305 | $separator = is_bool($separator) ? ', ' : htmlspecialchars($separator, ENT_COMPAT); 1306 | 1307 | $rs = $this->evo->getDatabase() 1308 | ->query( 1309 | ' 1310 | SELECT value 1311 | FROM ' . $this->evo->getFullTableName('site_tmplvar_contentvalues') . ' 1312 | WHERE tmplvarid=' . $id . ' 1313 | GROUP BY value 1314 | ORDER BY value ASC 1315 | ' 1316 | ); 1317 | 1318 | $rs = $this->evo->getDatabase()->makeArray($rs); 1319 | 1320 | if (count($rs)) { 1321 | $out .= '
'; 1322 | $separator = trim(htmlspecialchars_decode($separator)); 1323 | $value = trim($value, $separator); 1324 | $list = []; 1325 | foreach ($rs as $row) { 1326 | $list = array_merge($list, array_map('trim', explode($separator, $row['value']))); 1327 | } 1328 | $list = array_unique($list); 1329 | sort($list); 1330 | $value = array_map('trim', explode($separator, $value)); 1331 | foreach ($list as $val) { 1332 | if (trim($val) != '') { 1333 | if (in_array($val, $value)) { 1334 | $out .= '' . $val . ''; 1335 | } else { 1336 | $out .= '' . $val . ''; 1337 | } 1338 | } 1339 | } 1340 | $out .= '
'; 1341 | } 1342 | 1343 | return $out; 1344 | } 1345 | 1346 | /** 1347 | * @param int $id 1348 | * @param string $mode 1349 | */ 1350 | public function OnDocFormSave(int $id, string $mode): void 1351 | { 1352 | if (!empty($id)) { 1353 | $data = []; 1354 | 1355 | if (file_exists($this->basePath . 'configs/custom_fields.php')) { 1356 | $custom_fields = require_once $this->basePath . 'configs/custom_fields.php'; 1357 | if (is_array($custom_fields)) { 1358 | foreach ($custom_fields as $k => $v) { 1359 | if (!empty($v['save'])) { 1360 | if (isset($_REQUEST[$k])) { 1361 | if (!empty($v['prepareSave'])) { 1362 | $v = $this->prepare($v['prepareSave'], $_REQUEST[$k], $mode); 1363 | } else { 1364 | $v = $_REQUEST[$k]; 1365 | } 1366 | if (!is_null($v)) { 1367 | if (is_array($v)) { 1368 | $v = implode('||', $v); 1369 | } 1370 | $data[$k] = $this->evo->getDatabase()->escape($v); 1371 | } 1372 | } else { 1373 | $data[$k] = $v['default'] ?? ''; 1374 | } 1375 | } 1376 | } 1377 | } 1378 | } 1379 | 1380 | if (!empty($data)) { 1381 | $this->evo->getDatabase()->update($data, '[+prefix+]site_content', 'id=' . $id); 1382 | } 1383 | } 1384 | } 1385 | 1386 | /** 1387 | * @param $name 1388 | * @param $data 1389 | * @param string|null $mode 1390 | * 1391 | * @return array|false|mixed|string 1392 | */ 1393 | protected function prepare($name, $data, string $mode = null) 1394 | { 1395 | if (!empty($name)) { 1396 | $params = [ 1397 | 'data' => $data, 1398 | 'modx' => $this->evo, 1399 | '_TE' => $this, 1400 | 'mode' => $mode, 1401 | ]; 1402 | 1403 | if ((is_object($name)) || is_callable($name)) { 1404 | $data = call_user_func_array($name, $params); 1405 | } else { 1406 | $data = $this->evo->runSnippet($name, $params); 1407 | } 1408 | } 1409 | 1410 | return $data; 1411 | } 1412 | } 1413 | --------------------------------------------------------------------------------