From fa9417dd95d7ec4d8c981c0553bddcfd714c7f97 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Sun, 6 Sep 2020 21:14:58 -0600 Subject: [PATCH 01/69] Fix broken links --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 88168beb3..16ad959d6 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@

- October + October

[October](https://octobercms.com) is a Content Management System (CMS) and web platform whose sole purpose is to make your development workflow simple again. It was born out of frustration with existing systems. We feel building websites has become a convoluted and confusing process that leaves developers unsatisfied. We want to turn you around to the simpler side and get back to basics. October's mission is to show the world that web development is not rocket science. -![Stable Build](https://github.com/octobercms/october/workflows/Tests/badge.svg?branch=master) +![Stable Build](https://github.com/octobercms/october/workflows/Tests/badge.svg?branch=1.1) [![License](https://poser.pugx.org/october/october/license.svg)](https://packagist.org/packages/october/october) ## Installing October @@ -73,9 +73,9 @@ Before sending or reviewing Pull Requests, be sure to review the [Contributing G Please follow the following guides and code standards: -* [PSR 4 Coding Standards](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md) -* [PSR 2 Coding Style Guide](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) -* [PSR 1 Coding Standards](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md) +* [PSR 4 Coding Standards](https://github.com/php-fig/fig-standards/blob/develop/accepted/PSR-4-autoloader.md) +* [PSR 2 Coding Style Guide](https://github.com/php-fig/fig-standards/blob/develop/accepted/PSR-2-coding-style-guide.md) +* [PSR 1 Coding Standards](https://github.com/php-fig/fig-standards/blob/develop/accepted/PSR-1-basic-coding-standard.md) ### Code of Conduct From 1ef50d475168b68effe3f357b24234f0e6047bce Mon Sep 17 00:00:00 2001 From: lctoan Date: Mon, 7 Sep 2020 23:48:12 +0800 Subject: [PATCH 02/69] Improve Taiwanese translations (#5264) --- modules/backend/lang/zh-tw/lang.php | 26 +++++-- modules/cms/lang/zh-tw/lang.php | 19 +++-- modules/system/assets/js/lang/lang.zh-tw.js | 2 +- modules/system/lang/zh-tw/client.php | 86 +++++++++++++++++++++ 4 files changed, 117 insertions(+), 16 deletions(-) create mode 100644 modules/system/lang/zh-tw/client.php diff --git a/modules/backend/lang/zh-tw/lang.php b/modules/backend/lang/zh-tw/lang.php index 35ab3510b..9655ef6e6 100644 --- a/modules/backend/lang/zh-tw/lang.php +++ b/modules/backend/lang/zh-tw/lang.php @@ -52,6 +52,7 @@ return [ 'widget_width' => '寬度', 'full_width' => '全部寬度', 'add_widget' => '新增元件', + 'manage_widgets' => '管理元件', 'widget_inspector_title' => '元件設定', 'widget_inspector_description' => '設定報表元件', 'widget_columns_label' => '寬度 :columns', @@ -62,6 +63,12 @@ return [ 'widget_new_row_description' => '把元件放到新列', 'widget_title_label' => '元件標題', 'widget_title_error' => '需要元件標題', + 'reset_layout' => '重置版面', + 'reset_layout_confirm' => '確定重置為預設版面?', + 'reset_layout_success' => '版面已重置。', + 'make_default' => '設定為預設', + 'make_default_confirm' => '確定將此版面設定為預設?', + 'make_default_success' => '已設定此版面為預設。', 'status' => [ 'widget_title_default' => '系統狀態', 'update_available' => '{0} 更新可用!|{1} 更新可用!|[2,Inf] 更新可用!' @@ -170,7 +177,7 @@ return [ 'field_off' => '關', 'field_on' => '開', 'add' => '增加', - 'apply' => '應用', + 'apply' => '確定', 'cancel' => '取消', 'close' => '關閉', 'confirm' => '確認', @@ -297,7 +304,9 @@ return [ 'email' => 'Email' ], 'filter' => [ - 'all' => '全部' + 'all' => '全部', + 'date_all' => '全部區間', + 'number_all' => '全部數目', ], 'permissions' => [ 'manage_media' => 'Upload and manage media contents - images, videos, sounds, documents' @@ -316,10 +325,10 @@ return [ 'display' => '顯示', 'filter_everything' => '所有', 'filter_images' => '圖片', - 'filter_video' => '視頻', - 'filter_audio' => '音頻', + 'filter_video' => '影片', + 'filter_audio' => '音訊', 'filter_documents' => '文檔', - 'library' => '庫', + 'library' => '媒體庫', 'size' => '大小', 'title' => '標題', 'last_modified' => '最近修改', @@ -332,7 +341,7 @@ return [ 'multiple_selected' => '多選.', 'uploading_file_num' => '上傳 :number 檔案...', 'uploading_complete' => '上傳完畢', - 'order_by' => '排序', + 'order_by' => '排列方式', 'folder' => '檔案夾', 'no_files_found' => '沒找到您請求的檔案.', 'delete_empty' => '請選擇刪除項.', @@ -362,6 +371,9 @@ return [ 'selection_mode' => '選擇模式', 'resize_image' => '調整圖片', 'image_size' => '圖片大小:', - 'selected_size' => '選中:' + 'selected_size' => '選中:', + 'direction' => '順序', + 'direction_asc' => '升冪', + 'direction_desc' => '降冪', ] ]; diff --git a/modules/cms/lang/zh-tw/lang.php b/modules/cms/lang/zh-tw/lang.php index 9cbed535f..3c69e4484 100644 --- a/modules/cms/lang/zh-tw/lang.php +++ b/modules/cms/lang/zh-tw/lang.php @@ -46,8 +46,8 @@ return [ 'dir_name_create_label' => '目標主題目錄', 'theme_label' => '主題', 'theme_title' => '主題', - 'activate_button' => '激活', - 'active_button' => '激活', + 'activate_button' => '啟用', + 'active_button' => '啟用', 'customize_theme' => '自訂主題', 'customize_button' => '自訂', 'duplicate_button' => '複製', @@ -110,7 +110,10 @@ return [ 'invalid_url' => '不合法的URL格式. URL可以正斜槓開頭, 包含數字, 拉丁字母和下面的字元: ._-[]:?|/+*^$', 'delete_confirm_multiple' => '真的想要刪除選擇的頁面嗎?', 'delete_confirm_single' => '真的想要刪除這個頁面嗎?', - 'no_layout' => '-- 沒有佈局 --' + 'no_layout' => '-- 沒有佈局 --', + 'title' => '頁面標題', + 'url' => '頁面網址', + 'file_name' => '頁面檔案名稱', ], 'layout' => [ 'not_found_name' => "佈局 ':name' 找不到", @@ -168,8 +171,8 @@ return [ 'content' => '內容', 'hidden' => '隱藏', 'hidden_comment' => '隱藏頁面只能被登錄的後台使用者訪問.', - 'enter_fullscreen' => '進入全屏模式', - 'exit_fullscreen' => '退出全屏模式' + 'enter_fullscreen' => '進入全螢幕模式', + 'exit_fullscreen' => '退出全螢幕模式', ], 'asset' => [ 'menu_label' => '資源', @@ -181,12 +184,12 @@ return [ 'create_directory' => '新建目錄', 'directory_popup_title' => '新目錄', 'directory_name' => '目錄名', - 'rename' => '重命名', + 'rename' => '重新命名', 'delete' => '刪除', 'move' => '移動', 'select' => '選擇', 'new' => '新檔案', - 'rename_popup_title' => '重命名', + 'rename_popup_title' => '重新命名', 'rename_new_name' => '新名稱', 'invalid_path' => '路徑名稱只能包含數字, 拉丁字母和以下字元: _-/', 'error_deleting_file' => '刪除檔案 :name 錯誤.', @@ -195,7 +198,7 @@ return [ 'invalid_name' => '名稱只能包含數字, 拉丁字母, 空格和以下字元: _-', 'original_not_found' => '原始檔案或目錄找不到', 'already_exists' => '檔案或目錄已存在', - 'error_renaming' => '重命名檔案或目錄錯誤', + 'error_renaming' => '重新命名檔案或目錄錯誤', 'name_cant_be_empty' => '名稱不能為空', 'too_large' => '上傳的檔案太大. 最大檔案大小是 :max_size', 'type_not_allowed' => '只有下面的檔案類型是允許的: :allowed_types', diff --git a/modules/system/assets/js/lang/lang.zh-tw.js b/modules/system/assets/js/lang/lang.zh-tw.js index 8aecf9d62..cbcae46b4 100644 --- a/modules/system/assets/js/lang/lang.zh-tw.js +++ b/modules/system/assets/js/lang/lang.zh-tw.js @@ -5,7 +5,7 @@ if ($.oc === undefined) $.oc = {} if ($.oc.langMessages === undefined) $.oc.langMessages = {} $.oc.langMessages['zh-tw'] = $.extend( $.oc.langMessages['zh-tw'] || {}, - {"markdowneditor":{"formatting":"Formatting","quote":"Quote","code":"Code","header1":"Header 1","header2":"Header 2","header3":"Header 3","header4":"Header 4","header5":"Header 5","header6":"Header 6","bold":"Bold","italic":"Italic","unorderedlist":"Unordered List","orderedlist":"Ordered List","video":"Video","image":"Image","link":"Link","horizontalrule":"Insert Horizontal Rule","fullscreen":"Full screen","preview":"Preview"},"mediamanager":{"insert_link":"Insert Media Link","insert_image":"Insert Media Image","insert_video":"Insert Media Video","insert_audio":"Insert Media Audio","invalid_file_empty_insert":"Please select file to insert a links to.","invalid_file_single_insert":"Please select a single file.","invalid_image_empty_insert":"Please select image(s) to insert.","invalid_video_empty_insert":"Please select a video file to insert.","invalid_audio_empty_insert":"Please select an audio file to insert."},"alert":{"confirm_button_text":"OK","cancel_button_text":"Cancel","widget_remove_confirm":"Remove this widget?"},"datepicker":{"previousMonth":"Previous Month","nextMonth":"Next Month","months":["January","February","March","April","May","June","July","August","September","October","November","December"],"weekdays":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"weekdaysShort":["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]},"colorpicker":{"choose":"Ok"},"filter":{"group":{"all":"all"},"scopes":{"apply_button_text":"Apply","clear_button_text":"Clear"},"dates":{"all":"all","filter_button_text":"Filter","reset_button_text":"Reset","date_placeholder":"Date","after_placeholder":"After","before_placeholder":"Before"},"numbers":{"all":"all","filter_button_text":"Filter","reset_button_text":"Reset","min_placeholder":"Min","max_placeholder":"Max"}},"eventlog":{"show_stacktrace":"Show the stacktrace","hide_stacktrace":"Hide the stacktrace","tabs":{"formatted":"Formatted","raw":"Raw"},"editor":{"title":"Source code editor","description":"Your operating system should be configured to listen to one of these URL schemes.","openWith":"Open with","remember_choice":"Remember selected option for this session","open":"Open","cancel":"Cancel"}}} + {"markdowneditor":{"formatting":"\u683c\u5f0f","quote":"\u5f15\u8a00","code":"\u7a0b\u5f0f\u78bc","header1":"\u6a19\u984c\u4e00","header2":"\u6a19\u984c\u4e8c","header3":"\u6a19\u984c\u4e09","header4":"\u6a19\u984c\u56db","header5":"\u6a19\u984c\u4e94","header6":"\u6a19\u984c\u516d","bold":"\u7c97\u9ad4","italic":"\u659c\u9ad4","unorderedlist":"\u9805\u76ee\u6e05\u55ae","orderedlist":"\u6578\u5b57\u6e05\u55ae","video":"\u5f71\u7247","image":"\u5716\u7247","link":"\u9023\u7d50","horizontalrule":"\u63d2\u5165\u6c34\u5e73\u7dda","fullscreen":"\u5168\u87a2\u5e55","preview":"\u9810\u89bd"},"mediamanager":{"insert_link":"\u63d2\u5165\u5a92\u9ad4\u6ac3\u9023\u7d50","insert_image":"\u63d2\u5165\u5a92\u9ad4\u6ac3\u5716\u7247","insert_video":"\u63d2\u5165\u5a92\u9ad4\u6ac3\u5f71\u7247","insert_audio":"\u63d2\u5165\u5a92\u9ad4\u6ac3\u97f3\u8a0a","invalid_file_empty_insert":"\u8acb\u9078\u64c7\u6a94\u6848\u4ee5\u63d2\u5165\u9023\u7d50\u3002","invalid_file_single_insert":"\u8acb\u9078\u64c7\u4e00\u500b\u6a94\u6848\u3002","invalid_image_empty_insert":"\u8acb\u9078\u64c7\u63d2\u5165\u7684\u5716\u7247\u3002","invalid_video_empty_insert":"\u8acb\u9078\u64c7\u63d2\u5165\u7684\u5f71\u7247\u3002","invalid_audio_empty_insert":"\u8acb\u9078\u64c7\u63d2\u5165\u7684\u97f3\u8a0a\u3002"},"alert":{"confirm_button_text":"\u78ba\u8a8d","cancel_button_text":"\u53d6\u6d88","widget_remove_confirm":"\u78ba\u5b9a\u79fb\u9664\u6b64\u5143\u4ef6\uff1f"},"datepicker":{"previousMonth":"\u4e0a\u500b\u6708","nextMonth":"\u4e0b\u500b\u6708","months":["\u4e00\u6708","\u4e8c\u6708","\u4e09\u6708","\u56db\u6708","\u4e94\u6708","\u516d\u6708","\u4e03\u6708","\u516b\u6708","\u4e5d\u6708","\u5341\u6708","\u5341\u4e00\u6708","\u5341\u4e8c\u6708"],"weekdays":["\u661f\u671f\u65e5","\u661f\u671f\u4e00","\u661f\u671f\u4e8c","\u661f\u671f\u4e09","\u661f\u671f\u56db","\u661f\u671f\u4e94","\u661f\u671f\u516d"],"weekdaysShort":["\u9031\u65e5","\u9031\u4e00","\u9031\u4e8c","\u9031\u4e09","\u9031\u56db","\u9031\u4e94","\u9031\u516d"]},"colorpicker":{"choose":"\u78ba\u5b9a"},"filter":{"group":{"all":"\u5168\u90e8"},"scopes":{"apply_button_text":"\u78ba\u5b9a","clear_button_text":"\u6e05\u9664"},"dates":{"all":"\u5168\u90e8","filter_button_text":"\u7be9\u9078","reset_button_text":"\u91cd\u7f6e","date_placeholder":"\u65e5\u671f","after_placeholder":"\u5728\u6b64\u4e4b\u5f8c","before_placeholder":"\u5728\u6b64\u4e4b\u524d"},"numbers":{"all":"\u5168\u90e8","filter_button_text":"\u7be9\u9078","reset_button_text":"\u91cd\u7f6e","min_placeholder":"\u6700\u5c0f\u503c","max_placeholder":"\u6700\u5927\u503c"}},"eventlog":{"show_stacktrace":"Show the stacktrace","hide_stacktrace":"Hide the stacktrace","tabs":{"formatted":"Formatted","raw":"Raw"},"editor":{"title":"Source code editor","description":"Your operating system should be configured to listen to one of these URL schemes.","openWith":"Open with","remember_choice":"Remember selected option for this session","open":"Open","cancel":"Cancel"}}} ); //! moment.js locale configuration v2.22.2 diff --git a/modules/system/lang/zh-tw/client.php b/modules/system/lang/zh-tw/client.php new file mode 100644 index 000000000..3b1aa730f --- /dev/null +++ b/modules/system/lang/zh-tw/client.php @@ -0,0 +1,86 @@ + [ + 'formatting' => '格式', + 'quote' => '引用', + 'code' => '程式碼', + 'header1' => '標題一', + 'header2' => '標題二', + 'header3' => '標題三', + 'header4' => '標題四', + 'header5' => '標題五', + 'header6' => '標題六', + 'bold' => '粗體', + 'italic' => '斜體', + 'unorderedlist' => '項目清單', + 'orderedlist' => '數字清單', + 'video' => '影片', + 'image' => '圖片', + 'link' => '連結', + 'horizontalrule' => '插入水平線', + 'fullscreen' => '全螢幕', + 'preview' => '預覽', + ], + 'mediamanager' => [ + 'insert_link' => '插入媒體櫃連結', + 'insert_image' => '插入媒體櫃圖片', + 'insert_video' => '插入媒體櫃影片', + 'insert_audio' => '插入媒體櫃音訊', + 'invalid_file_empty_insert' => '請選擇檔案以插入連結。', + 'invalid_file_single_insert' => '請選擇一個檔案。', + 'invalid_image_empty_insert' => '請選擇插入的圖片。', + 'invalid_video_empty_insert' => '請選擇插入的影片。', + 'invalid_audio_empty_insert' => '請選擇插入的音訊。', + ], + 'alert' => [ + 'confirm_button_text' => '確認', + 'cancel_button_text' => '取消', + 'widget_remove_confirm' => '確定移除此元件?', + ], + 'datepicker' => [ + 'previousMonth' => '上個月', + 'nextMonth' => '下個月', + 'months' => ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], + 'weekdays' => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + 'weekdaysShort' => ['週日', '週一', '週二', '週三', '週四', '週五', '週六'] + ], + 'colorpicker' => [ + 'choose' => '確定', + ], + 'filter' => [ + 'group' => [ + 'all' => '全部', + ], + 'scopes' => [ + 'apply_button_text' => '確定', + 'clear_button_text' => '清除', + ], + 'dates' => [ + 'all' => '全部', + 'filter_button_text' => '篩選', + 'reset_button_text' => '重置', + 'date_placeholder' => '日期', + 'after_placeholder' => '在此之後', + 'before_placeholder' => '在此之前', + ], + 'numbers' => [ + 'all' => '全部', + 'filter_button_text' => '篩選', + 'reset_button_text' => '重置', + 'min_placeholder' => '最小值', + 'max_placeholder' => '最大值', + ], + ] +]; From ce47c0248fb5a208495017893c42a7ed02be4f8e Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Mon, 7 Sep 2020 15:23:00 -0600 Subject: [PATCH 03/69] Fix issue displaying protected file thumbnails when width or height is empty. Related #5267 --- modules/system/models/File.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/system/models/File.php b/modules/system/models/File.php index a373cb363..980da0312 100644 --- a/modules/system/models/File.php +++ b/modules/system/models/File.php @@ -26,6 +26,9 @@ class File extends FileBase public function getThumb($width, $height, $options = []) { $url = ''; + $width = !empty($width) ? $width : 0; + $height = !empty($height) ? $height : 0; + if (!$this->isPublic() && class_exists(Files::class)) { $options = $this->getDefaultThumbOptions($options); // Ensure that the thumb exists first From 2c4d3c9f98f3e62b650ca22085e0c8ee8379ae10 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Tue, 8 Sep 2020 16:04:40 -0600 Subject: [PATCH 04/69] Fix issue where URLs generated by the ImageResizer were not correctly encoded. Related https://github.com/laravel/framework/issues/34199 --- modules/system/classes/ImageResizer.php | 5 ++++- tests/fixtures/media/october space.png | Bin 0 -> 6733 bytes tests/unit/system/classes/ImageResizerTest.php | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/media/october space.png diff --git a/modules/system/classes/ImageResizer.php b/modules/system/classes/ImageResizer.php index 2e5570ac5..0b1ae8af5 100644 --- a/modules/system/classes/ImageResizer.php +++ b/modules/system/classes/ImageResizer.php @@ -500,7 +500,10 @@ class ImageResizer $url = $resizedDisk->url($this->getPathToResizedImage()); } - return $url; + // Ensure that a properly encoded URL is returned + $segments = explode('/', $url); + $lastSegment = array_pop($segments); + return implode('/', $segments) . '/' . rawurlencode(rawurldecode($lastSegment)); } /** diff --git a/tests/fixtures/media/october space.png b/tests/fixtures/media/october space.png new file mode 100644 index 0000000000000000000000000000000000000000..abf039cf0309b8ca30dd42616033077c700c1306 GIT binary patch literal 6733 zcmZ`;cTf}2x85X#5{l9iAt)sj0TX%$MQV_y^b#OSCm}=#9W}8b(osYODT?#}0)YUQ zfYPLhC`AE@g3?4$l&-w^oA<|W-prewyLaw6-}%m-Gdpwdew%XA!CDX|2?GE?&<1Pa z#F?l54PI`}9qILo#+kT+kJ}#yfJd49yQiU?wTwU3$sPa_6aj#G83499Ch7tJM5_b9 z5*`2yZvy~=T+nvPnB(9+V{2^z?ESlnpOoL=Sa>Kl_Le+sehBXYb#eKzEDrdUjm2@7 zxc4jf;*(rQ89mQzcrWoifT)}jmcNLKHglG`pG@@$E_QzU7Urxd%n$8wHK8 z{uVOU-xKPfD!C^fI#s%;xYoH8o8$9TRA07ieyH$FjmC@VyTXf?_yxdLV1HU99*Iv} zi%_K+7K?0(dGNYG7^XKmD_E5Y+;rdoD2+!ETPxyUdwcfGaJw=_QS)-%H;jS zJq`U+MBG5TK%5|q!&T)v3D@V@EZn+~?+P_O&C0(-Qa()1=xE+YMw4-N1cn_$VAF)G zwwmH)1RaW*xK4dQMbiEpe3Gk(2a%Dh+VwUi&TOJjojn9&t=s4`9>jjd{Wx|wqzoJ^ zOuR&kqV-cx7fydNgG@^cUS0|c^1Ncm7245kLQeN=6kJ=?-QXtioWD|9SoyZI3oMBp z>$S&z!Oj4rbHb~l?9%~ij`VxrJiUpRRB!5qo~M5pxG=~nu!@?i$TBO(1P-}!@9x*v zQJ*l?3><=dGnZ>PXJ{l-?ZlsF-yee_dr|A7-)gem*cYe?3aU=R?Buw!}p3aiOgD#j(^GFaq^Zg6K0o zP5b(8(bI@QR_<`SJZI%g6ebF?rh_zR!hh_)1=gY#fgXq}Gsu-SlVx(s_D0^6`2MUX zbqcH!Y~&AeYI37^9Goc`=Z(JS#jM5W1GoqYBJf?d%THr?urUdnO7%~(ECj@s+8k)h z6+YS6$)~q`+`(}NoqZ%c`~gabzacMER9UZNSefkS(Ncn?&hhfAJrb-$OQbtsz+qAn zpUzQ>kg4v)!%zWSY1*^_R;#lA>QrU>q=msE-Jjc<;!78<(NW3Chaab>40S389m(IDW-9@wYg=)uur*?YJ z=cT?E+N9Ai9vhnSd6BSHpRa0}!?2o_`t!D7trp4aeA0WYpmOM&Jr*(yJ zkq3ROQRD2PG1FK=8DKEX+qPSB3ytre!qRVFLcOx3MdAEFwWilwKJ6zX+qPO>nIs%dL|qBqP}m>O zR}k+lWS%kk_1nbf@y5@$Oq~R={+30Q{UX?RXf9Rv1!dv^_sFVCEhl+gV$U``6z410 zEK_FXvc_`7Kl`@lJ#D+0D4Q&2EA@DNkkZh45JVPT&&Bn28yg=?k6Mpi11uEOax}BjaebL)T+8xL}0CBAXXsu`&p%u zsL_WS@a#we*)Vwlp ztI%z2uo|dEZlyjfJ+-;~lD`gDiq}(6QW+ehCwa+*{Zofoe|0LHpJ*XTu!9tuJKV~X zyJJ|pWIA{Gnih}SJpkkM^P5X4Dz&akR%*9nU` zS5&R94{kn=QSGJu_5~O4PeF+pZ>^MdKXhVydTOK$l%1;Jn8Yzbc78^!&WO+RKrKeK zh_d*m_V8GV$i&gjsLoGZ@-t4f{+yRth6-JUkd~aUP=_xP{p(`GcQ2zyNLy#{!H(VG zhn@7pZm*CYsgJ@ED2xjeb!jDz7g$&F?J7Bi zInw$~{b>)tj~qrR(`72+{W0f98lu5StZSo0_sgvs6RN*B^B3Klc2?MIaV#(NNuL1= zdanci`eeXtA`Y0<2SVpGr@EsH2H~Xr4US??(m!2X_tWV8fXxeZ5oyi)7>}WVt!RFD z;Q)`Upt$S3=j-rQ6GOf%%Iy$8%k|l9sJ4{?HfKkD5;M^D1}q%=(_`S&wyBG4n0UKL zcZ12zCgOC308?^8frWTe!ivK;doc?cMx+hiUJIQs)k>}?o8E}^)e)8B4K|9xWmjcP z=C>#2+`Ixm%0aY1DY{q6NS745?~dlz6?T(VA}I=UI;$Uh3uluUWS?p=eemFs#X)|; zF+2VlR&s!RLngs~^@SBXhLt@Kk?K=v`(rP_vTALIfSoag*7DoGn0pQ_vr5Pg6;ZCA zv%K=@NEo=RGf&WMNRlm=XfT5g7w zOr>|bvZG0u6x>2J{1N$F)>O-RkmEJ(VujoU#q76q*sMxrf*W7XerZC}2%OEm@WN&i zrGMyM)N=KMoCd}2m4rz!&fvVe%aAT{_Vru3-SkUM=+H&IOmoR=rKA>Z`k zU<4l_)4CBg0&;jl8+eP*zkQ&^kyuYgW!37JRNE)ZMD*t!KWnKH4v@4Z#~g;AR;R1P zN;;%nsXPP0{JbiW(iP3MR_fMf;8OLnsyXA0Lgr-WZ|J(2bB@c_oBehdEu~0-Q>@(k zuXSDjGPD{tkWyrG-W~l6m7{F#WZtO*Qj54d{-5u!RaOylWfLPiKrP6>dY*QccWxQD z2bKxHc=lep=tpiP2r%gm%yVZ6)-lKa$ zKQ{MtzW-i@r%uXdY)LDTja1212WUt;0a}$l4M{89f3mCmAnkq@b<&3B+*P4|1ShZ3 zL_fNQo|1C?E7q@ThlAIO*}|f{?&~I;`hv^NZdNQV?lV0SR*$DiQa>9C#EA=O6>u>)zc0LZ{i^EgF^?~PMYpFW!a1)L)%*JbLy780-Jvm9mL9`! z73k=TACU{9v*u2%byjRXHk=(Uf9Io^HPu@@+4i0Fd|DOac=8Nz3{o|?5!01^XqM0a z<3*qrKv%QdZb8OD7r^R04te_b8l<^{;){RR2- zCMlyn4+BKgNX^HewfeZ1TwWzrjeNsliwp+%g4KJZ+R`Vjc8K-tklQO2eAHn~p!Y~Y z_0y9FW7d-i8udOxN$+3(xjxr@$UF^QtT_zBJ7eJv`TLsl(jN|aQ;&i3Kt(ZYtv=~S zo;9p@?N{F%5p5q0Ix?Ai&$Ibjw5s}8_=V(ebn(74n>i!A2!hNvdrV2pSap<$Zx`@* z{k(MQ&^tMDUmG2AG-Ell=c7}z#EsfTpLMV1{`N~~kvqnacVA;{T)R8F?lW;O74J=s zDND#~op&b%aOqObX^U8dcV3f-hh)wbbMzGax*X*TKl}_zTQ01bUkk4qc3;u39n``T;Chotd!KeqTMaK5I9Lo<8y;&6%%g@sDTjR z{7@r`IkpQL!eCRhup2wuu=M zYgVNx*_bdwgW25pf?5AvkVeq$7vUdsSgy(h)9aQtf-*WyYJ{u|+uzRY+I>1E<;awV#DdVuoo;D!HV&!rg#qT(G({{UUz1;YjG|6tw#m^G7-jjM29LUH^> zG2Ld=Shx~9fvsAYzHH5kYWBG)h`ifRjitOBRUMV}l;6ne?2h4T?!ekyQ9hK>=kLLM zg;XU6ooSLhme?41BU_R>N%MmYhO)+Z^m+98_fC9J(_!hTlJ6}?|1u3z41@fo24oW* zw6L1uee%k`$vEE=3HG}586xLA>$S6?!{d<#zWn+y>jUlFSEbicvP6t&+l>5bL7;coo^3<&<;=I5wfBBJlqTLvi5x& zm{mRYC6}2`L20q3L(TR-DM$2VKmT0{e`1=S;gcjVAT;bmSNKDHO}{NTt&K-sE)Bm& zeWviG$PQyqq$ttr$-=7RK~y8rN>AzQk~XNjL5d?+wNI;>ZMWO=Y{wgB6rp;us)$zx zb@`Ly3D4;Ljb9JvpyH*-%Id?jljpeO2G>)YI3HMURa!^m8b9TTPpB_5@=Z1*Zy}e* zLK>qRUw6_h?!c8bJzpP?mopez+ezY8rUgGCa*?1TU6$>o>qBiTAx995a~am~rxNSczQ?VLTlH9Zz)w?-@Heelp#%d=`(q_X+9 zAGR>S?^O>;H^()H+|lOAhm$Ya@*R0~pL~jt?RDcFl@4seoxDokuATKxmnYUmE#42W z)!RI}N|u4wqWSA-7V>r0Fwj=bdLdqkP8vo^%X__=Rk$W}A3)mI0 z6}@>6g3-LjpB-gi^^HEME}y-{21u>&P4U%|R73>*iq^0WYW~Slm!@W}K~}T4Q_oz$ z?qGJ>l3H({2xrGK)syD?v-A`<|0+V#>3yvqu+h%DX;5F%?mGv=eMj`}yZ>9&s4cti z+MGBxTx5pPS91!%{Ckyh+?Hnaqw$-~!O*!3E4dsVIpMwr08g+4hg|3=G0{_^r3!p| z&|6|V(^U9t=Si;Y@4XGa7ZrQc%$(@jnQNl(A=4IkrNd|Mh}C9+vGP2?nj1V>j{TFX zaL|_p!yWo1nNzXkomx$KklM}-_C`5ne~p*MM+q~7*d!_akcuUSNbjxT=~53HFIxYg z7fE?lR6ft)bDgi%{TyTxZP0)pA+b46Al!$4&X`>_CN~v3#`sL79 zCa<;=7c~Sz#LuG5B|Vsj7&X?D7^P9XZ|T0RCQ{UgPRm0tw6Jp3{h~2Xz&t_m9I=I8 z${5OSx0SFbJ%uc$e5vv>dTe2KWktD^ojuM;X~InY%|eHm*yiaVnA}mFGxK?wZ&;Rx z28`K;yZvU1Cv*-?_k;1=BWfLqw%;FLNi{BhcK0{#9KfQ#$V6v9>~0w3>!M?RE%@H0 z&Bz7Mdl_FKL75jOD9TPRzrV{GtM>&E25BBw0$%UA=2^NC(jO5W!E{+@T2ceqW`VQW zpYa+pEZ%0}y^}3a(p^6q!#f~|*GAaAr7j^B=P0w)4Cf+u}D&HPe>|6`5`M%$fqG-JH7O^ zj!Uj9+R~w7M%<9-3qjCoKh#K&_|;8?TW;NE{6cZT(*;DByp(9H@Li+T;{EBUyQ!4b zc(c>|yc}zO8X;cEN!2_TsgR|J)Ec-T3iHOZEQ`JOO#!>zBDUiJzFbc$I+&TksD~>j zC%{n5&s9B{?0)Z*y~9|OlPi6zW3rXe^N`%yTXOE~NQjU4D$0MQDq8ZFR}m_)a2!%C zt_`i#S_n%g#2>6bQEt|Km#2V#0IaJ*gyCX`b!2wT5yDo9v&1TBoaExv+5u2-=3(-p z`NsZ{Yk$$5HjJh8sw}WWFp|G~nISf+^b2%AhoHfcDfmjq0>zz@eq9^(vzlIz#@eLv z&=jHeXk6JXA^(qMsLu=}CE$G}JT%|}Y{c-yX=na%f-TQUz9eY__gGMUw9ph=jneBAx)tT)|)eso&2;0@|<~Yi=>=rN&D$kyzo_KoGw90Mfl7+0h zN(ijLe(ak$TV-ycPl07&-SbF1<;7p=5sO)K;}wqwtk#H?uxv^hG>9IhsQxnBJ4DNE z+eUt1@96OTVr4SMqx~~>jfKe4E*GVZXrFh5Y*jNxF5VPO5=&`&no9CjB@0~~ck(>- z_wF55CUR8~dq}5A&tJfJpHv31z|mChZtK(rQG1M7@+d$JER8bAg53qr>%^sS z+N;AV%F^-|rB<@!@kfS_*vN`6>JD@lA8zLWQhsuEA;^6x#=rxCn!l&9aSaEZk zud)Mtm7*{?F^mbIMJ#jY%uXrz;Kkj(0xRGEAOLL!!87hM^s~QwKWw%B`t5a&jQ+oB zdhUr_)E`klnftnD% ze`FwM0yNb%bkQ0bXmu?Yb$vrkZ9{E+Rdsbkb#=A5fBz=_DbhTyvlU>~at*P~& zvN%=6#>Qeo{{?XdC+J^IR%}r!hxouhVwaHU@H2*n77_l@|2ku6c#;E+0yN6^+SUFK z=0B+o4Y7ft!66htOG7setUpStorage(); + $this->copyMedia(); + + $imageResizer = new ImageResizer( + URL::to(MediaLibrary::url('october space.png')), + 100, + 100 + ); + + $this->assertStringContainsString('october%20space', $imageResizer->getResizedUrl(), 'Resized URLs are not properly URL encoded'); + } + protected function setUpStorage() { $this->app->useStoragePath(base_path('storage/temp')); From 67cabdcb28a61c02c5c34c4ec492d1b1d6f71739 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Tue, 8 Sep 2020 16:11:12 -0600 Subject: [PATCH 05/69] fix broken media tests --- tests/unit/system/classes/MediaLibraryTest.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/unit/system/classes/MediaLibraryTest.php b/tests/unit/system/classes/MediaLibraryTest.php index 52b70333b..96ce5ac18 100644 --- a/tests/unit/system/classes/MediaLibraryTest.php +++ b/tests/unit/system/classes/MediaLibraryTest.php @@ -76,17 +76,17 @@ class MediaLibraryTest extends TestCase // @codingStandardsIgnoreLine $contents = MediaLibrary::instance()->listFolderContents(); $this->assertNotEmpty($contents, 'Media library item is not discovered'); - $this->assertCount(2, $contents); - - $this->assertEquals('file', $contents[0]->type, 'Media library item does not have the right type'); - $this->assertEquals('/october.png', $contents[0]->path, 'Media library item does not have the right path'); - $this->assertNotEmpty($contents[0]->lastModified, 'Media library item last modified is empty'); - $this->assertNotEmpty($contents[0]->size, 'Media library item size is empty'); + $this->assertCount(3, $contents); $this->assertEquals('file', $contents[1]->type, 'Media library item does not have the right type'); - $this->assertEquals('/text.txt', $contents[1]->path, 'Media library item does not have the right path'); + $this->assertEquals('/october.png', $contents[1]->path, 'Media library item does not have the right path'); $this->assertNotEmpty($contents[1]->lastModified, 'Media library item last modified is empty'); $this->assertNotEmpty($contents[1]->size, 'Media library item size is empty'); + + $this->assertEquals('file', $contents[2]->type, 'Media library item does not have the right type'); + $this->assertEquals('/text.txt', $contents[2]->path, 'Media library item does not have the right path'); + $this->assertNotEmpty($contents[2]->lastModified, 'Media library item last modified is empty'); + $this->assertNotEmpty($contents[2]->size, 'Media library item size is empty'); } protected function setUpStorage() From 561815cac14dd079c70d89588cc7175e8839e6ef Mon Sep 17 00:00:00 2001 From: Raja Khoury Date: Tue, 8 Sep 2020 21:18:51 -0500 Subject: [PATCH 06/69] Define beforeAddAsset properties as references in docs. (#5269) --- modules/system/traits/AssetMaker.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/system/traits/AssetMaker.php b/modules/system/traits/AssetMaker.php index 4918872e7..94fc41e7f 100644 --- a/modules/system/traits/AssetMaker.php +++ b/modules/system/traits/AssetMaker.php @@ -208,7 +208,7 @@ trait AssetMaker * * Example usage: * - * Event::listen('system.assets.beforeAddAsset', function (string $type, string $path, array $attributes) { + * Event::listen('system.assets.beforeAddAsset', function (string &$type, string &$path, array &$attributes) { * if (in_array($path, $blockedAssets)) { * return false; * } @@ -216,7 +216,7 @@ trait AssetMaker * * Or * - * $this->bindEvent('assets.beforeAddAsset', function (string $type, string $path, array $attributes) { + * $this->bindEvent('assets.beforeAddAsset', function (string &$type, string &$path, array &$attributes) { * $attributes['special_cdn_flag'] = false; * }); * From b251867f56c6854e5daccd858b209709c8870b0f Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 9 Sep 2020 12:38:48 +0800 Subject: [PATCH 07/69] Use path.config binding in "october:env" to allow unit test to work --- modules/system/console/OctoberEnv.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/system/console/OctoberEnv.php b/modules/system/console/OctoberEnv.php index 43f2079bf..d13997b3e 100644 --- a/modules/system/console/OctoberEnv.php +++ b/modules/system/console/OctoberEnv.php @@ -1,5 +1,6 @@ config . '.php'), $content); + file_put_contents(rtrim(App::make('path.config'), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $this->config . '.php', $content); } /** @@ -335,7 +336,7 @@ class OctoberEnv extends Command */ protected function lines() { - return file(config_path($this->config . '.php')); + return file(rtrim(App::make('path.config'), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $this->config . '.php'); } /** From 619be11d23b77da1eacefea4a215601ac715e1a7 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 9 Sep 2020 17:47:18 +0800 Subject: [PATCH 08/69] Make "october:env" command privileged. This allows developers to use the command even if plugins are failing to boot due to missing configuration options (such as the DB configuration being incorrect). Previously, plugins which use the database in boot would prevent the command from running if the database details were incorrect. This change removes a barrier from configuring your site correctly. --- modules/system/ServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index 5ebce0b2f..0ed00230d 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -135,7 +135,7 @@ class ServiceProvider extends ModuleServiceProvider protected function registerPrivilegedActions() { $requests = ['/combine/', '@/system/updates', '@/system/install', '@/backend/auth']; - $commands = ['october:up', 'october:update', 'october:version']; + $commands = ['october:up', 'october:update', 'october:env', 'october:version']; /* * Requests From b407f26e024c9f3d10fc9c0ea14fe2649c01e1e2 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Thu, 10 Sep 2020 12:12:46 -0600 Subject: [PATCH 09/69] Add support for \Path\To\Class::staticMethodName for defining field options. Related: https://github.com/octobercms/library/commit/95d0b61a298d0933dd2117967481e00b416029c4 --- modules/backend/lang/en/lang.php | 1 + modules/backend/widgets/Form.php | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/modules/backend/lang/en/lang.php b/modules/backend/lang/en/lang.php index 368664c63..230e744a4 100644 --- a/modules/backend/lang/en/lang.php +++ b/modules/backend/lang/en/lang.php @@ -9,6 +9,7 @@ return [ 'invalid_type' => 'Invalid field type used :type.', 'options_method_invalid_model' => "The attribute ':field' does not resolve to a valid model. Try specifying the options method for model class :model explicitly.", 'options_method_not_exists' => "The model class :model must define a method :method() returning options for the ':field' form field.", + 'options_static_method_invalid_value' => "The static method ':method()' on :class did not return a valid options array.", 'colors_method_not_exists' => "The model class :model must define a method :method() returning html color HEX codes for the ':field' form field.", ], 'widget' => [ diff --git a/modules/backend/widgets/Form.php b/modules/backend/widgets/Form.php index 5b79a6c75..c2acbb2a3 100644 --- a/modules/backend/widgets/Form.php +++ b/modules/backend/widgets/Form.php @@ -1283,6 +1283,7 @@ class Form extends WidgetBase { /* * Advanced usage, supplied options are callable + * [\Path\To\Class, methodName] */ if (is_array($fieldOptions) && is_callable($fieldOptions)) { $fieldOptions = call_user_func($fieldOptions, $this, $field); @@ -1328,6 +1329,22 @@ class Form extends WidgetBase * Field options are an explicit method reference */ elseif (is_string($fieldOptions)) { + // \Path\To\Class::staticMethodOptions + if (str_contains($fieldOptions, '::')) { + $options = explode('::', $fieldOptions); + if (count($options) === 2 && class_exists($options[0]) && method_exists($options[0], $options[1])) { + $result = $options[0]::{$options[1]}(); + if (!is_array($result)) { + throw new ApplicationException(Lang::get('backend::lang.field.options_static_method_invalid_value', [ + 'class' => $options[0], + 'method' => $options[1] + ])); + } + return $result; + } + } + + // $model->{$fieldOptions}() if (!$this->objectMethodExists($this->model, $fieldOptions)) { throw new ApplicationException(Lang::get('backend::lang.field.options_method_not_exists', [ 'model' => get_class($this->model), From 4c41eee1b3e4e81134058541c46758e4bb6d33c3 Mon Sep 17 00:00:00 2001 From: serega-kasyanow Date: Fri, 11 Sep 2020 06:49:16 +0400 Subject: [PATCH 10/69] Update module facade docblocks (#5273) --- modules/backend/facades/Backend.php | 14 ++++++++++++- modules/backend/facades/BackendAuth.php | 14 ++++++++++--- modules/backend/facades/BackendMenu.php | 28 ++++++++++++++++++++++--- modules/cms/facades/Cms.php | 6 +++++- 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/modules/backend/facades/Backend.php b/modules/backend/facades/Backend.php index 4ca59f641..1a1ae24bd 100644 --- a/modules/backend/facades/Backend.php +++ b/modules/backend/facades/Backend.php @@ -2,12 +2,24 @@ use October\Rain\Support\Facade; +/** + * @method static string uri() + * @method static string url(string $path = null, array $parameters = [], bool $secure = null) + * @method static string baseUrl(string $path = null) + * @method static string skinAsset(string $path = null) + * @method static \Illuminate\Http\RedirectResponse redirect(string $path, int $status = 302, array $headers = [], bool $secure = null) + * @method static \Illuminate\Http\RedirectResponse redirectGuest(string $path, int $status = 302, array $headers = [], bool $secure = null) + * @method static \Illuminate\Http\RedirectResponse redirectIntended(string $path, int $status = 302, array $headers = [], bool $secure = null) + * @method static string date($dateTime, array $options = []) + * @method static string dateTime($dateTime, array $options = []) + * + * @see \Backend\Helpers\Backend + */ class Backend extends Facade { /** * Get the registered name of the component. * - * @see \Backend\Helpers\Backend * @return string */ protected static function getFacadeAccessor() diff --git a/modules/backend/facades/BackendAuth.php b/modules/backend/facades/BackendAuth.php index 99dde86f3..6c3cfc078 100644 --- a/modules/backend/facades/BackendAuth.php +++ b/modules/backend/facades/BackendAuth.php @@ -2,14 +2,22 @@ use October\Rain\Support\Facade; +/** + * @method static void registerCallback(callable $callback) + * @method static void registerPermissions(string $owner, array $definitions) + * @method static void removePermission(string $owner, string $code) + * @method static array listPermissions() + * @method static array listTabbedPermissions() + * @method static array listPermissionsForRole(string $role, bool $includeOrphans = true) + * @method static boolean hasPermissionsForRole(string $role) + * + * @see \Backend\Classes\AuthManager + */ class BackendAuth extends Facade { /** * Get the registered name of the component. * - * Resolves to: - * - Backend\Classes\AuthManager - * * @return string */ protected static function getFacadeAccessor() diff --git a/modules/backend/facades/BackendMenu.php b/modules/backend/facades/BackendMenu.php index eb93dff9a..9b305c188 100644 --- a/modules/backend/facades/BackendMenu.php +++ b/modules/backend/facades/BackendMenu.php @@ -2,14 +2,36 @@ use October\Rain\Support\Facade; +/** + * @method static void registerCallback(callable $callback) + * @method static void registerMenuItems(string $owner, array $definitions) + * @method static void addMainMenuItems(string $owner, array $definitions) + * @method static void addMainMenuItem(string $owner, $code, array $definition) + * @method static \Backend\Classes\MainMenuItem getMainMenuItem(string $owner, string $code) + * @method static void removeMainMenuItem(string $owner, string $code) + * @method static void addSideMenuItems(string $owner, string $code, array $definitions) + * @method static bool addSideMenuItem(string $owner, string $code, string $sideCode, array $definition) + * @method static bool removeSideMenuItem(string $owner, string $code, string $sideCode) + * @method static \Backend\Classes\MainMenuItem[] listMainMenuItems() + * @method static \Backend\Classes\SideMenuItem[] listSideMenuItems(string|null $owner = null, string|null $code = null) + * @method static void setContext(string $owner, string $mainMenuItemCode, string|null $sideMenuItemCode = null) + * @method static void setContextOwner(string $owner) + * @method static void setContextMainMenu(string $mainMenuItemCode) + * @method static object getContext() + * @method static void setContextSideMenu(string $sideMenuItemCode) + * @method static bool isMainMenuItemActive(\Backend\Classes\MainMenuItem $item) + * @method static \Backend\Classes\MainMenuItem|null getActiveMainMenuItem() + * @method static bool isSideMenuItemActive(\Backend\Classes\SideMenuItem $item) + * @method static void registerContextSidenavPartial(string $owner, string $mainMenuItemCode, string $partial) + * @method static mixed getContextSidenavPartial(string $owner, string $mainMenuItemCode) + * + * @see \Backend\Classes\NavigationManager + */ class BackendMenu extends Facade { /** * Get the registered name of the component. * - * Resolves to: - * - Backend\Classes\NavigationManager - * * @return string */ protected static function getFacadeAccessor() diff --git a/modules/cms/facades/Cms.php b/modules/cms/facades/Cms.php index 81931d80b..4657595e4 100644 --- a/modules/cms/facades/Cms.php +++ b/modules/cms/facades/Cms.php @@ -2,12 +2,16 @@ use October\Rain\Support\Facade; +/** + * @method static string url(string $path = null) + * + * @see \Cms\Helpers\Cms + */ class Cms extends Facade { /** * Get the registered name of the component. * - * @see \Cms\Helpers\Cms * @return string */ protected static function getFacadeAccessor() From e7b1862c441b8984a704bfd4c2e1e08b54de9b38 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 11 Sep 2020 15:26:58 +0800 Subject: [PATCH 11/69] Add unit tests for #5271 --- .../updates/drop_blog_settings_table.php | 0 .../october/tester/updates/version.yaml | 5 ++++ .../system/classes/VersionManagerTest.php | 24 ++++++++++++++----- 3 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 tests/fixtures/plugins/october/tester/updates/drop_blog_settings_table.php diff --git a/tests/fixtures/plugins/october/tester/updates/drop_blog_settings_table.php b/tests/fixtures/plugins/october/tester/updates/drop_blog_settings_table.php new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/plugins/october/tester/updates/version.yaml b/tests/fixtures/plugins/october/tester/updates/version.yaml index 7f9156ee8..fbb9eda1a 100644 --- a/tests/fixtures/plugins/october/tester/updates/version.yaml +++ b/tests/fixtures/plugins/october/tester/updates/version.yaml @@ -1,3 +1,8 @@ +1.2.0: + - "!!! Security update - see: https://octobercms.com" +1.1.0: + - !!! Drop support for blog settings + - drop_blog_settings_table.php 1.0.5: - Create blog settings table - Another update message diff --git a/tests/unit/system/classes/VersionManagerTest.php b/tests/unit/system/classes/VersionManagerTest.php index ba163d18c..2a2498fb4 100644 --- a/tests/unit/system/classes/VersionManagerTest.php +++ b/tests/unit/system/classes/VersionManagerTest.php @@ -24,7 +24,7 @@ class VersionManagerTest extends TestCase $result = self::callProtectedMethod($manager, 'getLatestFileVersion', ['\October\\Tester']); $this->assertNotNull($result); - $this->assertEquals('1.0.5', $result); + $this->assertEquals('1.2.0', $result); } public function testGetFileVersions() @@ -32,16 +32,24 @@ class VersionManagerTest extends TestCase $manager = VersionManager::instance(); $result = self::callProtectedMethod($manager, 'getFileVersions', ['\October\\Tester']); - $this->assertCount(5, $result); + $this->assertCount(7, $result); $this->assertArrayHasKey('1.0.1', $result); $this->assertArrayHasKey('1.0.2', $result); $this->assertArrayHasKey('1.0.3', $result); $this->assertArrayHasKey('1.0.4', $result); $this->assertArrayHasKey('1.0.5', $result); + $this->assertArrayHasKey('1.1.0', $result); + $this->assertArrayHasKey('1.2.0', $result); $sample = $result['1.0.1']; - $comment = array_shift($sample); - $this->assertEquals("Added some upgrade file and some seeding", $comment); + $this->assertEquals('Added some upgrade file and some seeding', $sample[0]); + + $sample = $result['1.1.0']; + $this->assertEquals('!!! Drop support for blog settings', $sample[0]); + $this->assertEquals('drop_blog_settings_table.php', $sample[1]); + + $sample = $result['1.2.0']; + $this->assertEquals('!!! Security update - see: https://octobercms.com', $sample[0]); /* * Test junk file @@ -70,9 +78,11 @@ class VersionManagerTest extends TestCase $manager = VersionManager::instance(); $result = self::callProtectedMethod($manager, 'getNewFileVersions', ['\October\\Tester', '1.0.3']); - $this->assertCount(2, $result); + $this->assertCount(4, $result); $this->assertArrayHasKey('1.0.4', $result); $this->assertArrayHasKey('1.0.5', $result); + $this->assertArrayHasKey('1.1.0', $result); + $this->assertArrayHasKey('1.2.0', $result); /* * When at version 0, should return everything @@ -80,12 +90,14 @@ class VersionManagerTest extends TestCase $manager = VersionManager::instance(); $result = self::callProtectedMethod($manager, 'getNewFileVersions', ['\October\\Tester']); - $this->assertCount(5, $result); + $this->assertCount(7, $result); $this->assertArrayHasKey('1.0.1', $result); $this->assertArrayHasKey('1.0.2', $result); $this->assertArrayHasKey('1.0.3', $result); $this->assertArrayHasKey('1.0.4', $result); $this->assertArrayHasKey('1.0.5', $result); + $this->assertArrayHasKey('1.1.0', $result); + $this->assertArrayHasKey('1.2.0', $result); } /** From ce361cae67cad5aeeab92b4b19ab03002f2e69b4 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 11 Sep 2020 15:34:24 +0800 Subject: [PATCH 12/69] Fix UpdatesController test --- tests/unit/system/classes/UpdatesControllerTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/unit/system/classes/UpdatesControllerTest.php b/tests/unit/system/classes/UpdatesControllerTest.php index 6535f37e3..63420924f 100644 --- a/tests/unit/system/classes/UpdatesControllerTest.php +++ b/tests/unit/system/classes/UpdatesControllerTest.php @@ -14,6 +14,12 @@ class UpdatesControllerTest extends TestCase $controller = $this->getMockBuilder(Updates::class)->disableOriginalConstructor()->getMock(); $expectedVersions = [ + '1.2.0' => [ + '!!! Security update - see: https://octobercms.com', + ], + '1.1.0' => [ + '!!! Drop support for blog settings', + ], '1.0.5' => [ 'Create blog settings table', 'Another update message', From 8a785e439395aa901d2b9d7bcb6a343a071c7870 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 11 Sep 2020 02:10:59 -0600 Subject: [PATCH 13/69] Tightened up the default permissions granted to the "Publisher" system role out of the box --- modules/backend/ServiceProvider.php | 13 ++++++++----- modules/system/ServiceProvider.php | 13 +++++++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/modules/backend/ServiceProvider.php b/modules/backend/ServiceProvider.php index 4c3825dd0..f815fc93b 100644 --- a/modules/backend/ServiceProvider.php +++ b/modules/backend/ServiceProvider.php @@ -141,31 +141,34 @@ class ServiceProvider extends ModuleServiceProvider $manager->registerPermissions('October.Backend', [ 'backend.access_dashboard' => [ 'label' => 'system::lang.permissions.view_the_dashboard', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', ], 'backend.manage_default_dashboard' => [ 'label' => 'system::lang.permissions.manage_default_dashboard', 'tab' => 'system::lang.permissions.name', + 'roles' => UserRole::CODE_DEVELOPER, ], 'backend.manage_users' => [ 'label' => 'system::lang.permissions.manage_other_administrators', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', + 'roles' => UserRole::CODE_DEVELOPER, ], 'backend.impersonate_users' => [ 'label' => 'system::lang.permissions.impersonate_users', 'tab' => 'system::lang.permissions.name', + 'roles' => UserRole::CODE_DEVELOPER, ], 'backend.manage_preferences' => [ 'label' => 'system::lang.permissions.manage_preferences', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', ], 'backend.manage_editor' => [ 'label' => 'system::lang.permissions.manage_editor', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', ], 'backend.manage_branding' => [ 'label' => 'system::lang.permissions.manage_branding', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', ], 'media.manage_media' => [ 'label' => 'backend::lang.permissions.manage_media', diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index 0ed00230d..0a7b5fda7 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -9,6 +9,7 @@ use Backend; use Request; use BackendMenu; use BackendAuth; +use Backend\Models\UserRole; use Twig\Extension\SandboxExtension; use Twig\Environment as TwigEnvironment; use System\Classes\MailManager; @@ -420,19 +421,23 @@ class ServiceProvider extends ModuleServiceProvider $manager->registerPermissions('October.System', [ 'system.manage_updates' => [ 'label' => 'system::lang.permissions.manage_software_updates', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', + 'roles' => UserRole::CODE_DEVELOPER, ], 'system.access_logs' => [ 'label' => 'system::lang.permissions.access_logs', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', + 'roles' => UserRole::CODE_DEVELOPER, ], 'system.manage_mail_settings' => [ 'label' => 'system::lang.permissions.manage_mail_settings', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', + 'roles' => UserRole::CODE_DEVELOPER, ], 'system.manage_mail_templates' => [ 'label' => 'system::lang.permissions.manage_mail_templates', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', + 'roles' => UserRole::CODE_DEVELOPER, ] ]); }); From 78a37298a4ed4602b383522344a31e311402d829 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 11 Sep 2020 02:10:59 -0600 Subject: [PATCH 14/69] Tightened up the default permissions granted to the "Publisher" system role out of the box (cherry picked from commit 8a785e439395aa901d2b9d7bcb6a343a071c7870) --- modules/backend/ServiceProvider.php | 13 ++++++++----- modules/system/ServiceProvider.php | 13 +++++++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/modules/backend/ServiceProvider.php b/modules/backend/ServiceProvider.php index b0a5026f8..7452c4247 100644 --- a/modules/backend/ServiceProvider.php +++ b/modules/backend/ServiceProvider.php @@ -140,31 +140,34 @@ class ServiceProvider extends ModuleServiceProvider $manager->registerPermissions('October.Backend', [ 'backend.access_dashboard' => [ 'label' => 'system::lang.permissions.view_the_dashboard', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', ], 'backend.manage_default_dashboard' => [ 'label' => 'system::lang.permissions.manage_default_dashboard', 'tab' => 'system::lang.permissions.name', + 'roles' => UserRole::CODE_DEVELOPER, ], 'backend.manage_users' => [ 'label' => 'system::lang.permissions.manage_other_administrators', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', + 'roles' => UserRole::CODE_DEVELOPER, ], 'backend.impersonate_users' => [ 'label' => 'system::lang.permissions.impersonate_users', 'tab' => 'system::lang.permissions.name', + 'roles' => UserRole::CODE_DEVELOPER, ], 'backend.manage_preferences' => [ 'label' => 'system::lang.permissions.manage_preferences', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', ], 'backend.manage_editor' => [ 'label' => 'system::lang.permissions.manage_editor', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', ], 'backend.manage_branding' => [ 'label' => 'system::lang.permissions.manage_branding', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', ], 'media.manage_media' => [ 'label' => 'backend::lang.permissions.manage_media', diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index ae065786a..284dbb94b 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -9,6 +9,7 @@ use Backend; use Request; use BackendMenu; use BackendAuth; +use Backend\Models\UserRole; use Twig\Extension\SandboxExtension; use Twig\Environment as TwigEnvironment; use System\Classes\MailManager; @@ -417,19 +418,23 @@ class ServiceProvider extends ModuleServiceProvider $manager->registerPermissions('October.System', [ 'system.manage_updates' => [ 'label' => 'system::lang.permissions.manage_software_updates', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', + 'roles' => UserRole::CODE_DEVELOPER, ], 'system.access_logs' => [ 'label' => 'system::lang.permissions.access_logs', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', + 'roles' => UserRole::CODE_DEVELOPER, ], 'system.manage_mail_settings' => [ 'label' => 'system::lang.permissions.manage_mail_settings', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', + 'roles' => UserRole::CODE_DEVELOPER, ], 'system.manage_mail_templates' => [ 'label' => 'system::lang.permissions.manage_mail_templates', - 'tab' => 'system::lang.permissions.name' + 'tab' => 'system::lang.permissions.name', + 'roles' => UserRole::CODE_DEVELOPER, ] ]); }); From 9dca130faee47d598cc6f4e6febe337a652e1f4d Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 11 Sep 2020 11:33:44 -0600 Subject: [PATCH 15/69] Add unit tests for the different ways of providing field options --- tests/unit/backend/widgets/FormTest.php | 76 +++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/tests/unit/backend/widgets/FormTest.php b/tests/unit/backend/widgets/FormTest.php index d373a1d86..211fc97a9 100644 --- a/tests/unit/backend/widgets/FormTest.php +++ b/tests/unit/backend/widgets/FormTest.php @@ -6,7 +6,28 @@ use October\Tests\Fixtures\Backend\Models\UserFixture; class FormTestModel extends Model { + public function modelCustomOptionsMethod() + { + return ['model', 'custom', 'options']; + } + public function getFieldNameOnModelOptionsMethodOptions() + { + return ['model', 'field name', 'options method']; + } + + public function getDropdownOptions() + { + return ['dropdown', 'options']; + } +} + +class FormHelper +{ + public static function staticMethodOptions() + { + return ['static', 'method']; + } } class FormTest extends PluginTestCase @@ -131,6 +152,61 @@ class FormTest extends PluginTestCase $this->assertEquals('[name="array[trigger][]"]', array_get($attributes, 'data-trigger')); } + public function testOptionsGeneration() + { + $form = new Form(null, [ + 'model' => new FormTestModel, + 'arrayName' => 'array', + 'fields' => [ + 'static_method_options' => [ + 'type' => 'dropdown', + 'options' => '\FormHelper::staticMethodOptions', + 'expect' => ['static', 'method'], + ], + 'callable_options' => [ + 'type' => 'dropdown', + 'options' => [\FormHelper::class, 'staticMethodOptions'], + 'expect' => ['static', 'method'], + ], + 'model_method_options' => [ + 'type' => 'dropdown', + 'options' => 'modelCustomOptionsMethod', + 'expect' => ['model', 'custom', 'options'], + ], + 'defined_options' => [ + 'type' => 'dropdown', + 'options' => ['value1', 'value2'], + 'expect' => ['value1', 'value2'], + ], + 'defined_options_key_value' => [ + 'type' => 'dropdown', + 'options' => [ + 'key1' => 'value1', + 'key2' => 'value2', + ], + 'expect' => [ + 'key1' => 'value1', + 'key2' => 'value2', + ], + ], + 'field_name_on_model_options_method' => [ + 'type' => 'dropdown', + 'expect' => ['model', 'field name', 'options method'], + ], + 'get_dropdown_options_method' => [ + 'type' => 'dropdown', + 'expect' => ['dropdown', 'options'], + ], + ] + ]); + + $form->render(); + + foreach ($form->getFields() as $name => $field) { + $this->assertEquals($field->options(), $field->config['expect']); + } + } + protected function restrictedFormFixture(bool $singlePermission = false) { return new Form(null, [ From dca6128501890473043cbe2bfd3016b4f48d39d6 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 11 Sep 2020 15:47:49 -0600 Subject: [PATCH 16/69] Change Twig template loading fallbacks Previously: - registered Laravel view file - attempt to load file as a CMS partial Now: - registered Laravel view file - valid CMS partials - any file that Twig can access (from the project root) rendered as a plain twig template (but with support for the CMS twig environment) Fixes https://github.com/octobercms/library/commit/80aab47f044a2660aa352450f55137598f362aa4#commitcomment-42223643, https://github.com/octobercms/october/issues/5261#issuecomment-691235167 --- modules/cms/twig/Loader.php | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/modules/cms/twig/Loader.php b/modules/cms/twig/Loader.php index d3fe3475b..c0b1dc10a 100644 --- a/modules/cms/twig/Loader.php +++ b/modules/cms/twig/Loader.php @@ -27,7 +27,9 @@ class Loader extends LoaderBase implements TwigLoaderInterface /** * Sets a CMS object to load the template from. + * * @param \Cms\Contracts\CmsObject $obj Specifies the CMS object. + * @return void */ public function setObject(CmsObject $obj) { @@ -37,6 +39,9 @@ class Loader extends LoaderBase implements TwigLoaderInterface /** * Returns the Twig content string. * This step is cached internally by Twig. + * + * @param string $name The template name + * @return TwigSource */ public function getSourceContext($name) { @@ -65,6 +70,9 @@ class Loader extends LoaderBase implements TwigLoaderInterface /** * Returns the Twig cache key. + * + * @param string $name The template name + * @return string */ public function getCacheKey($name) { @@ -77,6 +85,10 @@ class Loader extends LoaderBase implements TwigLoaderInterface /** * Determines if the content is fresh. + * + * @param string $name The template name + * @param mixed $time The time to check against the template + * @return bool */ public function isFresh($name, $time) { @@ -89,6 +101,9 @@ class Loader extends LoaderBase implements TwigLoaderInterface /** * Returns the file name of the loaded template. + * + * @param string $name The template name + * @return string */ public function getFilename($name) { @@ -101,6 +116,9 @@ class Loader extends LoaderBase implements TwigLoaderInterface /** * Checks that the template exists. + * + * @param string $name The template name + * @return bool */ public function exists($name) { @@ -115,11 +133,12 @@ class Loader extends LoaderBase implements TwigLoaderInterface * Internal method that checks if the template name matches * the loaded object, with fallback support to partials. * + * @param string $name The template name to validate * @return bool */ protected function validateCmsObject($name) { - if ($name === $this->obj->getFilePath()) { + if ($this->obj && $name === $this->obj->getFilePath()) { return true; } @@ -133,18 +152,29 @@ class Loader extends LoaderBase implements TwigLoaderInterface /** * Looks up a fallback CMS partial object. - * @return Cms\Classes\Partial + * + * @param string $name The filename to attempt to load a fallback CMS partial for + * @return Cms\Classes\Partial|bool Returns false if a CMS partial can't be found */ protected function findFallbackObject($name) { + // Ignore Laravel views if (strpos($name, '::') !== false) { return false; } + // Check the cache if (array_key_exists($name, $this->fallbackCache)) { return $this->fallbackCache[$name]; } - return $this->fallbackCache[$name] = CmsPartial::find($name); + // Attempt to load the path as a CMS Partial object + try { + $partial = CmsPartial::find($name); + } catch (\Exception $e) { + return false; + } + + return $this->fallbackCache[$name] = $partial; } } From fc9d6233a8da738b5b8bbdf2fb7e6fd67da12b66 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Sun, 13 Sep 2020 18:51:09 -0400 Subject: [PATCH 17/69] Allow october:fresh to remove remove the demo plugin even when the demo theme has already been removed (#5275) --- modules/system/console/OctoberFresh.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/system/console/OctoberFresh.php b/modules/system/console/OctoberFresh.php index e87e5422d..627eab62e 100644 --- a/modules/system/console/OctoberFresh.php +++ b/modules/system/console/OctoberFresh.php @@ -37,16 +37,24 @@ class OctoberFresh extends Command } $demoThemePath = themes_path().'/demo'; - if (File::exists($demoThemePath)) { - Artisan::call('plugin:remove', ['name' => 'October.Demo', '--force' => true]); File::deleteDirectory($demoThemePath); - $this->info('Demo has been removed! Enjoy a fresh start.'); + $this->info('Demo theme has been removed! Enjoy a fresh start.'); } else { $this->error('Demo theme is already removed.'); } + + $demoPluginPath = plugins_path().'/october/demo'; + if (File::exists($demoPluginPath)) { + Artisan::call('plugin:remove', ['name' => 'October.Demo', '--force' => true]); + + $this->info('Demo plugin has been removed! Enjoy a fresh start.'); + } + else { + $this->error('Demo plugin is already removed.'); + } } /** From 216b0d6004ec193263bdf2f535220d693f251508 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 14 Sep 2020 08:09:00 +0800 Subject: [PATCH 18/69] Remove errors from `october:fresh` and indicate which demo files were moved Follow-up work to fc9d6233a8da738b5b8bbdf2fb7e6fd67da12b66. --- modules/system/console/OctoberFresh.php | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/modules/system/console/OctoberFresh.php b/modules/system/console/OctoberFresh.php index 627eab62e..a10283268 100644 --- a/modules/system/console/OctoberFresh.php +++ b/modules/system/console/OctoberFresh.php @@ -35,25 +35,30 @@ class OctoberFresh extends Command if (!$this->confirmToProceed('Are you sure?')) { return; } + + $themeRemoved = false; + $pluginRemoved = false; $demoThemePath = themes_path().'/demo'; if (File::exists($demoThemePath)) { File::deleteDirectory($demoThemePath); - - $this->info('Demo theme has been removed! Enjoy a fresh start.'); - } - else { - $this->error('Demo theme is already removed.'); + $themeRemoved = true; } $demoPluginPath = plugins_path().'/october/demo'; if (File::exists($demoPluginPath)) { Artisan::call('plugin:remove', ['name' => 'October.Demo', '--force' => true]); - - $this->info('Demo plugin has been removed! Enjoy a fresh start.'); + $pluginRemoved = true; } - else { - $this->error('Demo plugin is already removed.'); + + if ($themeRemoved && $pluginRemoved) { + $this->info('Demo theme and plugin have been removed! Enjoy a fresh start.'); + } elseif ($themeRemoved) { + $this->info('Demo theme has been removed! Enjoy a fresh start.'); + } elseif ($pluginRemoved) { + $this->info('Demo plugin has been removed! Enjoy a fresh start.'); + } else { + $this->info('Demo theme and plugin have already been removed.'); } } From ed06a6f1ac94c219e37a16e74325cf0a89bbe73e Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 17 Sep 2020 12:45:55 +0800 Subject: [PATCH 19/69] Do minItem initialization before checking current value in repeater prep Fixes https://github.com/octobercms/october/issues/5274 --- modules/backend/formwidgets/Repeater.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/backend/formwidgets/Repeater.php b/modules/backend/formwidgets/Repeater.php index 95ac67c90..159f20c3e 100644 --- a/modules/backend/formwidgets/Repeater.php +++ b/modules/backend/formwidgets/Repeater.php @@ -248,16 +248,6 @@ class Repeater extends FormWidgetBase } } - if (!$this->childAddItemCalled && $currentValue === null) { - $this->formWidgets = []; - return; - } - - if ($this->childAddItemCalled && !isset($currentValue[$this->childIndexCalled])) { - // If no value is available but a child repeater has added an item, add a "stub" repeater item - $this->makeItemFormWidget($this->childIndexCalled); - } - // Ensure that the minimum number of items are preinitialized // ONLY DONE WHEN NOT IN GROUP MODE if (!$this->useGroups && $this->minItems > 0) { @@ -273,6 +263,16 @@ class Repeater extends FormWidgetBase } } + if (!$this->childAddItemCalled && $currentValue === null) { + $this->formWidgets = []; + return; + } + + if ($this->childAddItemCalled && !isset($currentValue[$this->childIndexCalled])) { + // If no value is available but a child repeater has added an item, add a "stub" repeater item + $this->makeItemFormWidget($this->childIndexCalled); + } + if (!is_array($currentValue)) { return; } From 618e2b58abd0beae01d5a291a840902d5e97c7d9 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Thu, 17 Sep 2020 22:53:49 -0600 Subject: [PATCH 20/69] Ignore images that can't be processed by the resizer --- modules/system/classes/ImageResizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/classes/ImageResizer.php b/modules/system/classes/ImageResizer.php index 0b1ae8af5..ee3006d25 100644 --- a/modules/system/classes/ImageResizer.php +++ b/modules/system/classes/ImageResizer.php @@ -612,7 +612,7 @@ class ImageResizer } } - if (!$disk || !$path || !$selectedSource) { + if (!$disk || !$path || !$selectedSource || (!in_array(FileHelper::extension($path), ['jpg', 'jpeg', 'png', 'webp', 'gif']))) { if (is_object($image)) { $image = get_class($image); } From fe2ca6c15eb04345448dd0674af4c5c8eb8ea8a8 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 18 Sep 2020 00:11:11 -0600 Subject: [PATCH 21/69] Fix issue where resized images were not correctly identified as already having been resized when atomic (blue/green) deployment strategies are used in conjunction with files being stored on the local filesystem in a shared symlinked storage folder. --- modules/system/classes/ImageResizer.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/system/classes/ImageResizer.php b/modules/system/classes/ImageResizer.php index ee3006d25..e5d463aca 100644 --- a/modules/system/classes/ImageResizer.php +++ b/modules/system/classes/ImageResizer.php @@ -219,9 +219,21 @@ class ImageResizer */ public function getConfig() { + $disk = $this->image['disk']; + + // Normalize local disk adapters with symlinked paths to their target path + // to support atomic deployments where the base application path changes + // each deployment but the realpath of the storage directory does not + if (FileHelper::isLocalDisk($disk)) { + $realPath = realpath($disk->getAdapter()->getPathPrefix()); + if ($realPath) { + $disk->getAdapter()->setPathPrefix($realPath); + } + } + $config = [ 'image' => [ - 'disk' => $this->image['disk'], + 'disk' => $disk, 'path' => $this->image['path'], 'source' => $this->image['source'], ], From 15ca68c22df788397b2e84a9abfa5ecbc579b3ff Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 18 Sep 2020 16:01:02 -0600 Subject: [PATCH 22/69] No need to throw exceptions when generating MediaLibrary URLs --- modules/system/classes/MediaLibrary.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/classes/MediaLibrary.php b/modules/system/classes/MediaLibrary.php index 40808854e..f11a17bad 100644 --- a/modules/system/classes/MediaLibrary.php +++ b/modules/system/classes/MediaLibrary.php @@ -536,7 +536,7 @@ class MediaLibrary */ public function getPathUrl($path) { - $path = $this->validatePath($path); + $path = $this->validatePath($path, true); $fullPath = $this->storagePath . implode("/", array_map("rawurlencode", explode("/", $path))); From 0101e1f96b49cf9324ca5ff405e42e810eabc96a Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Sat, 19 Sep 2020 14:07:23 -0600 Subject: [PATCH 23/69] Fix issue with image resizer URLs that contain URL-encoded characters (i.e. spaces) Don't double decode the URL when validating it because the routing engine already decoded it once --- modules/system/classes/ImageResizer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/system/classes/ImageResizer.php b/modules/system/classes/ImageResizer.php index e5d463aca..71ea874a4 100644 --- a/modules/system/classes/ImageResizer.php +++ b/modules/system/classes/ImageResizer.php @@ -483,7 +483,7 @@ class ImageResizer { // Slashes in URL params have to be double encoded to survive Laravel's router // @see https://github.com/octobercms/october/issues/3592#issuecomment-671017380 - $resizedUrl = urlencode(urlencode($this->getResizedUrl())); + $resizedUrl = rawurlencode(rawurlencode($this->getResizedUrl())); // Get the current configuration's identifier $identifier = $this->getIdentifier(); @@ -571,14 +571,14 @@ class ImageResizer // Process a string } elseif (is_string($image)) { // Parse the provided image path into a filesystem ready relative path - $relativePath = static::normalizePath(urldecode(parse_url($image, PHP_URL_PATH))); + $relativePath = static::normalizePath(rawurldecode(parse_url($image, PHP_URL_PATH))); // Loop through the sources available to the application to pull from // to identify the source most likely to be holding the image $resizeSources = static::getAvailableSources(); foreach ($resizeSources as $source => $details) { // Normalize the source path - $sourcePath = static::normalizePath(urldecode(parse_url($details['path'], PHP_URL_PATH))); + $sourcePath = static::normalizePath(rawurldecode(parse_url($details['path'], PHP_URL_PATH))); // Identify if the current source is a match if (starts_with($relativePath, $sourcePath)) { @@ -727,7 +727,7 @@ class ImageResizer { // Slashes in URL params have to be double encoded to survive Laravel's router // @see https://github.com/octobercms/october/issues/3592#issuecomment-671017380 - $decodedUrl = urldecode(urldecode($encodedUrl)); + $decodedUrl = rawurldecode($encodedUrl); $url = null; // The identifier should be the signed version of the decoded URL From 789de0217d0c06c95097261b59aee8902bf19ed7 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 24 Sep 2020 16:52:59 +0800 Subject: [PATCH 24/69] Add styling for fancy breadcrumb based on Backend brand colors --- .../backend/models/brandsetting/custom.less | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/modules/backend/models/brandsetting/custom.less b/modules/backend/models/brandsetting/custom.less index c15fc93a1..729f58680 100644 --- a/modules/backend/models/brandsetting/custom.less +++ b/modules/backend/models/brandsetting/custom.less @@ -193,3 +193,31 @@ div.control-componentlist { .nav.selector-group li.active { border-left-color: @brand-secondary; } + +// +// Fancy breadcrumb +// +body.breadcrumb-fancy .control-breadcrumb, +.control-breadcrumb.breadcrumb-fancy { + background-color: mix(black, saturate(@brand-secondary, 20%), 16%); + + li { + background-color: mix(black, saturate(@brand-secondary, 20%), 31%); + + &:last-child { + background-color: mix(black, saturate(@brand-secondary, 20%), 16%); + + &::before { + border-left-color: mix(black, saturate(@brand-secondary, 20%), 16%); + } + } + + &::after { + border-left-color: mix(black, saturate(@brand-secondary, 20%), 31%); + } + + &:not(:last-child)::before { + border-left-color: @brand-secondary; + } + } +} From b6ee7d9bcabc48cb073187f9abdea365b06fe2ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Andr=C3=A9=20Vullioud?= Date: Thu, 24 Sep 2020 17:47:38 +0200 Subject: [PATCH 25/69] Improve French translation (#5283) --- modules/system/lang/fr/validation.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/system/lang/fr/validation.php b/modules/system/lang/fr/validation.php index 7f02f584e..0fe72612f 100644 --- a/modules/system/lang/fr/validation.php +++ b/modules/system/lang/fr/validation.php @@ -22,7 +22,7 @@ return [ 'alpha_num' => 'Le champ :attribute ne peut contenir que des lettres et des chiffres.', 'array' => 'Le champ :attribute doit être un groupe.', 'before' => 'Le champ :attribute doit être une date avant le :date.', - 'before_or_equal' => 'LE champ :attribute doit être une date avant le ou égal à :date.', + 'before_or_equal' => 'Le champ :attribute doit être une date avant le ou égal à :date.', 'between' => [ 'numeric' => 'Le champ :attribute doit être compris entre :min - :max.', 'file' => 'Le champ :attribute doit être compris entre :min - :max kilooctets.', @@ -36,8 +36,8 @@ return [ 'different' => 'Le champ :attribute et :other doivent être différents.', 'digits' => 'Le champ :attribute doit être de :digits chiffres.', 'digits_between' => 'Le champ :attribute doit être compris entre :min et :max chiffres.', - 'dimensions' => 'Le cahmp :attribute a des dimensions d’image invalides.', - 'distinct' => 'Le cahmp :attribute a une valeur en double..', + 'dimensions' => 'Le champ :attribute a des dimensions d’image invalides.', + 'distinct' => 'Le champ :attribute a une valeur en double..', 'email' => 'Le format du champ :attribute n’est pas valide.', 'exists' => 'Le champ :attribute sélectionné n’est pas valide.', 'file' => 'Le champ :attribute doit être un fichier.', From d4ac1595adaf56730e99582aafdf9a888257ac84 Mon Sep 17 00:00:00 2001 From: Jukka Date: Tue, 29 Sep 2020 20:56:51 +0300 Subject: [PATCH 26/69] Update auth.php (#5289) typo fix --- config/auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/auth.php b/config/auth.php index fe87b873e..8dc13b61c 100644 --- a/config/auth.php +++ b/config/auth.php @@ -19,7 +19,7 @@ return [ | Failed Authentication Attempt Limit |-------------------------------------------------------------------------- | - | Number of failed attemps allowed while trying to authenticate a user. + | Number of failed attempts allowed while trying to authenticate a user. | */ 'attemptLimit' => 5, From f9e14b02f586d509499b292ea9b8017208f02ce3 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Wed, 30 Sep 2020 11:11:03 +1000 Subject: [PATCH 27/69] Only allow view files in system twig This no longer allows arbitrary inclusions, only views from the native Laravel view engine. Note this also affects the cms twig loader --- modules/system/twig/Loader.php | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/modules/system/twig/Loader.php b/modules/system/twig/Loader.php index 341d08959..28b52c834 100644 --- a/modules/system/twig/Loader.php +++ b/modules/system/twig/Loader.php @@ -2,6 +2,7 @@ use App; use File; +use View; use Twig\Source as TwigSource; use Twig\Loader\LoaderInterface as TwigLoaderInterface; use Exception; @@ -14,11 +15,6 @@ use Exception; */ class Loader implements TwigLoaderInterface { - /** - * @var string Expected file extension - */ - protected $extension = 'htm'; - /** * @var array Cache */ @@ -37,22 +33,13 @@ class Loader implements TwigLoaderInterface return $this->cache[$name]; } - if (File::isFile($name)) { - return $this->cache[$name] = $name; - } - - $view = $name; - if (File::extension($view) === $this->extension) { - $view = substr($view, 0, -strlen($this->extension)); - } - - $path = $finder->find($view); + $path = $finder->find($name); return $this->cache[$name] = $path; } public function getSourceContext($name) { - return new TwigSource(File::get($this->findTemplate($name)), $name); + return new TwigSource((string) View::make($name), $name); } public function getCacheKey($name) From 4d25ec6813488f87c9402cfaa64eb96fb980862e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Ora=C5=BEem?= Date: Fri, 2 Oct 2020 10:35:39 +0200 Subject: [PATCH 28/69] Improve Slovenian translation (#5292) --- modules/backend/lang/sl/lang.php | 26 ++++-- modules/system/lang/sl/lang.php | 121 +++++++++++++------------- modules/system/lang/sl/validation.php | 41 +++++++-- 3 files changed, 115 insertions(+), 73 deletions(-) diff --git a/modules/backend/lang/sl/lang.php b/modules/backend/lang/sl/lang.php index d8ca85772..4cb459a2b 100644 --- a/modules/backend/lang/sl/lang.php +++ b/modules/backend/lang/sl/lang.php @@ -6,10 +6,11 @@ return [ 'invalid_login' => 'Podatki, ki ste jih vnesli, se ne ujemajo z našimi zapisi. Prosimo, ponovno preverite podatke in poskusite znova.', ], 'field' => [ - 'invalid_type' => 'Uporabljen je neveljaven tip polja :type.', - 'options_method_invalid_model' => "Atribut ':field' ne ustreza veljavnemu modelu. Poskusite natančno določiti možnosti metode za model :model.", - 'options_method_not_exists' => "Model :model mora vsebovati metodo :method(), ki vrača možnosti za polje ':field' na obrazcu.", - 'colors_method_not_exists' => "Model :model mora vsebovati metodo :method(), ki vrača HTML barvne kode v HEX formatu za polje ':field' na obrazcu.", + 'invalid_type' => 'Uporabljen je neveljaven tip polja :type.', + 'options_method_invalid_model' => "Atribut ':field' ne ustreza veljavnemu modelu. Poskusite natančno določiti možnosti metode za model :model.", + 'options_method_not_exists' => "Model :model mora vsebovati metodo :method(), ki vrača možnosti za polje ':field' na obrazcu.", + 'options_static_method_invalid_value' => "Statična metoda ':method()' v razredu :class ni vrnila veljavnih možnosti.", + 'colors_method_not_exists' => "Model :model mora vsebovati metodo :method(), ki vrača HTML barvne kode v HEX formatu za polje ':field' na obrazcu.", ], 'widget' => [ 'not_registered' => "Ime vtičnika ':name' ni bilo registrirano.", @@ -48,6 +49,10 @@ return [ 'impersonate_working' => 'Impersoniram...', 'impersonating' => 'Impersonacija uporabnika :full_name', 'stop_impersonating' => 'Prekliči impersonacijo', + 'unsuspend' => 'Odsuspendiraj', + 'unsuspend_confirm' => 'Ali ste prepričani, da želite odsuspendirati tega uporabnika?', + 'unsuspend_success' => 'Uporabnik je odsuspendiran.', + 'unsuspend_working' => 'Odsuspendiram...', 'signed_in_as' => 'Prijavljeni ste kot :full_name', 'sign_out' => 'Odjava', 'login' => 'Prijava', @@ -402,6 +407,7 @@ return [ 'label' => 'Opis', 'class_name' => 'Oznaka razreda', 'markup_tags' => 'Označevalne oznake', + 'markup_tag' => 'Označevalna oznaka', 'allowed_empty_tags' => 'Dovoljene prazne oznake', 'allowed_empty_tags_comment' => 'Seznam oznak, ki niso odstranjene, če v njih ni vsebine.', 'allowed_tags' => 'Dovoljene oznake', @@ -412,6 +418,7 @@ return [ 'remove_tags_comment' => 'Seznam oznak, ki so odstranjene skupaj z njihovo vsebino.', 'line_breaker_tags' => 'Oznake prekinitve vrstic', 'line_breaker_tags_comment' => 'Seznam oznak, ki se uporabljajo za postavitev elementa prekinitve med vrstice.', + 'toolbar_options' => 'Nastavitve orodne vrstice', 'toolbar_buttons' => 'Gumbi orodne vrstice', 'toolbar_buttons_comment' => 'Gumbi orodne vrstice, ki se privzeto prikažejo v urejevalniku. [fullscreen, bold, italic, underline, strikeThrough, subscript, superscript, fontFamily, fontSize, |, color, emoticons, inlineStyle, paragraphStyle, |, paragraphFormat, align, formatOL, formatUL, outdent, indent, quote, insertHR, -, insertLink, insertImage, insertVideo, insertAudio, insertFile, insertTable, undo, redo, clearFormatting, selectAll, html]', 'toolbar_buttons_preset' => 'Vstavite prednastavljeno konfiguracijo gumbov orodne vrstice:', @@ -420,6 +427,8 @@ return [ 'minimal' => 'Minimalno', 'full' => 'Polno', ], + 'paragraph_formats' => 'Formati odstavkov', + 'paragraph_formats_comment' => 'Možnosti, ki se prikažejo v spustnem seznamu Format odstavka.', ], 'tooltips' => [ 'preview_website' => 'Ogled spletne strani', @@ -470,7 +479,7 @@ return [ ], 'access_log' => [ 'hint' => 'Ta dnevnik beleži seznam uspešnih prijav administratorjev. Zapisi se hranijo :days dni.', - 'menu_label' => 'Dnevnik dostopa', + 'menu_label' => 'Dnevnik dostopov', 'menu_description' => 'Prikaz seznama uspešnih prijav administratorjev.', 'id' => 'ID', 'created_at' => 'Datum in čas', @@ -558,11 +567,12 @@ return [ 'iso_8859_15' => 'ISO-8859-15 (Latin-9, Western European revision with euro sign)', 'windows_1250' => 'Windows-1250 (CP1250, Central and Eastern European)', 'windows_1251' => 'Windows-1251 (CP1251)', - 'windows_1252' => 'Windows-1252 (CP1252)' - ] + 'windows_1252' => 'Windows-1252 (CP1252)', + ], ], 'permissions' => [ - 'manage_media' => 'Nalaganje in upravljanje z media vsebinami - slike, video posnetki, zvočni posnetki, dokumenti', + 'manage_media' => 'Nalaganje in upravljanje z media vsebinami - slike, video posnetki, zvočni posnetki, dokumenti', + 'allow_unsafe_markdown' => 'Dovoli uporabo nevarnih označb (lahko vključi Javascript)', ], 'mediafinder' => [ 'label' => 'Media brskalnik', diff --git a/modules/system/lang/sl/lang.php b/modules/system/lang/sl/lang.php index 12f62a43b..7aec2800e 100644 --- a/modules/system/lang/sl/lang.php +++ b/modules/system/lang/sl/lang.php @@ -106,7 +106,7 @@ return [ ], 'plugins' => [ 'manage' => 'Upravljanje vtičnikov', - 'install' => 'Namesti vtičnike in teme', + 'install' => 'Namesti vtičnike', 'install_products' => 'Namesti produkte', 'search' => 'išči vtičnike za namestitev...', 'installed' => 'Nameščeni vtičniki', @@ -310,74 +310,77 @@ return [ 'install_success' => 'Produkt je bil uspešno nameščen.', ], 'updates' => [ - 'title' => 'Upravljanje posodobitev', - 'name' => 'Posodobitev programske opreme', - 'menu_label' => 'Posodobitve in vtičniki', - 'menu_description' => 'Posodobitev sistema, upravljanje in nameščanje vtičnikov in tem.', - 'return_link' => 'Vrni se na sistemske posodobitve', - 'check_label' => 'Preveri za nove posodobitve', - 'retry_label' => 'Poskusi ponovno', - 'plugin_name' => 'Ime', - 'plugin_code' => 'Koda', - 'plugin_description' => 'Opis', - 'plugin_version' => 'Različica', - 'plugin_author' => 'Avtor', - 'plugin_not_found' => 'Vtičnika ni mogoče najti', - 'core_current_build' => 'Trenutna različica', - 'core_view_changelog' => 'Pokaži dnevnik sprememb', - 'core_build' => 'Različica :build', - 'core_build_help' => 'Na voljo je najnovejša različica.', - 'core_downloading' => 'Prenašanje datotek aplikacije', - 'core_extracting' => 'Ekstrahiranje datotek aplikacije', - 'core_set_build' => 'Nastavljanje številke različice', - 'changelog' => 'Dnevnik sprememb', - 'changelog_view_details' => 'Pokaži podrobnosti', - 'plugins' => 'Vtičniki', - 'themes' => 'Teme', - 'disabled' => 'Onemogočeno', - 'plugin_downloading' => 'Prenašanje vtičnika :name', - 'plugin_extracting' => 'Ekstrahiranje vtičnika :name', - 'plugin_version_none' => 'Nov vtičnik', - 'plugin_current_version' => 'Trenutna verzija', - 'theme_new_install' => 'Namestitev nove teme.', - 'theme_downloading' => 'Prenašanje teme :name', - 'theme_extracting' => 'Ekstrahiranje teme :name', - 'update_label' => 'Posodobitev programske opreme', - 'update_completing' => 'Zaključevanje posodabljanja', - 'update_loading' => 'Nalaganje razpoložljivih posodobitev...', - 'update_success' => 'Posodabljanje končano.', - 'update_failed_label' => 'Posodabljanje ni bilo uspešno.', - 'force_label' => 'Vsili posodobitev', - 'found' => [ + 'title' => 'Upravljanje posodobitev', + 'name' => 'Posodobitev programske opreme', + 'menu_label' => 'Posodobitve in vtičniki', + 'menu_description' => 'Posodobitev sistema, upravljanje in nameščanje vtičnikov in tem.', + 'return_link' => 'Vrni se na sistemske posodobitve', + 'check_label' => 'Preveri za nove posodobitve', + 'retry_label' => 'Poskusi ponovno', + 'plugin_name' => 'Ime', + 'plugin_code' => 'Koda', + 'plugin_description' => 'Opis', + 'plugin_version' => 'Različica', + 'plugin_author' => 'Avtor', + 'plugin_not_found' => 'Vtičnika ni mogoče najti', + 'plugin_version_not_found' => 'Različice vtičnika ni mogoče najti', + 'core_current_build' => 'Trenutna različica', + 'core_view_changelog' => 'Pokaži dnevnik sprememb', + 'core_build' => 'Različica :build', + 'core_build_help' => 'Na voljo je najnovejša različica.', + 'core_downloading' => 'Prenašanje datotek aplikacije', + 'core_extracting' => 'Ekstrahiranje datotek aplikacije', + 'core_set_build' => 'Nastavljanje številke različice', + 'update_warnings_title' => 'Odkrite so bile nekatere težave, ki zahtevajo pozornost:', + 'update_warnings_plugin_missing' => 'Vtičnik :parent_code zahteva za pravilno delovanje namestitev :code.', + 'changelog' => 'Dnevnik sprememb', + 'changelog_view_details' => 'Prikaži podrobnosti', + 'plugins' => 'Vtičniki', + 'themes' => 'Teme', + 'disabled' => 'Onemogočeno', + 'plugin_downloading' => 'Prenašanje vtičnika :name', + 'plugin_extracting' => 'Ekstrahiranje vtičnika :name', + 'plugin_version_none' => 'Nov vtičnik', + 'plugin_current_version' => 'Trenutna verzija', + 'theme_new_install' => 'Namestitev nove teme.', + 'theme_downloading' => 'Prenašanje teme :name', + 'theme_extracting' => 'Ekstrahiranje teme :name', + 'update_label' => 'Posodobitev programske opreme', + 'update_completing' => 'Zaključevanje posodabljanja', + 'update_loading' => 'Nalaganje razpoložljivih posodobitev...', + 'update_success' => 'Posodabljanje končano.', + 'update_failed_label' => 'Posodabljanje ni bilo uspešno.', + 'force_label' => 'Vsili posodobitev', + 'found' => [ 'label' => 'Nove posodobitve so na voljo!', - 'help' => "Kliknite 'Posodobitev programske opreme' za začetek posodabljanja.", + 'help' => 'Kliknite "Posodobitev programske opreme" za začetek posodabljanja.', ], - 'none' => [ + 'none' => [ 'label' => 'Ni posodobitev', 'help' => 'Nove posodobitve niso na voljo.', ], - 'important_action' => [ + 'important_action' => [ 'empty' => 'Izberite dejanje', 'confirm' => 'Potrdi posodobitev', 'skip' => 'Preskoči to posodobitev (le tokrat)', 'ignore' => 'Preskoči to posodobitev (vedno)', ], - 'important_action_required' => 'Potrebno je ukrepanje', - 'important_view_guide' => 'Oglejte si navodila za nadgradnjo', - 'important_view_release_notes' => 'Oglejte si opombe ob izdaji', - 'important_alert_text' => 'Nekatere posodobitve potrebujejo vašo pozornost.', - 'details_title' => 'Podrobnosti vtičnika', - 'details_view_homepage' => 'Odpri spletno stran', - 'details_readme' => 'Dokumentacija', - 'details_readme_missing' => 'Dokumentacija ni na voljo.', - 'details_changelog' => 'Dnevnik sprememb', - 'details_changelog_missing' => 'Dnevnik sprememb ni na voljo.', - 'details_upgrades' => 'Navodila za nadgradnjo', - 'details_upgrades_missing' => 'Navodila za nadgradnjo niso na voljo.', - 'details_licence' => 'Licenca', - 'details_licence_missing' => 'Licenca ni na voljo.', - 'details_current_version' => 'Trenutna različica', - 'details_author' => 'Avtor', + 'important_action_required' => 'Potrebno je ukrepanje', + 'important_view_guide' => 'Oglejte si navodila za nadgradnjo', + 'important_view_release_notes' => 'Oglejte si opombe ob izdaji', + 'important_alert_text' => 'Nekatere posodobitve potrebujejo vašo pozornost.', + 'details_title' => 'Podrobnosti vtičnika', + 'details_view_homepage' => 'Odpri spletno stran', + 'details_readme' => 'Dokumentacija', + 'details_readme_missing' => 'Dokumentacija ni na voljo.', + 'details_changelog' => 'Dnevnik sprememb', + 'details_changelog_missing' => 'Dnevnik sprememb ni na voljo.', + 'details_upgrades' => 'Navodila za nadgradnjo', + 'details_upgrades_missing' => 'Navodila za nadgradnjo niso na voljo.', + 'details_licence' => 'Licenca', + 'details_licence_missing' => 'Licenca ni na voljo.', + 'details_current_version' => 'Trenutna različica', + 'details_author' => 'Avtor', ], 'server' => [ 'connect_error' => 'Napaka pri povezavi s strežnikom.', diff --git a/modules/system/lang/sl/validation.php b/modules/system/lang/sl/validation.php index 0a934db29..14752296c 100644 --- a/modules/system/lang/sl/validation.php +++ b/modules/system/lang/sl/validation.php @@ -32,6 +32,7 @@ return [ 'boolean' => '":attribute" mora biti Da (true) ali Ne (false).', 'confirmed' => 'Potrditev polja ":attribute" se ne ujema.', 'date' => '":attribute" ni veljaven datum.', + 'date_equals' => '":attribute" mora biti datum enak :date.', 'date_format' => '":attribute" se ne ujema s formatom :format.', 'different' => '":attribute" in :other morata biti različna.', 'digits' => '":attribute" mora vsebovati :digits številk.', @@ -39,9 +40,22 @@ return [ 'dimensions' => '":attribute" vsebuje neveljavne dimenzije slike.', 'distinct' => '":attribute" polje ima podvojeno vrednost.', 'email' => '":attribute" mora biti veljaven e-poštni naslov.', + 'ends_with' => '":attribute" se mora končati z eno od naslednjih vrednosti: :values.', 'exists' => 'Izbran element ":attribute" je neveljaven.', 'file' => '":attribute" mora biti datoteka.', 'filled' => '":attribute" polje mora vsebovati vrednost.', + 'gt' => [ + 'numeric' => ':attribute mora biti večji od :value.', + 'file' => ':attribute mora biti večji od :value kB.', + 'string' => ':attribute mora vsebovati več kot :value znakov.', + 'array' => ':attribute mora vsebovati več kot :value elementov.', + ], + 'gte' => [ + 'numeric' => ':attribute mora biti večji ali enak kot :value.', + 'file' => ':attribute mora biti večji ali enak kot :value kB.', + 'string' => ':attribute mora vsebovati vsaj ali več kot :value znakov.', + 'array' => ':attribute mora vsebovati vsaj :value elementov ali več.', + ], 'image' => '":attribute" mora biti slika.', 'in' => 'Izbran element ":attribute" je neveljaven.', 'in_array' => 'Element ":attribute" ne obstaja v ":other".', @@ -50,6 +64,18 @@ return [ 'ipv4' => '":attribute" mora biti veljaven IPv4 naslov.', 'ipv6' => '":attribute" mora biti veljaven IPv6 naslov.', 'json' => '":attribute" mora biti veljaven JSON format.', + 'lt' => [ + 'numeric' => ':attribute mora biti manjši od :value.', + 'file' => ':attribute mora biti manjši od :value kB.', + 'string' => ':attribute mora vsebovati manj kot :value znakov.', + 'array' => ':attribute mora vsebovati manj kot :value elementov.', + ], + 'lte' => [ + 'numeric' => ':attribute mora biti manjši ali enak kot :value.', + 'file' => ':attribute mora biti manjši ali enak kot :value kB.', + 'string' => ':attribute mora vsebovati manj ali enako kot :value znakov.', + 'array' => ':attribute ne sme vsebovati več kot :value elementov.', + ], 'max' => [ 'numeric' => ':attribute ne sme biti večji od :max.', 'file' => ':attribute ne sme biti večji od :max kB.', @@ -65,6 +91,7 @@ return [ 'array' => ':attribute mora vsebovati vsaj :min elementov.', ], 'not_in' => 'Izbrani element ":attribute" je neveljaven.', + 'not_regex' => 'Format za ":attribute" je neveljaven.', 'numeric' => '":attribute" mora biti število.', 'present' => 'Polje za ":attribute" mora obstajati.', 'regex' => 'Format za ":attribute" je neveljaven.', @@ -74,7 +101,7 @@ return [ 'required_with' => 'Polje za ":attribute" je obvezno, kadar :values obstaja.', 'required_with_all' => 'Polje za ":attribute" je obvezno, kadar :values obstaja.', 'required_without' => 'Polje za ":attribute" je obvezno, kadar :values ne obstaja.', - 'required_without_all' => 'Polje za ":attribute" je obvezno, kadar :values ne obstaja.', + 'required_without_all' => 'Polje za ":attribute" je obvezno, kadar nobeden od :values ne obstaja.', 'same' => '":attribute" in ":other" se morata ujemati.', 'size' => [ 'numeric' => ':attribute mora biti :size.', @@ -82,11 +109,13 @@ return [ 'string' => ':attribute mora vsebovati :size znakov.', 'array' => ':attribute mora vsebovati :size elementov.', ], - 'string' => ':attribute mora biti veljaven znakovni niz.', - 'timezone' => ':attribute mora biti veljavno območje.', - 'unique' => 'Element :attribute je že uporabljen.', - 'uploaded' => 'Elementa :attribute ni bilo mogoče naložiti.', - 'url' => 'Format elementa :attribute je neveljaven.', + 'starts_with' => '":attribute" se mora začeti z eno od naslednjih vrednosti: :values.', + 'string' => '":attribute" mora biti veljaven znakovni niz.', + 'timezone' => '":attribute" mora biti veljavno območje.', + 'unique' => 'Element ":attribute" je že zaseden.', + 'uploaded' => 'Elementa ":attribute" ni bilo mogoče naložiti.', + 'url' => 'Format za ":attribute" je neveljaven.', + 'uuid' => '":attribute" mora biti veljaven UUID.', /* |-------------------------------------------------------------------------- From f8f5e6e02273c81488542e51be50b5baed7a8af2 Mon Sep 17 00:00:00 2001 From: Nick Khaetsky Date: Fri, 2 Oct 2020 11:38:10 +0300 Subject: [PATCH 29/69] Changing translation of "layout" word in Russian (#5291) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Last commit changed translation of word "layout" from 'Шаблон' to 'Макет'. By "straightforward" translation this was maybe correct, but contextually not. We decided to make a poll, to vote for new translation. --- modules/system/lang/ru/lang.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/modules/system/lang/ru/lang.php b/modules/system/lang/ru/lang.php index 3d6fffa89..9b4f54c07 100644 --- a/modules/system/lang/ru/lang.php +++ b/modules/system/lang/ru/lang.php @@ -166,18 +166,18 @@ return [ 'mail_templates' => [ 'menu_label' => 'Шаблоны почты', 'menu_description' => 'Изменение шаблонов писем, отправляемых пользователям и администраторам.', - 'new_template' => 'Новый шаблон', - 'new_layout' => 'Новый макет', + 'new_template' => 'Новая тема', + 'new_layout' => 'Новый шаблон', 'new_partial' => 'Новый фрагмент', 'template' => 'Шаблон', 'templates' => 'Шаблоны', 'partial' => 'Фрагмент', 'partials' => 'Фрагменты', - 'menu_layouts_label' => 'Макеты почты', + 'menu_layouts_label' => 'Шаблоны почты', 'menu_partials_label' => 'Фрагменты почты', - 'layout' => 'Макет', - 'layouts' => 'Макеты', - 'no_layout' => '-- Нет макета --', + 'layout' => 'Шаблон', + 'layouts' => 'Шаблоны', + 'no_layout' => '-- Нет шаблона --', 'name' => 'Название', 'name_comment' => 'Уникальное имя, используемое для обозначения этого шаблона', 'code' => 'Код', @@ -191,14 +191,14 @@ return [ 'test_send' => 'Отправить тестовое сообщение', 'test_success' => 'Тестовое сообщение было успешно отправлено.', 'test_confirm' => 'Тестовое сообщение будет отправлено на :email. Продолжить?', - 'creating' => 'Создание шаблона...', - 'creating_layout' => 'Создание макета...', - 'saving' => 'Сохранение шаблона...', - 'saving_layout' => 'Сохранение макета...', - 'delete_confirm' => 'Вы действительно хотите удалить этот шаблон?', - 'delete_layout_confirm' => 'Вы действительно хотите удалить этот макет?', - 'deleting' => 'Удаление шаблона...', - 'deleting_layout' => 'Удаление макета...', + 'creating' => 'Создание...', + 'creating_layout' => 'Создание шаблона...', + 'saving' => 'Сохранение...', + 'saving_layout' => 'Сохранение шаблона...', + 'delete_confirm' => 'Вы действительно хотите это удалить?', + 'delete_layout_confirm' => 'Вы действительно хотите удалить этот шаблон?', + 'deleting' => 'Удаление...', + 'deleting_layout' => 'Удаление шаблона...', 'sending' => 'Отправка тестового сообщения...', 'return' => 'Вернуться к списку шаблонов', 'options' => 'Опции', From af6db51b7978aae874844e79e0103b0952d7b762 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 7 Oct 2020 14:05:17 +0800 Subject: [PATCH 30/69] Allow "no records" message to be defined for relation widgets. Both the "view" and "manage" widgets, in list mode, now support a custom message when no records are available. To maintain BC, if "emptyMessage" is provided in the relation config, this is still used for the "view" widget in list mode. Fixes https://github.com/octobercms/october/issues/3594 --- modules/backend/behaviors/RelationController.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/backend/behaviors/RelationController.php b/modules/backend/behaviors/RelationController.php index 510242597..88ef54ff4 100644 --- a/modules/backend/behaviors/RelationController.php +++ b/modules/backend/behaviors/RelationController.php @@ -669,8 +669,9 @@ class RelationController extends ControllerBehavior $config->defaultSort = $this->getConfig('view[defaultSort]'); $config->recordsPerPage = $this->getConfig('view[recordsPerPage]'); $config->showCheckboxes = $this->getConfig('view[showCheckboxes]', !$this->readOnly); - $config->recordUrl = $this->getConfig('view[recordUrl]', null); - $config->customViewPath = $this->getConfig('view[customViewPath]', null); + $config->recordUrl = $this->getConfig('view[recordUrl]'); + $config->customViewPath = $this->getConfig('view[customViewPath]'); + $config->noRecordsMessage = $this->getConfig('view[noRecordsMessage]'); $defaultOnClick = sprintf( "$.oc.relationBehavior.clickViewListRecord(':%s', '%s', '%s')", @@ -818,6 +819,7 @@ class RelationController extends ControllerBehavior $config->showSorting = $this->getConfig('manage[showSorting]', !$isPivot); $config->defaultSort = $this->getConfig('manage[defaultSort]'); $config->recordsPerPage = $this->getConfig('manage[recordsPerPage]'); + $config->noRecordsMessage = $this->getConfig('manage[noRecordsMessage]'); if ($this->viewMode == 'single') { $config->showCheckboxes = false; From 08de0c99299577384eceb27877b60af4adf447ba Mon Sep 17 00:00:00 2001 From: Gary Houbre Date: Sat, 10 Oct 2020 21:56:22 +0200 Subject: [PATCH 31/69] Fix links to the coding standards (#5307) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 16ad959d6..2e9d0c5ae 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,9 @@ Before sending or reviewing Pull Requests, be sure to review the [Contributing G Please follow the following guides and code standards: -* [PSR 4 Coding Standards](https://github.com/php-fig/fig-standards/blob/develop/accepted/PSR-4-autoloader.md) -* [PSR 2 Coding Style Guide](https://github.com/php-fig/fig-standards/blob/develop/accepted/PSR-2-coding-style-guide.md) -* [PSR 1 Coding Standards](https://github.com/php-fig/fig-standards/blob/develop/accepted/PSR-1-basic-coding-standard.md) +* [PSR 4 Autoloader](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md) +* [PSR 2 Coding Style Guide](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) +* [PSR 1 Coding Standards](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md) ### Code of Conduct From 11c93f0a3bf3bddc969847bbcc0da38132978f0d Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Sun, 11 Oct 2020 19:05:38 +1100 Subject: [PATCH 32/69] Fixes View::make recursion This logic is called via {% include %} (fixed) and as a custom .htm driver for View::make (broken). The previous change was too aggressive and broke the latter. This still fixes arbitrary file inclusion whilst retaining the original design. Both logic paths are now fixed and have been tested --- modules/system/twig/Loader.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/system/twig/Loader.php b/modules/system/twig/Loader.php index 28b52c834..c42756a0a 100644 --- a/modules/system/twig/Loader.php +++ b/modules/system/twig/Loader.php @@ -2,7 +2,6 @@ use App; use File; -use View; use Twig\Source as TwigSource; use Twig\Loader\LoaderInterface as TwigLoaderInterface; use Exception; @@ -15,6 +14,11 @@ use Exception; */ class Loader implements TwigLoaderInterface { + /** + * @var string Expected file extension + */ + protected $extension = 'htm'; + /** * @var array Cache */ @@ -33,13 +37,18 @@ class Loader implements TwigLoaderInterface return $this->cache[$name]; } + $view = $name; + if (File::extension($view) === $this->extension) { + $view = substr($view, 0, -strlen($this->extension)); + } + $path = $finder->find($name); return $this->cache[$name] = $path; } public function getSourceContext($name) { - return new TwigSource((string) View::make($name), $name); + return new TwigSource(File::get($this->findTemplate($name)), $name); } public function getCacheKey($name) From 20181b16de58a7a749b9dae7229792837235eccc Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Mon, 12 Oct 2020 13:35:33 -0400 Subject: [PATCH 33/69] Remove hidden CMS pages from menus (#5309) --- modules/cms/classes/Page.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/cms/classes/Page.php b/modules/cms/classes/Page.php index ad3b1d46d..873b51855 100644 --- a/modules/cms/classes/Page.php +++ b/modules/cms/classes/Page.php @@ -1,6 +1,7 @@ reference); + + // Remove hidden CMS pages from menus when backend user is logged out + if ($page && $page->is_hidden && !BackendAuth::getUser()) { + return; + } + $controller = Controller::getController() ?: new Controller; $pageUrl = $controller->pageUrl($item->reference, [], false); From df4c2cf86eac3f54484ba3a4dd84f05259c0b8a4 Mon Sep 17 00:00:00 2001 From: Ayumi <57409060+ayumi-cloud@users.noreply.github.com> Date: Mon, 12 Oct 2020 19:10:17 +0100 Subject: [PATCH 34/69] Default session.same_site to Lax (#5293) --- config/session.php | 28 +++++++++++++++++++++------- modules/system/ServiceProvider.php | 7 +++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/config/session.php b/config/session.php index ab762f221..8e2f8fa57 100644 --- a/config/session.php +++ b/config/session.php @@ -169,17 +169,31 @@ return [ |-------------------------------------------------------------------------- | | This option determines how your cookies behave when cross-site requests - | take place, and can be used to mitigate CSRF attacks. By default, we - | do not enable this as other CSRF protection services are in place. + | take place and can be used to mitigate CSRF attacks. | - | In the strict mode, the cookie is not sent with any cross-site usage - | even if the user follows a link to another website. Lax cookies are - | only sent with a top-level get request. + | Cookies that match the domain of the current site, i.e. what's displayed + | in the browser's address bar, are referred to as first-party cookies. + | Similarly, cookies from domains other than the current site are referred + | to as third-party cookies. | - | Supported: "lax", "strict" + | Cookies without a SameSite attribute will be treated as `SameSite=Lax`, + | meaning the default behaviour will be to restrict cookies to first party + | contexts only. + | + | Cookies for cross-site usage must specify `same_site` as 'None' and `secure` + | as `true` to work correctly. + | + | Lax - Cookies are allowed to be sent with top-level navigations and will + | be sent along with GET request initiated by third party website. + | This is the default value in modern browsers. + | + | Strict - Cookies will only be sent in a first-party context and not be + | sent along with requests initiated by third party websites. + | + | Supported: "Lax", "Strict" and "None" | */ - 'same_site' => null, + 'same_site' => 'Lax', ]; diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index 0a7b5fda7..e18477565 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -97,6 +97,13 @@ class ServiceProvider extends ModuleServiceProvider } } + /* + * Set a default samesite config value for invalid values + */ + if (!in_array(strtolower(Config::get('session.same_site')), ['lax', 'strict', 'none'])) { + Config::set('session.same_site', 'Lax'); + } + Paginator::useBootstrapThree(); Paginator::defaultSimpleView('system::pagination.simple-default'); From 8a9775428b90a85bcab26a9063f33e9f10147982 Mon Sep 17 00:00:00 2001 From: Salvatore Brosio <30407646+ArchimedeSolutions@users.noreply.github.com> Date: Mon, 12 Oct 2020 20:11:24 +0200 Subject: [PATCH 35/69] Improve Italian translation (#5310) --- modules/system/lang/it/validation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/lang/it/validation.php b/modules/system/lang/it/validation.php index 69af90970..52b885798 100644 --- a/modules/system/lang/it/validation.php +++ b/modules/system/lang/it/validation.php @@ -37,7 +37,7 @@ return [ "exists" => "Il valore di :attribute non è valido.", "image" => ":attribute deve essere un'immagine.", "in" => "Il valore di :attribute non è valido.", - "integer" => ":attribute deve essere un numero interno.", + "integer" => ":attribute deve essere un numero intero.", "ip" => ":attribute deve essere un indirizzo IP valido.", "max" => [ "numeric" => ":attribute non può essere maggiore di :max.", From 463cd57bc08ecabebe2557e2cda898e8aae2cf7a Mon Sep 17 00:00:00 2001 From: 4NIK3T Date: Tue, 13 Oct 2020 13:21:59 +0530 Subject: [PATCH 36/69] Add removeSideMenuItems function (#5285) Similar to addSideMenuItems, add a helper function to removeSideMenuItems --- modules/backend/classes/NavigationManager.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/modules/backend/classes/NavigationManager.php b/modules/backend/classes/NavigationManager.php index ee57c1666..6d54d9550 100644 --- a/modules/backend/classes/NavigationManager.php +++ b/modules/backend/classes/NavigationManager.php @@ -319,6 +319,21 @@ class NavigationManager $this->items[$itemKey]->addSideMenuItem($item); return true; } + + /** + * Remove multiple side menu items + * + * @param string $owner + * @param string $code + * @param array $sideCodes + * @return void + */ + public function removeSideMenuItems($owner, $code, $sideCodes) + { + foreach ($sideCodes as $sideCode) { + $this->removeSideMenuItem($owner, $code, $sideCode); + } + } /** * Removes a single main menu item From 3dc105173a8899b6266bc8befce0d289802a06a0 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Tue, 13 Oct 2020 19:14:31 +1100 Subject: [PATCH 37/69] Only allow local files via view engine The Laravel view engine wants to supply the Twig engine with an absolute path, even though this is outside the inclusion rules. This implements a temporary exception to wave it through. It seems like a suitable alternative instead of implementing a reverse lookup to ensure the path is a valid view file, since we can trust the source engine has passed the value through its resolver already Fixes previous fix --- modules/system/twig/Engine.php | 8 ++++++++ modules/system/twig/Loader.php | 9 ++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/system/twig/Engine.php b/modules/system/twig/Engine.php index affcab34e..6a3cbcae3 100644 --- a/modules/system/twig/Engine.php +++ b/modules/system/twig/Engine.php @@ -1,5 +1,6 @@ environment->loadTemplate($path); + + TwigLoader::$allowInclude = $previousAllow; + return $template->render($vars); } } diff --git a/modules/system/twig/Loader.php b/modules/system/twig/Loader.php index c42756a0a..baafc7773 100644 --- a/modules/system/twig/Loader.php +++ b/modules/system/twig/Loader.php @@ -15,9 +15,9 @@ use Exception; class Loader implements TwigLoaderInterface { /** - * @var string Expected file extension + * @var bool Allow any local file */ - protected $extension = 'htm'; + public static $allowInclude = false; /** * @var array Cache @@ -37,9 +37,8 @@ class Loader implements TwigLoaderInterface return $this->cache[$name]; } - $view = $name; - if (File::extension($view) === $this->extension) { - $view = substr($view, 0, -strlen($this->extension)); + if (static::$allowInclude === true && File::isFile($name)) { + return $this->cache[$name] = $name; } $path = $finder->find($name); From a9d54eaa97e4691f5df84ef56ddca6b83b801d9b Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Thu, 15 Oct 2020 09:48:46 -0600 Subject: [PATCH 38/69] Warn about unsupported cache drivers being used for image resizing --- modules/system/classes/SystemController.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/system/classes/SystemController.php b/modules/system/classes/SystemController.php index 2f7ac2418..1b36c7b49 100644 --- a/modules/system/classes/SystemController.php +++ b/modules/system/classes/SystemController.php @@ -1,6 +1,7 @@ default to a persistent cache driver.'); + } } catch (Exception $ex) { // If it failed for any other reason, restore the config so that // the resizer route will continue to work until it succeeds From cbcb3894b0f227c190f8cbb92c003d6b6bb660f4 Mon Sep 17 00:00:00 2001 From: Mehul <69337738+rootpipe@users.noreply.github.com> Date: Fri, 16 Oct 2020 12:31:57 +0530 Subject: [PATCH 39/69] Code tweak to server.php (#5317) --- server.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.php b/server.php index c42e5946c..b0f8cf081 100644 --- a/server.php +++ b/server.php @@ -11,7 +11,7 @@ $uri = urldecode( // This file allows us to emulate Apache's "mod_rewrite" functionality from the // built-in PHP web server. This provides a convenient way to test a Laravel // application without having installed a "real" web server software here. -if ($uri !== '/' and file_exists(__DIR__.'/'.$uri)) { +if ($uri !== '/' && file_exists(__DIR__.'/'.$uri)) { return false; } require_once __DIR__.'/index.php'; From aa9c209e76c6302a0d772277c7c99c948d72edfa Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Mon, 19 Oct 2020 13:08:37 -0400 Subject: [PATCH 40/69] Hide stripe load indicator when a redirect response is returned (#5321) Fixes #5055 --- modules/system/assets/js/framework-min.js | 3 ++- modules/system/assets/js/framework.combined-min.js | 3 ++- modules/system/assets/js/framework.js | 7 ++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/system/assets/js/framework-min.js b/modules/system/assets/js/framework-min.js index a75e43143..3d7146a4c 100644 --- a/modules/system/assets/js/framework-min.js +++ b/modules/system/assets/js/framework-min.js @@ -69,7 +69,8 @@ var fieldElement=$form.find('[name="'+fieldName+'"], [name="'+fieldName+'[]"], [ if(fieldElement.length>0){var _event=jQuery.Event('ajaxInvalidField') $(window).trigger(_event,[fieldElement.get(0),fieldName,fieldMessages,isFirstInvalidField]) if(isFirstInvalidField){if(!_event.isDefaultPrevented())fieldElement.focus() -isFirstInvalidField=false}}})},handleFlashMessage:function(message,type){},handleRedirectResponse:function(url){window.location.assign(url)},handleUpdateResponse:function(data,textStatus,jqXHR){var updatePromise=$.Deferred().done(function(){for(var partial in data){var selector=(options.update[partial])?options.update[partial]:partial +isFirstInvalidField=false}}})},handleFlashMessage:function(message,type){},handleRedirectResponse:function(url){window.location.assign(url) +$el.trigger('ajaxDone')},handleUpdateResponse:function(data,textStatus,jqXHR){var updatePromise=$.Deferred().done(function(){for(var partial in data){var selector=(options.update[partial])?options.update[partial]:partial if($.type(selector)=='string'&&selector.charAt(0)=='@'){$(selector.substring(1)).append(data[partial]).trigger('ajaxUpdate',[context,data,textStatus,jqXHR])} else if($.type(selector)=='string'&&selector.charAt(0)=='^'){$(selector.substring(1)).prepend(data[partial]).trigger('ajaxUpdate',[context,data,textStatus,jqXHR])} else{$(selector).trigger('ajaxBeforeReplace') diff --git a/modules/system/assets/js/framework.combined-min.js b/modules/system/assets/js/framework.combined-min.js index 382a869ec..4aad7c23b 100644 --- a/modules/system/assets/js/framework.combined-min.js +++ b/modules/system/assets/js/framework.combined-min.js @@ -69,7 +69,8 @@ var fieldElement=$form.find('[name="'+fieldName+'"], [name="'+fieldName+'[]"], [ if(fieldElement.length>0){var _event=jQuery.Event('ajaxInvalidField') $(window).trigger(_event,[fieldElement.get(0),fieldName,fieldMessages,isFirstInvalidField]) if(isFirstInvalidField){if(!_event.isDefaultPrevented())fieldElement.focus() -isFirstInvalidField=false}}})},handleFlashMessage:function(message,type){},handleRedirectResponse:function(url){window.location.assign(url)},handleUpdateResponse:function(data,textStatus,jqXHR){var updatePromise=$.Deferred().done(function(){for(var partial in data){var selector=(options.update[partial])?options.update[partial]:partial +isFirstInvalidField=false}}})},handleFlashMessage:function(message,type){},handleRedirectResponse:function(url){window.location.assign(url) +$el.trigger('ajaxDone')},handleUpdateResponse:function(data,textStatus,jqXHR){var updatePromise=$.Deferred().done(function(){for(var partial in data){var selector=(options.update[partial])?options.update[partial]:partial if($.type(selector)=='string'&&selector.charAt(0)=='@'){$(selector.substring(1)).append(data[partial]).trigger('ajaxUpdate',[context,data,textStatus,jqXHR])} else if($.type(selector)=='string'&&selector.charAt(0)=='^'){$(selector.substring(1)).prepend(data[partial]).trigger('ajaxUpdate',[context,data,textStatus,jqXHR])} else{$(selector).trigger('ajaxBeforeReplace') diff --git a/modules/system/assets/js/framework.js b/modules/system/assets/js/framework.js index c97a1554c..afcf8423e 100644 --- a/modules/system/assets/js/framework.js +++ b/modules/system/assets/js/framework.js @@ -288,6 +288,11 @@ if (window.jQuery.request !== undefined) { */ handleRedirectResponse: function(url) { window.location.assign(url) + // Indicate that the AJAX request is finished if we're still on the current page + // so that the loading indicator for redirects that cause a browser to download + // a file instead of leave the page will properly stop. + // @see https://github.com/octobercms/october/issues/5055 + $el.trigger('ajaxDone') }, /* @@ -974,4 +979,4 @@ if (window.jQuery.request !== undefined) { return sanitize(html) }; -}(window); \ No newline at end of file +}(window); From 51d1c163693d3db3cab38f640de0b4585e811fd2 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Tue, 20 Oct 2020 09:20:16 -0600 Subject: [PATCH 41/69] More tweaks to the default publisher permissions, added separate permission for users to manage their own personal editor preferences. --- modules/backend/ServiceProvider.php | 6 ++++++ modules/backend/controllers/Preferences.php | 2 +- modules/backend/lang/en/lang.php | 1 + modules/backend/models/preference/fields.yaml | 1 + modules/system/lang/en/lang.php | 3 ++- 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/backend/ServiceProvider.php b/modules/backend/ServiceProvider.php index f815fc93b..e21bda158 100644 --- a/modules/backend/ServiceProvider.php +++ b/modules/backend/ServiceProvider.php @@ -165,10 +165,16 @@ class ServiceProvider extends ModuleServiceProvider 'backend.manage_editor' => [ 'label' => 'system::lang.permissions.manage_editor', 'tab' => 'system::lang.permissions.name', + 'roles' => UserRole::CODE_DEVELOPER, + ], + 'backend.manage_own_editor' => [ + 'label' => 'system::lang.permissions.manage_own_editor', + 'tab' => 'system::lang.permissions.name', ], 'backend.manage_branding' => [ 'label' => 'system::lang.permissions.manage_branding', 'tab' => 'system::lang.permissions.name', + 'roles' => UserRole::CODE_DEVELOPER, ], 'media.manage_media' => [ 'label' => 'backend::lang.permissions.manage_media', diff --git a/modules/backend/controllers/Preferences.php b/modules/backend/controllers/Preferences.php index 9a79f88b5..1a9e25828 100644 --- a/modules/backend/controllers/Preferences.php +++ b/modules/backend/controllers/Preferences.php @@ -57,7 +57,7 @@ class Preferences extends Controller */ public function formExtendFields($form) { - if (!$this->user->hasAccess('backend.manage_editor')) { + if (!$this->user->hasAccess('backend.manage_own_editor')) { $form->removeTab('backend::lang.backend_preferences.code_editor'); } } diff --git a/modules/backend/lang/en/lang.php b/modules/backend/lang/en/lang.php index 230e744a4..aca0ab6cd 100644 --- a/modules/backend/lang/en/lang.php +++ b/modules/backend/lang/en/lang.php @@ -373,6 +373,7 @@ return [ 'editor' => [ 'menu_label' => 'Editor settings', 'menu_description' => 'Customize the global editor preferences, such as font size and color scheme.', + 'preview' => 'Preview', 'font_size' => 'Font size', 'tab_size' => 'Tab size', 'use_hard_tabs' => 'Indent using tabs', diff --git a/modules/backend/models/preference/fields.yaml b/modules/backend/models/preference/fields.yaml index dd4cedb4a..f8e0aad97 100644 --- a/modules/backend/models/preference/fields.yaml +++ b/modules/backend/models/preference/fields.yaml @@ -21,6 +21,7 @@ tabs: editor_preview: type: partial + label: backend::lang.editor.preview tab: backend::lang.backend_preferences.code_editor path: field_editor_preview diff --git a/modules/system/lang/en/lang.php b/modules/system/lang/en/lang.php index fa55cb114..136841bdd 100644 --- a/modules/system/lang/en/lang.php +++ b/modules/system/lang/en/lang.php @@ -441,7 +441,8 @@ return [ 'manage_other_administrators' => 'Manage other administrators', 'impersonate_users' => 'Impersonate users', 'manage_preferences' => 'Manage backend preferences', - 'manage_editor' => 'Manage code editor preferences', + 'manage_editor' => 'Manage global code editor preferences', + 'manage_own_editor' => 'Manage personal code editor preferences', 'view_the_dashboard' => 'View the dashboard', 'manage_default_dashboard' => 'Manage the default dashboard', 'manage_branding' => 'Customize the back-end', From baecc2c1e6d73ea25c12d626a76f6fb401474bef Mon Sep 17 00:00:00 2001 From: Igor Date: Tue, 20 Oct 2020 23:02:53 +0200 Subject: [PATCH 42/69] Fix codestyle (#5323) --- modules/backend/models/AccessLog.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/backend/models/AccessLog.php b/modules/backend/models/AccessLog.php index 2e22e0462..9ca8be6e8 100644 --- a/modules/backend/models/AccessLog.php +++ b/modules/backend/models/AccessLog.php @@ -48,8 +48,7 @@ class AccessLog extends Model $records = static::where('user_id', $user->id) ->orderBy('created_at', 'desc') ->limit(2) - ->get() - ; + ->get(); if (!count($records)) { return null; From a9a2c6e83b2d2455c62640075f5833ae953b7b02 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Tue, 20 Oct 2020 15:35:27 -0600 Subject: [PATCH 43/69] Improve handling of custom editor styles in the backend --- modules/backend/layouts/_custom_styles.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/backend/layouts/_custom_styles.htm b/modules/backend/layouts/_custom_styles.htm index 8eec0413b..cb4e3ad82 100644 --- a/modules/backend/layouts/_custom_styles.htm +++ b/modules/backend/layouts/_custom_styles.htm @@ -9,6 +9,6 @@ From bd83f34ac4f3fb9c67a7c01d6b668db6f3982f94 Mon Sep 17 00:00:00 2001 From: TimFoerster Date: Thu, 22 Oct 2020 17:02:22 +0200 Subject: [PATCH 44/69] Minor UX tweak (#5325) Change order to plugin, theme to match the install page. --- modules/system/controllers/updates/_list_toolbar.htm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/system/controllers/updates/_list_toolbar.htm b/modules/system/controllers/updates/_list_toolbar.htm index fc32bc329..0d8743072 100644 --- a/modules/system/controllers/updates/_list_toolbar.htm +++ b/modules/system/controllers/updates/_list_toolbar.htm @@ -6,16 +6,16 @@ data-handler="onLoadUpdates"> - - - + + + From 86496ff8187be05de4bcecec89ee269c91ca4269 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Thu, 22 Oct 2020 13:20:19 -0600 Subject: [PATCH 45/69] Support disks that can't be serialized in ImageResizer. (#5324) Requires https://github.com/octobercms/library/pull/532 --- modules/system/classes/ImageResizer.php | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/modules/system/classes/ImageResizer.php b/modules/system/classes/ImageResizer.php index 71ea874a4..5a843af2c 100644 --- a/modules/system/classes/ImageResizer.php +++ b/modules/system/classes/ImageResizer.php @@ -149,22 +149,22 @@ class ImageResizer 'themes' => [ 'disk' => 'system', 'folder' => config('cms.themesPathLocal', base_path('themes')), - 'path' => config('cms.themesPath', '/themes'), + 'path' => rtrim(config('cms.themesPath', '/themes'), '/'), ], 'plugins' => [ 'disk' => 'system', 'folder' => config('cms.pluginsPathLocal', base_path('plugins')), - 'path' => config('cms.pluginsPath', '/plugins'), + 'path' => rtrim(config('cms.pluginsPath', '/plugins'), '/'), ], 'resized' => [ 'disk' => config('cms.storage.resized.disk', 'local'), 'folder' => config('cms.storage.resized.folder', 'resized'), - 'path' => config('cms.storage.resized.path', '/storage/app/resized'), + 'path' => rtrim(config('cms.storage.resized.path', '/storage/app/resized'), '/'), ], 'media' => [ 'disk' => config('cms.storage.media.disk', 'local'), 'folder' => config('cms.storage.media.folder', 'media'), - 'path' => config('cms.storage.media.path', '/storage/app/media'), + 'path' => rtrim(config('cms.storage.media.path', '/storage/app/media'), '/'), ], 'modules' => [ 'disk' => 'system', @@ -174,7 +174,7 @@ class ImageResizer 'filemodel' => [ 'disk' => config('cms.storage.uploads.disk', 'local'), 'folder' => config('cms.storage.uploads.folder', 'uploads'), - 'path' => config('cms.storage.uploads.path', '/storage/app/uploads'), + 'path' => rtrim(config('cms.storage.uploads.path', '/storage/app/uploads'), '/'), ], ]; @@ -231,6 +231,14 @@ class ImageResizer } } + // Handle disks that can't be serialized by referencing them by their + // filesystems.php config name + try { + serialize($disk); + } catch (Exception $ex) { + $disk = Storage::identify($disk); + } + $config = [ 'image' => [ 'disk' => $disk, @@ -542,6 +550,11 @@ class ImageResizer $path = $image['path']; $selectedSource = $image['source']; + // Handle disks that couldn't be serialized + if (is_string($disk)) { + $disk = Storage::disk($disk); + } + // Verify that the source file exists if (empty(FileHelper::extension($path)) || !$disk->exists($path)) { $disk = null; From fbb470c7eee87cc5a5afb8cb6a83d476094df445 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Thu, 22 Oct 2020 16:14:14 -0600 Subject: [PATCH 46/69] UX improvements for managing plugins. Fixes #5127, also will force a page reload after changing the status of a plugin (remove, disable, enable, etc) so that the menu structure is accurately reflected. --- modules/system/controllers/Updates.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/system/controllers/Updates.php b/modules/system/controllers/Updates.php index 1d1be7923..230cd57c2 100644 --- a/modules/system/controllers/Updates.php +++ b/modules/system/controllers/Updates.php @@ -450,12 +450,15 @@ class Updates extends Controller /** * Contacts the update server for a list of necessary updates. + * + * @param bool $force Whether or not to force the redownload of existing tools + * @return string The rendered "execute" partial */ - public function onForceUpdate() + public function onForceUpdate($force = true) { try { $manager = UpdateManager::instance(); - $result = $manager->requestUpdateList(true); + $result = $manager->requestUpdateList($force); $coreHash = array_get($result, 'core.hash', false); $coreBuild = array_get($result, 'core.build', false); @@ -706,7 +709,7 @@ class Updates extends Controller 'system::project.owner' => $result['owner'], ]); - return $this->onForceUpdate(); + return $this->onForceUpdate(false); } catch (Exception $ex) { $this->handleError($ex); @@ -878,7 +881,7 @@ class Updates extends Controller } Flash::success(Lang::get("system::lang.plugins.{$bulkAction}_success")); - return $this->listRefresh('manage'); + return redirect()->refresh(); } // From 6fb3708440242d5761fbe76025f6a663d2260376 Mon Sep 17 00:00:00 2001 From: Bertware Date: Fri, 23 Oct 2020 22:14:59 +0200 Subject: [PATCH 47/69] Improve Dutch translation (#5328) Sources: https://www.vandale.nl/gratis-woordenboek/nederlands/betekenis/component http://de-of-het.nl/component/ --- modules/backend/lang/nl/lang.php | 8 ++++++- modules/cms/lang/nl/lang.php | 9 ++++---- modules/system/lang/nl/lang.php | 7 ++++-- modules/system/lang/nl/validation.php | 31 ++++++++++++++++++++++++++- 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/modules/backend/lang/nl/lang.php b/modules/backend/lang/nl/lang.php index d214a087c..4c8375b32 100644 --- a/modules/backend/lang/nl/lang.php +++ b/modules/backend/lang/nl/lang.php @@ -9,6 +9,7 @@ return [ 'invalid_type' => 'Ongeldig type veld: :type.', 'options_method_invalid_model' => "Het attribuut ':field' levert geen geldig model op. Probeer de opties methode expliciet te specifieren voor modelklasse :model.", 'options_method_not_exists' => 'De modelklasse :model moet de methode :method() definiëren met daarin opties voor het veld ":field".', + 'options_static_method_invalid_value' => "De statische methode ':method()' in :class leverde geen geldige array met opties op.", 'colors_method_not_exists' => 'De modelklasse :model moet de methode :method() definiëren met daarin html HEX kleurcodes voor het veld ":field".', ], 'widget' => [ @@ -372,6 +373,7 @@ return [ 'editor' => [ 'menu_label' => 'Editor instellingen', 'menu_description' => 'Beheer editor instellingen, zoals lettergrootte en kleurschema.', + 'preview' => 'Voorbeeldweergave', 'font_size' => 'Lettergrootte', 'tab_size' => 'Tab grootte', 'use_hard_tabs' => 'Inspringen met tabs', @@ -406,6 +408,7 @@ return [ 'label' => 'Label', 'class_name' => 'Class naam', 'markup_tags' => 'Opmaak HTML-tags', + 'markup_tag' => 'Opmaak HTML-tag', 'allowed_empty_tags' => 'Toegestane lege HTML-tags', 'allowed_empty_tags_comment' => 'Een lijst van HTML-tags die niet worden verwijderd als ze leeg zijn.', 'allowed_tags' => 'Toegestane HTML-tags', @@ -416,6 +419,7 @@ return [ 'remove_tags_comment' => 'Een lijst van HTML-tags die samen met hun inhoud worden verwijderd.', 'line_breaker_tags' => 'Line breaker tags', 'line_breaker_tags_comment' => 'Een lijst van HTML-tags waartussen een line breaker element wordt geplaatst.', + 'toolbar_options' => 'Toolbar opties', 'toolbar_buttons' => 'Toolbar knoppen', 'toolbar_buttons_comment' => 'De toolbar knoppen die standaard getoond worden door de Rich Editor.', 'toolbar_buttons_preset' => 'Voeg preset toe voor toolbar knoppen:', @@ -424,9 +428,11 @@ return [ 'minimal' => 'Minimaal', 'full' => 'Volledig', ], + 'paragraph_formats' => 'Paragraaf formaten', + 'paragraph_formats_comment' => 'De opties die in de "Paragraaf formaat" lijst zullen verschijnen.', ], 'tooltips' => [ - 'preview_website' => 'Voorvertoning website', + 'preview_website' => 'Voorbeeldweergave website', ], 'mysettings' => [ 'menu_label' => 'Mijn instellingen', diff --git a/modules/cms/lang/nl/lang.php b/modules/cms/lang/nl/lang.php index ab6ec9fa7..defd17170 100644 --- a/modules/cms/lang/nl/lang.php +++ b/modules/cms/lang/nl/lang.php @@ -11,7 +11,7 @@ return [ 'error_deleting' => 'Fout bij het verwijderen van template: ":name". Controleer de schrijfrechten.', 'delete_success' => 'Templates zijn succesvol verwijderd: :count.', 'file_name_required' => 'Het invullen van een bestandsnaam is verplicht.', - 'safe_mode_enabled' => 'Beveiligde modus is op dit moment ingeschakeld.', + 'safe_mode_enabled' => 'Veilige modus is op dit moment ingeschakeld.', ], 'dashboard' => [ 'active_theme' => [ @@ -245,11 +245,12 @@ return [ 'no_description' => 'Geen beschrijving opgegeven', 'alias' => 'Alias', 'alias_description' => 'Een unieke naam voor dit component voor gebruik in de code van een pagina of layout.', - 'validation_message' => 'Een alias voor het component is verplicht en mag alleen bestaan uit letters, cijfers en underscores. De alias moet beginnen met een letter.', + 'validation_message' => 'Een alias voor de component is verplicht en mag alleen bestaan uit letters, cijfers en underscores. De alias moet beginnen met een letter.', 'invalid_request' => 'De template kan niet worden opgeslagen vanwege ongeldige componentgegevens.', 'no_records' => 'Geen componenten gevonden', - 'not_found' => 'Het component \':name\' is niet gevonden.', - 'method_not_found' => 'Het component \':name\' bevat geen \':method\' methode.', + 'not_found' => 'De component \':name\' is niet gevonden.', + 'no_default_partial' => "De component bevat geen 'standaard' sjabloon", + 'method_not_found' => 'De component \':name\' bevat geen \':method\' methode.', 'soft_component' => 'Soft Component', 'soft_component_description' => 'Dit component ontbreekt, maar is optioneel.', ], diff --git a/modules/system/lang/nl/lang.php b/modules/system/lang/nl/lang.php index 5697e60d5..edd07010b 100644 --- a/modules/system/lang/nl/lang.php +++ b/modules/system/lang/nl/lang.php @@ -331,6 +331,8 @@ return [ 'core_downloading' => 'Bestanden aan het downloaden', 'core_extracting' => 'Bestanden aan het uitpakken', 'core_set_build' => 'Bouwnummer bijwerken', + 'update_warnings_title' => 'Enkele problemen zijn gedetecteerd en vereisen aandacht:', + 'update_warnings_plugin_missing' => 'De :parent_code plugin vereist dat :code geinstalleerd is, alvorens deze zal werken', 'changelog' => 'Logboek van wijzigingen', 'changelog_view_details' => 'Bekijk details', 'plugins' => 'Plugins', @@ -438,8 +440,9 @@ return [ 'manage_mail_settings' => 'Beheer e-mailinstellingen', 'manage_other_administrators' => 'Beheer mede-beheerders', 'impersonate_users' => 'Inloggen als', - 'manage_preferences' => 'Beheer back-end instellingen', - 'manage_editor' => 'Beheer code editor instellingen', + 'manage_preferences' => 'Beheer back-end voorkeuren', + 'manage_editor' => 'Beheer code editor voorkeuren', + 'manage_own_editor' => 'Beheer persoonlijk code editor voorkeuren', 'view_the_dashboard' => 'Toon dashboard', 'manage_default_dashboard' => 'Beheer het standaard dashboard', 'manage_branding' => 'Back-end aanpassen', diff --git a/modules/system/lang/nl/validation.php b/modules/system/lang/nl/validation.php index 38bc6c060..26540f0e7 100644 --- a/modules/system/lang/nl/validation.php +++ b/modules/system/lang/nl/validation.php @@ -32,6 +32,7 @@ return [ 'boolean' => ':attribute moet ja of nee zijn.', 'confirmed' => ':attribute bevestiging komt niet overeen.', 'date' => ':attribute moet een datum bevatten.', + 'date_equals' => ':attribute moet een datum zijn gelijk aan :date.', 'date_format' => ':attribute moet een geldig datum formaat bevatten.', 'different' => ':attribute en :other moeten verschillend zijn.', 'digits' => ':attribute moet bestaan uit :digits cijfers.', @@ -39,9 +40,22 @@ return [ 'dimensions' => ':attribute heeft geen geldige afmetingen voor afbeeldingen.', 'distinct' => ':attribute heeft een dubbele waarde.', 'email' => ':attribute is geen geldig e-mailadres.', + 'ends_with' => ':attribute moet eindigen op een van de volgende waarden: :values.', 'exists' => ':attribute bestaat niet.', 'file' => ':attribute moet een bestand zijn.', 'filled' => ':attribute is verplicht.', + 'gt' => [ + 'numeric' => ':attribute moet groter zijn dan :value.', + 'file' => ':attribute moet groter zijn dan :value kilobyte.', + 'string' => ':attribute moet langer zijn dan :value karakters.', + 'array' => ':attribute moet meer dan :value items bevatten.', + ], + 'gte' => [ + 'numeric' => ':attribute moet groter of gelijk zijn aan :value.', + 'file' => ':attribute moet minstens :value kilobyte groot zijn.', + 'string' => ':attribute moet minstens :value karakters lang zijn.', + 'array' => ':attribute moet minstens :value items bevatten.', + ], 'image' => ':attribute moet een afbeelding zijn.', 'in' => ':attribute is ongeldig.', 'in_array' => ':attribute bestaat niet in :other.', @@ -50,6 +64,18 @@ return [ 'ipv4' => ':attribute moet een geldig IPv4-adres zijn.', 'ipv6' => ':attribute moet een geldig IPv6-adres zijn.', 'json' => ':attribute moet een geldige JSON-string zijn.', + 'lt' => [ + 'numeric' => ':attribute moet kleiner zijn dan :value.', + 'file' => ':attribute moet kleiner zijn dan :value kilobyte.', + 'string' => ':attribute moet korter zijn dan :value karakters.', + 'array' => ':attribute moet minder dan :value items bevatten.', + ], + 'lte' => [ + 'numeric' => ':attribute moet kleiner of gelijk zijn aan :value.', + 'file' => ':attribute mag hoogstens :value kilobyte groot zijn.', + 'string' => ':attribute mag hoogstens :value karakters lang zijn.', + 'array' => ':attribute mag hoogstens :value items bevatten.', + ], 'max' => [ 'numeric' => ':attribute mag niet hoger dan :max zijn.', 'file' => ':attribute mag niet meer dan :max kilobytes zijn.', @@ -64,7 +90,8 @@ return [ 'string' => ':attribute moet minimaal :min karakters zijn.', 'array' => ':attribute moet minimaal :min items bevatten.', ], - 'not_in' => 'Het formaat van :attribute is ongeldig.', + 'not_in' => 'De gekozen waarde van :attribute is ongeldig.', + 'not_regex' => 'Het formaat van :attribute is ongeldig.', 'numeric' => ':attribute moet een nummer zijn.', 'present' => ':attribute moet bestaan.', 'regex' => ':attribute formaat is ongeldig.', @@ -82,11 +109,13 @@ return [ 'string' => ':attribute moet :size karakters zijn.', 'array' => ':attribute moet :size items bevatten.', ], + 'starts_with' => ':attribute moet beginnen met een van de volgende waarden: :values.', 'string' => ':attribute moet een tekenreeks zijn.', 'timezone' => ':attribute moet een geldige tijdzone zijn.', 'unique' => ':attribute is al in gebruik.', 'uploaded' => 'Het uploaden van :attribute is mislukt.', 'url' => ':attribute is geen geldige URL.', + 'uuid' => ':attribute moet een geldig UUID zijn.', /* |-------------------------------------------------------------------------- From e1ba909b65af19eadde517fc078a96e0ce871d61 Mon Sep 17 00:00:00 2001 From: Senuros Date: Sat, 24 Oct 2020 07:05:40 +0200 Subject: [PATCH 48/69] Improve German translation (#5329) --- modules/backend/lang/de/lang.php | 65 +++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/modules/backend/lang/de/lang.php b/modules/backend/lang/de/lang.php index 5118bb7f5..e765f39f6 100644 --- a/modules/backend/lang/de/lang.php +++ b/modules/backend/lang/de/lang.php @@ -2,12 +2,15 @@ return [ 'auth' => [ - 'title' => 'Admin-Bereich' + 'title' => 'Admin-Bereich', + 'invalid_login' => 'Die Angaben stimmen nicht mit unseren Aufzeichnungen überein. Überprüfen Sie diese und versuchen Sie es noch einmal.', ], 'field' => [ 'invalid_type' => 'Ungültiger Feldtyp :type.', 'options_method_invalid_model' => 'Das Attribut ":field" löst sich nicht zu einen gültigen Model auf. Probiere die options Methode der Model-Klasse :model explicit zu definieren.', - 'options_method_not_exists' => 'Die Model-Klasse :model muss eine Methode :method() mit Rückgabe der Werte von ":field" besitzen.', + 'options_method_not_exists' => 'Die Modell-Klasse :model muss eine Methode :method() mit Rückgabe der Werte von ":field" besitzen.', + 'options_static_method_invalid_value' => "Die statische Methode ':method()' in der Klasse :class hat kein valides Optionsarray zurückgegeben.", + 'colors_method_not_exists' => "Die Modellklasse :model muss eine Methode :method() definieren, welche html color (HEX) codes für das ':field' Formularfeld zurückgibt.", ], 'widget' => [ 'not_registered' => "Ein Widget namens ':name' wurde nicht registriert", @@ -15,6 +18,11 @@ return [ ], 'page' => [ 'untitled' => "Unbenannt", + '404' => [ + 'label' => 'Seite nicht gefunden', + 'help' => "Die von Ihnen angeforderte Seite konnte nicht gefunden werden.", + 'back_link' => 'Zurück zur vorigen Seite', + ], 'access_denied' => [ 'label' => "Zugriff verweigert", 'help' => "Sie haben nicht die erforderlichen Berechtigungen, um diese Seite zu sehen.", @@ -28,14 +36,23 @@ return [ ], 'partial' => [ 'not_found_name' => "Das Partial ':name' wurde nicht gefunden.", + 'invalid_name' => 'Ungültiger Partial: :name.', + ], + 'ajax_handler' => [ + 'invalid_name' => 'Ungültiger AJAX handler: :name.', + 'not_found' => "AJAX handler ':name' wurde nicht gefunden.", ], 'account' => [ + 'impersonate_confirm' => 'Sind Sie sicher, dass Sie sich als dieser Benutzer anmelden wollen? Sie können zu Ihrem ursprünglichen Zustand zurückkehren, indem Sie sich abmelden.', + 'impersonate_success' => 'Sie sind jetzt als dieser Benutzer angemeldet', + 'signed_in_as' => 'Angemeldet als :full_name', 'sign_out' => 'Abmelden', 'login' => 'Anmelden', 'reset' => 'Zurücksetzen', 'restore' => 'Wiederherstellen', 'login_placeholder' => 'Benutzername', 'password_placeholder' => 'Passwort', + 'remember_me' => 'Angemeldet bleiben', 'forgot_password' => "Passwort vergessen?", 'enter_email' => "Bitte E-Mail-Adresse eingeben", 'enter_login' => "Bitte Benutzernamen eingeben", @@ -112,6 +129,8 @@ return [ 'last_name' => 'Nachname', 'full_name' => 'Kompletter Name', 'email' => 'E-Mail', + 'role_field' => 'Rolle', + 'role_comment' => 'Rollen definieren Benutzerberechtigungen, die auf Benutzerebene auf der Registerkarte Berechtigungen überschrieben werden können.', 'groups' => 'Gruppen', 'groups_comment' => 'Geben Sie hier die Gruppenzugehörigkeit an', 'avatar' => 'Avatar', @@ -148,9 +167,25 @@ return [ 'return' => 'Zurück zur Gruppen-Übersicht', 'users_count' => 'Benutzer', ], + 'role' => [ + 'name' => 'Rolle', + 'name_field' => 'Name', + 'name_comment' => 'Der Name wird in der Rollenliste auf dem Administratorformular angezeigt.', + 'description_field' => 'Beschreibung', + 'code_field' => 'Code', + 'code_comment' => 'Geben Sie einen eindeutigen Code an, wenn Sie mit der API auf das Rollenobjekt zugreifen möchten.', + 'menu_label' => 'Rollen verwalten', + 'list_title' => 'Rollen verwalten', + 'new' => 'Neue Rolle', + 'delete_confirm' => 'Diese Administratorrolle löschen?', + 'return' => 'Zurück zur Rollenliste', + 'users_count' => 'Benutzer', + ], 'preferences' => [ 'not_authenticated' => 'Zum Speichern oder Anzeigen dieser Einstellungen liegt kein Nutzerkonto vor' - ] + ], + 'trashed_hint_title' => 'Dieses Konto wurde gelöscht.', + 'trashed_hint_desc' => 'Dieses Konto wurde gelöscht und kann nicht mehr angemeldet werden. Um es wiederherzustellen, klicken Sie auf das Symbol "Benutzer wiederherstellen" unten rechts', ], 'list' => [ 'default_title' => 'Auflisten', @@ -195,6 +230,11 @@ return [ 'remove_confirm' => 'Sind Sie sicher?', 'remove_file' => 'Datei entfernen', ], + 'repeater' => [ + 'add_new_item' => 'Neues Element hinzufügen', + 'min_items_failed' => ':name erfordert ein Minimum an :min Elementen, aber es wurden nur :items bereitgestellt', + 'max_items_failed' => ':name lässt nur bis zu :max Elemente zu, :items wurden bereitgestellt', + ], 'form' => [ 'create_title' => "Neuer :name", 'update_title' => "Bearbeite :name", @@ -312,6 +352,8 @@ return [ 'permissions' => 'Verzeichnis :name oder ein Unterverzeichnis kann nicht von PHP beschrieben werden. Bitte setzen Sie die korrekten Rechte für den Webserver in diesem Verzeichnis.', 'extension' => 'Die PHP Erweiterung :name ist nicht installiert. Bitte installieren Sie diese Library und aktivieren Sie die Erweiterung.', 'plugin_missing' => 'Das Plugin :name hat eine Abhängigkeit die nicht installiert ist. Bitte installieren Sie alle benötigten Plugins.', + 'debug' => 'Der Debug-Modus ist aktiviert. Dies wird für Produktionsinstallationen nicht empfohlen.', + 'decompileBackendAssets' => 'Assets im Backend sind derzeit dekompiliert. Dies wird für Produktionsinstallationen nicht empfohlen.', ], 'editor' => [ 'menu_label' => 'Editor Einstellungen', @@ -367,6 +409,8 @@ return [ 'minimal' => 'Minimal', 'full' => 'Vollständig', ], + 'paragraph_formats' => 'Absatzformatierungen', + 'paragraph_formats_comment' => 'Die Optionen, welche in der Dropdown-Liste für Absatzformatierungen angezeigt werden.', ], 'tooltips' => [ 'preview_website' => 'Vorschau der Webseite' @@ -386,6 +430,8 @@ return [ 'brand' => 'Brand', 'logo' => 'Logo', 'logo_description' => 'Lade ein eigenes Logo hoch, das im Backend verwendet werden soll.', + 'favicon' => 'Favicon', + 'favicon_description' => 'Laden Sie ein benutzerdefiniertes Favicon zur Verwendung im Back-End hoch', 'app_name' => 'App-Name', 'app_name_description' => 'Dieser Name wird als Titel des Backends angezeigt.', 'app_tagline' => 'App-Tagline', @@ -399,6 +445,7 @@ return [ 'navigation' => 'Navigation', 'menu_mode' => 'Menustyles', 'menu_mode_inline' => 'Inline', + 'menu_mode_inline_no_icons' => 'Inline (ohne Icons)', 'menu_mode_tile' => 'Tiles', 'menu_mode_collapsed' => 'Collapsed' ], @@ -503,6 +550,7 @@ return [ ], 'permissions' => [ 'manage_media' => 'Medien verwalten', + 'allow_unsafe_markdown' => 'Unsicheres Markdown verwenden (kann Javascript enthalten)', ], 'mediafinder' => [ 'label' => 'Media Finder', @@ -534,7 +582,12 @@ return [ 'multiple_selected' => 'Mehrere Dateien ausgewählt.', 'uploading_file_num' => 'Lade :number Datei(en)...', 'uploading_complete' => 'Upload vollständig', + 'uploading_error' => 'Upload fehlgeschlagen', + 'type_blocked' => 'Der verwendete Dateityp ist aus Sicherheitsgründen gesperrt.', 'order_by' => 'Sortieren nach', + 'direction' => 'Direction', + 'direction_asc' => 'Aufsteigend', + 'direction_desc' => 'Absteigend', 'folder' => 'Ordner', 'no_files_found' => 'Keine entsprechenden Dateien gefunden.', 'delete_empty' => 'Bitte Wählen Sie Dateien zum Löschen aus.', @@ -557,11 +610,11 @@ return [ 'restore' => 'Alle Änderungen rückgängig machen', 'resize' => 'Größe anpassen...', 'selection_mode_normal' => 'Normal', - 'selection_mode_fixed_ratio' => 'Fixes Verhältnis', - 'selection_mode_fixed_size' => 'Fixe Größe', + 'selection_mode_fixed_ratio' => 'Festes Verhältnis', + 'selection_mode_fixed_size' => 'Feste Größe', 'height' => 'Höhe', 'width' => 'Breite', - 'selection_mode' => 'Selection mode', + 'selection_mode' => 'Auswahlmodus', 'resize_image' => 'Bildgröße anpassen', 'image_size' => 'Dimensionen:', 'selected_size' => 'Ausgewählt:' From 34e9bea7cb4ab680059067021efe15f5705e165a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 25 Oct 2020 09:50:13 +0800 Subject: [PATCH 49/69] Force Composer v1 to be installed for tests Until such a time that `wikimedia/composer-merge-plugin` supports Composer 2. Refs: https://github.com/octobercms/october/pull/5330#issuecomment-715988820 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9f0ab376b..3cba038ea 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,7 +62,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.phpVersion }} - tools: composer + tools: composer:1.10.16 extensions: ${{ env.extensions }} - name: Setup dependency cache From ccd75e3e9c7c01089d710047bfc41ba0a2a4b6a9 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 25 Oct 2020 09:52:34 +0800 Subject: [PATCH 50/69] Fix Composer constraint in tests, test 1.0, 1.1 and develop branches --- .github/workflows/tests.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3cba038ea..348758a81 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,7 +3,8 @@ name: Tests on: push: branches: - - master + - 1.0 + - 1.1 - develop pull_request: @@ -62,7 +63,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.phpVersion }} - tools: composer:1.10.16 + tools: composer:v1 extensions: ${{ env.extensions }} - name: Setup dependency cache From 38a21365db1e12c207c83bcd1841e154d651c4b0 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 25 Oct 2020 09:54:41 +0800 Subject: [PATCH 51/69] Change branches to test code quality on push --- .github/workflows/code-quality-push.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code-quality-push.yaml b/.github/workflows/code-quality-push.yaml index f34f7b3b8..8c036b312 100644 --- a/.github/workflows/code-quality-push.yaml +++ b/.github/workflows/code-quality-push.yaml @@ -3,7 +3,8 @@ name: Code Quality on: push: branches: - - master + - 1.0 + - 1.1 - develop jobs: From 5dfe1ff74857f77c8cd3a9228d0ecbcc5a267e43 Mon Sep 17 00:00:00 2001 From: Senuros Date: Mon, 26 Oct 2020 03:10:08 +0100 Subject: [PATCH 52/69] Improve German translation (#5331) --- modules/cms/lang/de/lang.php | 86 +++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/modules/cms/lang/de/lang.php b/modules/cms/lang/de/lang.php index 3c52cfd83..e8f5a35fe 100644 --- a/modules/cms/lang/de/lang.php +++ b/modules/cms/lang/de/lang.php @@ -10,12 +10,24 @@ return [ 'invalid_file_extension'=>'Ungültige Dateiendung: :invalid. Erlaubt sind: :allowed.', 'error_deleting' => 'Fehler beim Löschen der Template-Datei ":name".', 'delete_success' => 'Templates wurden erfolgreich gelöscht: :count.', - 'file_name_required' => 'Ein Dateiname ist erforderlich.' + 'file_name_required' => 'Ein Dateiname ist erforderlich.', + 'safe_mode_enabled' => 'Der abgesicherte Modus ist derzeit aktiviert. Die Bearbeitung des PHP-Codes von CMS-Templates ist deaktiviert. Um den abgesicherten Modus zu deaktivieren, setzen Sie den Konfigurationswert `cms.enableSafeMode` auf `false`.', + ], + 'dashboard' => [ + 'active_theme' => [ + 'widget_title_default' => 'Website', + 'online' => 'Online', + 'maintenance' => 'In der Wartung', + 'manage_themes' => 'Themes Verwalten', + 'customize_theme' => 'Theme anpassen', + ], ], 'theme' => [ 'not_found_name' => "Das Theme ':name' konnte nicht gefunden werden.", + 'by_author' => 'Von :name', 'active' => [ 'not_set' => "Aktives Theme nicht definiert", + 'not_found' => 'Aktives Theme wurde nicht gefunden.', ], 'edit' => [ 'not_set' => "Edit Theme nicht definiert", @@ -35,6 +47,8 @@ return [ 'homepage_placeholder' => 'Webseiten URL', 'code_label' => 'Code', 'code_placeholder' => 'Ein einzigartiger Code genutzt bei der Weiterverbreitung dieses Themes', + 'preview_image_label' => 'Bildvorschau', + 'preview_image_placeholder' => 'Der Pfad des Theme-Vorschaubildes.', 'dir_name_label' => 'Verzeichnisname', 'dir_name_create_label' => 'Name des Zielverzeichnisses', 'theme_label' => 'Theme', @@ -84,7 +98,8 @@ return [ 'settings_menu' => 'Wartungsmodus', 'settings_menu_description' => 'Konfigurieren Sie den Wartungsmodus.', 'is_enabled' => 'Wartungsmodus aktivieren', - 'is_enabled_comment' => 'Sobald aktiviert, werden Besucher die unten ausgewählte Seite sehen.' + 'is_enabled_comment' => 'Sobald aktiviert, werden Besucher die unten ausgewählte Seite sehen.', + 'hint' => 'Im Wartungsmodus wird die Wartungsseite für Besucher angezeigt, die nicht im Back-End-Bereich angemeldet sind.', ], 'page' => [ 'not_found_name' => "Die Seite ':name' konnte nicht gefunden werden", @@ -97,16 +112,22 @@ return [ 'help' => "Entschuldigung, ein Fehler trat auf, sodass die gewünschte Seite nicht angezeigt werden kann.", ], 'menu_label' => 'Seiten', + 'unsaved_label' => 'Ungespeicherte Seite(n)', 'no_list_records' => 'Keine Seiten gefunden', 'new' => 'Neue Seite', 'invalid_url' => 'Ungültiges URL-Format. Die URL muss mit einem Slash beginnen und darf nur Ziffern, lateinische Zeichen und die folgenden Symbole beinhalten: ._-[]:?|/+*^$', 'delete_confirm_multiple' => 'Wollen Sie die ausgewählten Seiten wirklich löschen?', 'delete_confirm_single' => 'Wollen Sie diese Seite wirklich löschen?', - 'no_layout' => '-- Kein Layout --' + 'no_layout' => '-- Kein Layout --', + 'cms_page' => 'CMS Seite', + 'title' => 'Seitentitel', + 'url' => 'Seiten-URL', + 'file_name' => 'Seiten-Dateiname', ], 'layout' => [ 'not_found_name' => "Das Layout ':name' wurde nicht gefunden", 'menu_label' => 'Layouts', + 'unsaved_label' => 'Ungespeicherte(s) Layout(s)', 'no_list_records' => 'Keine Layouts gefunden', 'new' => 'Neues Layout', 'delete_confirm_multiple' => 'Wollen Sie die ausgewählten Layouts wirklich löschen?', @@ -116,6 +137,7 @@ return [ 'not_found_name' => "Das Partial ':name' wurde nicht gefunden.", 'invalid_name' => "Ungültiger Partial-Name: :name.", 'menu_label' => 'Partials', + 'unsaved_label' => 'Ungespeicherte(s) Partial(s)', 'no_list_records' => 'Keine Partials gefunden', 'delete_confirm_multiple' => 'Wollen Sie die ausgewählten Partials wirklich löschen?', 'delete_confirm_single' => 'Wollen Sie das ausgewählte Partial wirklich löschen?', @@ -124,6 +146,7 @@ return [ 'content' => [ 'not_found_name' => "Die Inhaltsdatei ':name' wurde nicht gefunden.", 'menu_label' => 'Inhalt', + 'unsaved_label' => 'Ungespeicherter Inhalt', 'no_list_records' => 'Keine Inhaltsdateien gefunden', 'delete_confirm_multiple' => 'Wollen Sie die ausgewählten Inhalte und Verzeichnisse wirklich löschen?', 'delete_confirm_single' => 'Wollen Sie diese Inhaltsdatei wirklich löschen?', @@ -158,18 +181,34 @@ return [ 'hidden' => 'Versteckt', 'hidden_comment' => 'Versteckte Seiten können nur von eingeloggten Backend-Benutzern genutzt werden.', 'enter_fullscreen' => 'In den Vollbildmodus wechseln', - 'exit_fullscreen' => 'Vollbildmodus beenden' + 'exit_fullscreen' => 'Vollbildmodus beenden', + 'open_searchbox' => 'Suchfeld öffnen', + 'close_searchbox' => 'Suchfeld schließen', + 'open_replacebox' => 'Ersetzen-Feld öffnen', + 'close_replacebox' => 'Ersetzen-Feld schließen', + 'commit' => 'Commit', + 'reset' => 'Zurücksetzen', + 'commit_confirm' => 'Sind Sie sicher, dass Sie Ihre Änderungen an dieser Datei auf das Dateisystem übertragen wollen? Dies wird die bestehende Datei auf dem Dateisystem überschreiben', + 'reset_confirm' => 'Sind Sie sicher, dass Sie diese Datei auf die Kopie zurücksetzen wollen, die sich auf dem Dateisystem befindet? Dadurch wird sie vollständig durch die Datei ersetzt, die sich auf dem Dateisystem befindet.', + 'committing' => 'Committing', + 'resetting' => 'Zurücksetzen', + 'commit_success' => 'Der :type wurde auf das Dateisystem übertragen', + 'reset_success' => 'Der :type wurde auf die Dateisystemversion zurückgesetzt', ], 'asset' => [ 'menu_label' => "Assets", + 'unsaved_label' => 'Ungespeicherte(s) Asset(s)', 'drop_down_add_title' => 'Hinzufügen...', 'drop_down_operation_title' => 'Aktion...', 'upload_files' => 'Datei(en) hochladen', 'create_file' => 'Datei erstellen', 'create_directory' => 'Verzeichnis erstellen', + 'directory_popup_title' => 'Neues Verzeichnis', + 'directory_name' => 'Verzeichnisname', 'rename' => 'Umbenennen', 'delete' => 'Löschen', 'move' => 'Verschieben', + 'select' => 'Auswählen', 'new' => 'Neue Datei', 'rename_popup_title' => 'Umbenennen', 'rename_new_name' => 'Neuer Name', @@ -196,6 +235,8 @@ return [ 'error_moving_file' => 'Fehler beim Verschieben der Datei :file', 'error_moving_directory' => 'Fehler beim Verschieben des Verzeichnisses :dir', 'error_deleting_directory' => 'Fehler beim Löschen des Originalverzeichnisses :dir', + 'no_list_records' => 'Keine Dateien gefunden', + 'delete_confirm' => 'Ausgewählte Dateien oder Verzeichnisse löschen?', 'path' => 'Pfad' ], 'component' => [ @@ -208,12 +249,17 @@ return [ 'invalid_request' => "Aufgrund ungültiger Komponentendaten kann das Template nicht gespeichert werden.", 'no_records' => 'Keine Komponenten gefunden', 'not_found' => "Die Komponente ':name' wurde nicht gefunden.", + 'no_default_partial' => "Diese Komponente hat keinen 'default' Partial", 'method_not_found' => "Die Komponente ':name' enthält keine Methode mit Namen ':method'.", + 'soft_component_description' => 'Diese Komponente fehlt, aber Sie ist optional.', ], 'template' => [ 'invalid_type' => "Unbekannter Template-Typ.", 'not_found' => "Das angeforderte Template wurde nicht gefunden.", - 'saved'=> "Das Template wurde erfolgreich gespeichert." + 'saved'=> "Das Template wurde erfolgreich gespeichert.", + 'no_list_records' => 'Keine Einträge gefunden', + 'delete_confirm' => 'Ausgewählte Templates löschen?', + 'order_by' => 'Sortieren nach', ], 'permissions' => [ 'name' => 'Cms', @@ -223,5 +269,35 @@ return [ 'manage_layouts' => 'Layouts verwalten', 'manage_partials' => 'Partials verwalten', 'manage_themes' => 'Themes verwalten', + 'manage_theme_options' => 'Konfigurieren Sie Anpassungsoptionen für das aktive Theme', + ], + 'theme_log' => [ + 'hint' => 'Dieses Protokoll zeigt alle Änderungen an, die von Administratoren im Back-End-Bereich am Theme vorgenommen wurden.', + 'menu_label' => 'Theme Protokoll', + 'menu_description' => 'Zeigen Sie die am aktiven Theme vorgenommenen Änderungen an.', + 'empty_link' => 'Leeres Theme Protokoll', + 'empty_loading' => 'Leeren des Theme Protokolls...', + 'empty_success' => 'Theme Protokoll', + 'return_link' => 'Zurück zum Theme Protokoll', + 'id' => 'ID', + 'id_label' => 'Protokoll ID', + 'created_at' => 'Datum & Zeit', + 'user' => 'Benutzer', + 'type' => 'Typ', + 'type_create' => 'Erstellen', + 'type_update' => 'Aktualisieren', + 'type_delete' => 'Löschen', + 'theme_name' => 'Theme', + 'theme_code' => 'Theme Code', + 'old_template' => 'Template (Alt)', + 'new_template' => 'Template (Neu)', + 'template' => 'Template', + 'diff' => 'Änderungen', + 'old_value' => 'Alter Wert', + 'new_value' => 'Neuer Wert', + 'preview_title' => 'Templateänderungen', + 'template_updated' => 'Template wurde aktualisiert', + 'template_created' => 'Template wurde erstellt', + 'template_deleted' => 'Template wurde gelöscht', ], ]; From 866af34b1ac04351752fe801e1bad6ce5cf77206 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 26 Oct 2020 15:32:13 +0800 Subject: [PATCH 53/69] Use number literals for booleans in filter conditions. SQL Server treats literal "false" and "true" as column names. This is probably the same for any other database type that does not have a true "boolean" storage. Refs: https://github.com/rainlab/blog-plugin/pull/526#issuecomment-716299459 --- modules/backend/controllers/users/config_filter.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/backend/controllers/users/config_filter.yaml b/modules/backend/controllers/users/config_filter.yaml index 08ebb3f56..d9660cc48 100644 --- a/modules/backend/controllers/users/config_filter.yaml +++ b/modules/backend/controllers/users/config_filter.yaml @@ -8,8 +8,8 @@ scopes: label: backend::lang.user.superuser type: switch conditions: - - is_superuser = false - - is_superuser = true + - is_superuser = 0 + - is_superuser = 1 login_date: label: backend::lang.user.last_login From df91c529a1419288d59ba4b9f1ae03effefcfc52 Mon Sep 17 00:00:00 2001 From: Salvatore Brosio <30407646+ArchimedeSolutions@users.noreply.github.com> Date: Thu, 29 Oct 2020 18:25:19 +0100 Subject: [PATCH 54/69] Improved Italian translations (#5338) Co-authored-by: salvatore brosio --- modules/backend/lang/it/lang.php | 2 +- modules/system/lang/it/client.php | 30 ++++++++++++++++++++---------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/modules/backend/lang/it/lang.php b/modules/backend/lang/it/lang.php index 022bdbb33..e0dab8b92 100644 --- a/modules/backend/lang/it/lang.php +++ b/modules/backend/lang/it/lang.php @@ -228,7 +228,7 @@ return [ 'preview_no_record_message' => 'Nessun record selezionato.', 'select' => 'Seleziona', 'select_all' => 'seleziona tutto', - 'select_none' => 'non selezionare niente', + 'select_none' => 'deseleziona tutto', 'select_placeholder' => 'seleziona', 'insert_row' => 'Inserisci riga', 'insert_row_below' => 'Inserisci riga sotto', diff --git a/modules/system/lang/it/client.php b/modules/system/lang/it/client.php index 0c13c7181..0eea9a73b 100644 --- a/modules/system/lang/it/client.php +++ b/modules/system/lang/it/client.php @@ -33,7 +33,6 @@ return [ 'fullscreen' => 'Schermo intero', 'preview' => 'Anteprima', ], - 'mediamanager' => [ 'insert_link' => "Inserisci collegamento elemento multimediale", 'insert_image' => "Inserisci immagine", @@ -45,23 +44,28 @@ return [ 'invalid_video_empty_insert' => "Si prega di selezionare un file video da inserire.", 'invalid_audio_empty_insert' => "Si prega di selezionare un file audio da inserire.", ], - 'alert' => [ 'confirm_button_text' => 'OK', 'cancel_button_text' => 'Annulla', + 'widget_remove_confirm' => 'Rimuovere questo widget?', ], - 'datepicker' => [ 'previousMonth' => 'Mese precedente', 'nextMonth' => 'Mese successivo', 'months' => ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'], 'weekdays' => ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'], - 'weekdaysShort' => ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'] + 'weekdaysShort' => ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'], + ], + 'colorpicker' => [ + 'choose' => 'OK', ], - 'filter' => [ 'group' => [ - 'all' => 'tutti' + 'all' => 'tutti', + ], + 'scopes' => [ + 'apply_button_text' => 'Applica', + 'clear_button_text' => 'Rimuovi', ], 'dates' => [ 'all' => 'tutte', @@ -69,10 +73,16 @@ return [ 'reset_button_text' => 'Reimposta', 'date_placeholder' => 'Data', 'after_placeholder' => 'Dopo', - 'before_placeholder' => 'Prima' - ] + 'before_placeholder' => 'Prima', + ], + 'numbers' => [ + 'all' => 'tutti', + 'filter_button_text' => 'Filtra', + 'reset_button_text' => 'Reset', + 'min_placeholder' => 'Min', + 'max_placeholder' => 'Max', + ], ], - 'eventlog' => [ 'show_stacktrace' => 'Visualizza la traccia dello stack', 'hide_stacktrace' => 'Nascondi la traccia dello stack', @@ -86,7 +96,7 @@ return [ 'openWith' => 'Apri con', 'remember_choice' => 'Ricorda l\'opzione selezionata per questa sessione', 'open' => 'Apri', - 'cancel' => 'Annulla' + 'cancel' => 'Annulla', ] ] ]; From fffa6db8d8b9dddd6b8fca79ffed26753029d59f Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 3 Nov 2020 09:51:23 +0800 Subject: [PATCH 55/69] Add Mockery as dev dependency, limit PHPUnit versions --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 37732c84c..01f9bfefc 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,8 @@ "wikimedia/composer-merge-plugin": "1.4.1" }, "require-dev": { - "phpunit/phpunit": "^8.0|^9.0", + "phpunit/phpunit": "^8.4|^9.3.3", + "mockery/mockery": "~1.3.3|^1.4.2", "fzaninotto/faker": "~1.9", "squizlabs/php_codesniffer": "3.*", "php-parallel-lint/php-parallel-lint": "^1.0", From 59d183b3b01566213aea87714aa08c338ad5de77 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Wed, 4 Nov 2020 12:18:22 -0500 Subject: [PATCH 56/69] Add Sortable Behavior to ReorderController validateModel (#5268) Checks for the newly added Sortable Behavior as well as the existing Sortable Trait. See https://github.com/octobercms/library/commit/b19deb853fbec8426aa9668b6546fa25245c871c --- modules/backend/behaviors/ReorderController.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/backend/behaviors/ReorderController.php b/modules/backend/behaviors/ReorderController.php index 1795afca0..4b9681972 100644 --- a/modules/backend/behaviors/ReorderController.php +++ b/modules/backend/behaviors/ReorderController.php @@ -214,7 +214,10 @@ class ReorderController extends ControllerBehavior $model = $this->controller->reorderGetModel(); $modelTraits = class_uses($model); - if (isset($modelTraits[\October\Rain\Database\Traits\Sortable::class])) { + if ( + isset($modelTraits[\October\Rain\Database\Traits\Sortable::class]) || + $model->isClassExtendedWith(\October\Rain\Database\Behaviors\Sortable::class) + ) { $this->sortMode = 'simple'; } elseif (isset($modelTraits[\October\Rain\Database\Traits\NestedTree::class])) { @@ -222,7 +225,7 @@ class ReorderController extends ControllerBehavior $this->showTree = true; } else { - throw new ApplicationException('The model must implement the NestedTree or Sortable traits.'); + throw new ApplicationException('The model must implement the Sortable trait/behavior or the NestedTree trait.'); } return $model; From 061d35e5fd39839990986ecfbdec755bc711301c Mon Sep 17 00:00:00 2001 From: Siarhei Karavai Date: Sun, 8 Nov 2020 00:13:25 +0300 Subject: [PATCH 57/69] Register theme backend localization files (#4960) Closes #4308. --- modules/cms/ServiceProvider.php | 18 ++++++++++++++++++ modules/cms/controllers/ThemeOptions.php | 5 ++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/modules/cms/ServiceProvider.php b/modules/cms/ServiceProvider.php index 40e170787..fd84ea3b9 100644 --- a/modules/cms/ServiceProvider.php +++ b/modules/cms/ServiceProvider.php @@ -1,11 +1,14 @@ registerBackendPermissions(); $this->registerBackendWidgets(); $this->registerBackendSettings(); + $this->registerBackendLocalization(); } } @@ -284,6 +288,20 @@ class ServiceProvider extends ModuleServiceProvider }); } + /** + * Registers localization from an active theme for backend items. + */ + protected function registerBackendLocalization() + { + $theme = CmsTheme::getActiveTheme(); + + $langPath = $theme->getPath() . '/lang'; + + if (File::isDirectory($langPath)) { + Lang::addNamespace("theme.{$theme->getId()}", $langPath); + } + } + /** * Registers events for menu items. */ diff --git a/modules/cms/controllers/ThemeOptions.php b/modules/cms/controllers/ThemeOptions.php index b20fd5eae..78068c83a 100644 --- a/modules/cms/controllers/ThemeOptions.php +++ b/modules/cms/controllers/ThemeOptions.php @@ -103,8 +103,11 @@ class ThemeOptions extends Controller /** * Default to the active theme if user doesn't have access to manage all themes + * + * @param string $dirName + * @return string */ - protected function getDirName($dirName = null) + protected function getDirName(string $dirName = null) { /* * Only the active theme can be managed without this permission From 3590571642a609901b118cbf3561b23c57a59561 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Mon, 9 Nov 2020 14:10:22 -0500 Subject: [PATCH 58/69] fix themes localization namespace (#5348) --- modules/cms/ServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cms/ServiceProvider.php b/modules/cms/ServiceProvider.php index fd84ea3b9..f70949925 100644 --- a/modules/cms/ServiceProvider.php +++ b/modules/cms/ServiceProvider.php @@ -298,7 +298,7 @@ class ServiceProvider extends ModuleServiceProvider $langPath = $theme->getPath() . '/lang'; if (File::isDirectory($langPath)) { - Lang::addNamespace("theme.{$theme->getId()}", $langPath); + Lang::addNamespace("themes.{$theme->getId()}", $langPath); } } From f18769e282fe4cd321c90e30fd0df6c21d1f0d8e Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Mon, 9 Nov 2020 21:47:56 -0500 Subject: [PATCH 59/69] Check that mail templates/layouts exist before extracting view content (#5322) Co-authored-by: Luke Towers Co-authored-by: Ben Thomson --- modules/system/models/MailLayout.php | 49 +++++++++++++++++++++++- modules/system/models/MailPartial.php | 36 +++++++++++++++++- modules/system/models/MailTemplate.php | 52 ++++++++++++++++++++++++-- 3 files changed, 131 insertions(+), 6 deletions(-) diff --git a/modules/system/models/MailLayout.php b/modules/system/models/MailLayout.php index 42b410fe2..48018e39d 100644 --- a/modules/system/models/MailLayout.php +++ b/modules/system/models/MailLayout.php @@ -50,6 +50,12 @@ class MailLayout extends Model public static $codeCache; + /** + * Fired before the model is deleted. + * + * @return void + * @throws ApplicationException if the template is locked + */ public function beforeDelete() { if ($this->is_locked) { @@ -57,6 +63,11 @@ class MailLayout extends Model } } + /** + * List MailLayouts codes keyed by ID. + * + * @return array + */ public static function listCodes() { if (self::$codeCache !== null) { @@ -66,11 +77,23 @@ class MailLayout extends Model return self::$codeCache = self::lists('id', 'code'); } + /** + * Return the ID of a MailLayout instance from a defined code. + * + * @param string $code + * @return string + */ public static function getIdFromCode($code) { return array_get(self::listCodes(), $code); } + /** + * Find a MailLayout instance by its code or create a new instance from the view file. + * + * @param string $code + * @return MailLayout + */ public static function findOrMakeLayout($code) { $layout = self::whereCode($code)->first(); @@ -87,6 +110,7 @@ class MailLayout extends Model /** * Loops over each mail layout and ensures the system has a layout, * if the layout does not exist, it will create one. + * * @return void */ public static function createLayouts() @@ -107,6 +131,13 @@ class MailLayout extends Model } } + /** + * Fill model using a view file retrieved by code. + * + * @param string|null $code + * @return void + * @throws ApplicationException if a layout with the defined code is not registered. + */ public function fillFromCode($code = null) { $definitions = MailManager::instance()->listRegisteredLayouts(); @@ -122,6 +153,12 @@ class MailLayout extends Model $this->fillFromView($definition); } + /** + * Fill model using a view file retrieved by path. + * + * @param string $path + * @return void + */ public function fillFromView($path) { $sections = self::getTemplateSections($path); @@ -150,8 +187,18 @@ class MailLayout extends Model $this->content_text = array_get($sections, 'text'); } + /** + * Get section array from a view file retrieved by code. + * + * @param string $code + * @return array|null + */ protected static function getTemplateSections($code) { - return MailParser::parse(FileHelper::get(View::make($code)->getPath())); + if (!View::exists($code)) { + return null; + } + $view = View::make($code); + return MailParser::parse(FileHelper::get($view->getPath())); } } diff --git a/modules/system/models/MailPartial.php b/modules/system/models/MailPartial.php index 5d003fcf0..a26dc345e 100644 --- a/modules/system/models/MailPartial.php +++ b/modules/system/models/MailPartial.php @@ -42,6 +42,11 @@ class MailPartial extends Model 'content_html' => 'required', ]; + /** + * Fired after the model has been fetched. + * + * @return void + */ public function afterFetch() { if (!$this->is_custom) { @@ -49,6 +54,12 @@ class MailPartial extends Model } } + /** + * Find a MailPartial instance by code or create a new instance from a view file. + * + * @param string $code + * @return MailTemplate + */ public static function findOrMakePartial($code) { try { @@ -68,6 +79,7 @@ class MailPartial extends Model /** * Loops over each mail layout and ensures the system has a layout, * if the layout does not exist, it will create one. + * * @return void */ public static function createPartials() @@ -98,6 +110,12 @@ class MailPartial extends Model } } + /** + * Fill model using a view file retrieved by code. + * + * @param string|null $code + * @return void + */ public function fillFromCode($code = null) { $definitions = MailManager::instance()->listRegisteredPartials(); @@ -113,6 +131,12 @@ class MailPartial extends Model $this->fillFromView($definition); } + /** + * Fill model using a view file retrieved by path. + * + * @param string $path + * @return void + */ public function fillFromView($path) { $sections = self::getTemplateSections($path); @@ -122,8 +146,18 @@ class MailPartial extends Model $this->content_text = array_get($sections, 'text'); } + /** + * Get section array from a view file retrieved by code. + * + * @param string $code + * @return array|null + */ protected static function getTemplateSections($code) { - return MailParser::parse(FileHelper::get(View::make($code)->getPath())); + if (!View::exists($code)) { + return null; + } + $view = View::make($code); + return MailParser::parse(FileHelper::get($view->getPath())); } } diff --git a/modules/system/models/MailTemplate.php b/modules/system/models/MailTemplate.php index 9940b271e..4cd637e29 100644 --- a/modules/system/models/MailTemplate.php +++ b/modules/system/models/MailTemplate.php @@ -47,6 +47,7 @@ class MailTemplate extends Model /** * Returns an array of template codes and descriptions. + * * @return array */ public static function listAllTemplates() @@ -60,6 +61,7 @@ class MailTemplate extends Model /** * Returns a list of all mail templates. + * * @return array Returns an array of the MailTemplate objects. */ public static function allTemplates() @@ -68,7 +70,9 @@ class MailTemplate extends Model $codes = array_keys(self::listAllTemplates()); foreach ($codes as $code) { - $result[] = self::findOrMakeTemplate($code); + if (View::exists($code)) { + $result[] = self::findOrMakeTemplate($code); + } } return $result; @@ -76,6 +80,7 @@ class MailTemplate extends Model /** * Syncronise all file templates to the database. + * * @return void */ public static function syncAll() @@ -117,6 +122,11 @@ class MailTemplate extends Model } } + /** + * Fired after the model has been fetched. + * + * @return void + */ public function afterFetch() { if (!$this->is_custom) { @@ -124,31 +134,65 @@ class MailTemplate extends Model } } + /** + * Fill model using provided content. + * + * @param string $content + * @return void + */ public function fillFromContent($content) { $this->fillFromSections(MailParser::parse($content)); } + /** + * Fill model using a view file path. + * + * @param string $path + * @return void + */ public function fillFromView($path) { $this->fillFromSections(self::getTemplateSections($path)); } + /** + * Fill model using provided section array. + * + * @param array $sections + * @return void + */ protected function fillFromSections($sections) { - $this->content_html = $sections['html']; - $this->content_text = $sections['text']; + $this->content_html = array_get($sections, 'html'); + $this->content_text = array_get($sections, 'text'); $this->subject = array_get($sections, 'settings.subject', 'No subject'); $layoutCode = array_get($sections, 'settings.layout', 'default'); $this->layout = MailLayout::findOrMakeLayout($layoutCode); } + /** + * Get section array from a view file retrieved by code. + * + * @param string $code + * @return array|null + */ protected static function getTemplateSections($code) { - return MailParser::parse(FileHelper::get(View::make($code)->getPath())); + if (!View::exists($code)) { + return null; + } + $view = View::make($code); + return MailParser::parse(FileHelper::get($view->getPath())); } + /** + * Find a MailTemplate record by code or create one from a view file. + * + * @param string $code + * @return MailTemplate model + */ public static function findOrMakeTemplate($code) { $template = self::whereCode($code)->first(); From 50816a955698eb9b3b7822f78be6fc0613cdcdf5 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 10 Nov 2020 12:53:17 +0800 Subject: [PATCH 60/69] Add support for defining quick actions in the Backend's main nav (#5344) Plugins now have the ability to define quick actions through a "registerQuickActions" method, which follows the same configuration as the "registerNavigation" method. It is still recommended and preferred that most plugin functionality be defined in their own main menu items, but this will allow a plugin to easily define a shortcut (or remove one). --- modules/backend/assets/css/october.css | 17 +- .../backend/assets/less/layout/mainmenu.less | 10 +- modules/backend/classes/NavigationManager.php | 170 ++++++++++++++++-- modules/backend/classes/QuickActionItem.php | 105 +++++++++++ modules/backend/layouts/_mainmenu.htm | 27 ++- modules/cms/ServiceProvider.php | 14 ++ modules/system/classes/PluginBase.php | 23 +++ 7 files changed, 336 insertions(+), 30 deletions(-) create mode 100644 modules/backend/classes/QuickActionItem.php diff --git a/modules/backend/assets/css/october.css b/modules/backend/assets/css/october.css index e11e70041..5703da3d0 100644 --- a/modules/backend/assets/css/october.css +++ b/modules/backend/assets/css/october.css @@ -679,9 +679,10 @@ nav#layout-mainmenu .toolbar-item:before {left:-12px} nav#layout-mainmenu .toolbar-item:after {right:-12px} nav#layout-mainmenu .toolbar-item.scroll-active-before:before {color:#fff} nav#layout-mainmenu .toolbar-item.scroll-active-after:after {color:#fff} -nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-preview {margin:0 0 0 21px} -nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-preview i {font-size:20px} -nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-preview a {position:relative;padding:0 10px;top:-1px} +nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-quick-action {margin:0} +nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-quick-action:first-child {margin-left:21px} +nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-quick-action i {font-size:20px} +nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-quick-action a {position:relative;padding:0 10px;top:-1px} nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-account {margin-right:0} nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-account >a {padding:0 15px 0 10px;font-size:13px;position:relative} nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-account.highlight >a {z-index:600} @@ -706,8 +707,8 @@ nav#layout-mainmenu ul li .mainmenu-accountmenu li:first-child a:active:after {c nav#layout-mainmenu ul li .mainmenu-accountmenu li.divider {height:1px;width:100%;background-color:#e0e0e0} nav#layout-mainmenu.navbar-mode-inline, nav#layout-mainmenu.navbar-mode-inline_no_icons {height:60px} -nav#layout-mainmenu.navbar-mode-inline ul.mainmenu-toolbar li.mainmenu-preview a, -nav#layout-mainmenu.navbar-mode-inline_no_icons ul.mainmenu-toolbar li.mainmenu-preview a {height:60px;line-height:60px} +nav#layout-mainmenu.navbar-mode-inline ul.mainmenu-toolbar li.mainmenu-quick-action a, +nav#layout-mainmenu.navbar-mode-inline_no_icons ul.mainmenu-toolbar li.mainmenu-quick-action a {height:60px;line-height:60px} nav#layout-mainmenu.navbar-mode-inline ul.mainmenu-toolbar li.mainmenu-account >a, nav#layout-mainmenu.navbar-mode-inline_no_icons ul.mainmenu-toolbar li.mainmenu-account >a {height:60px;line-height:60px} nav#layout-mainmenu.navbar-mode-inline ul li .mainmenu-accountmenu, @@ -730,7 +731,7 @@ nav#layout-mainmenu.navbar-mode-inline ul.mainmenu-nav li:last-child, nav#layout-mainmenu.navbar-mode-inline_no_icons ul.mainmenu-nav li:last-child {margin-right:0} nav#layout-mainmenu.navbar-mode-inline_no_icons .nav-icon {display:none !important} nav#layout-mainmenu.navbar-mode-tile {height:78px} -nav#layout-mainmenu.navbar-mode-tile ul.mainmenu-toolbar li.mainmenu-preview a {height:78px;line-height:78px} +nav#layout-mainmenu.navbar-mode-tile ul.mainmenu-toolbar li.mainmenu-quick-action a {height:78px;line-height:78px} nav#layout-mainmenu.navbar-mode-tile ul.mainmenu-toolbar li.mainmenu-account >a {height:78px;line-height:78px} nav#layout-mainmenu.navbar-mode-tile ul li .mainmenu-accountmenu {top:88px} nav#layout-mainmenu.navbar-mode-tile ul.mainmenu-nav li a {position:relative;width:65px;height:65px} @@ -749,14 +750,14 @@ nav#layout-mainmenu .menu-toggle .menu-toggle-title {margin-left:10px} nav#layout-mainmenu .menu-toggle:hover .menu-toggle-icon {opacity:1} body.mainmenu-open nav#layout-mainmenu .menu-toggle-icon {opacity:1} nav#layout-mainmenu.navbar-mode-collapse {padding-left:0;height:45px} -nav#layout-mainmenu.navbar-mode-collapse ul.mainmenu-toolbar li.mainmenu-preview a {height:45px;line-height:45px} +nav#layout-mainmenu.navbar-mode-collapse ul.mainmenu-toolbar li.mainmenu-quick-action a {height:45px;line-height:45px} nav#layout-mainmenu.navbar-mode-collapse ul.mainmenu-toolbar li.mainmenu-account >a {height:45px;line-height:45px} nav#layout-mainmenu.navbar-mode-collapse ul li .mainmenu-accountmenu {top:55px} nav#layout-mainmenu.navbar-mode-collapse ul.mainmenu-toolbar li.mainmenu-account >a {padding-right:0} nav#layout-mainmenu.navbar-mode-collapse ul li .mainmenu-accountmenu:after {right:13px} nav#layout-mainmenu.navbar-mode-collapse ul.nav {display:none} nav#layout-mainmenu.navbar-mode-collapse .menu-toggle {display:inline-block;color:#fff !important} -@media (max-width:769px) {nav#layout-mainmenu.navbar {padding-left:0;height:45px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-preview a {height:45px;line-height:45px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-account >a {height:45px;line-height:45px }nav#layout-mainmenu.navbar ul li .mainmenu-accountmenu {top:55px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-account >a {padding-right:0 }nav#layout-mainmenu.navbar ul li .mainmenu-accountmenu:after {right:13px }nav#layout-mainmenu.navbar ul.nav {display:none }nav#layout-mainmenu.navbar .menu-toggle {display:inline-block;color:#fff !important }} +@media (max-width:769px) {nav#layout-mainmenu.navbar {padding-left:0;height:45px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-quick-action a {height:45px;line-height:45px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-account >a {height:45px;line-height:45px }nav#layout-mainmenu.navbar ul li .mainmenu-accountmenu {top:55px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-account >a {padding-right:0 }nav#layout-mainmenu.navbar ul li .mainmenu-accountmenu:after {right:13px }nav#layout-mainmenu.navbar ul.nav {display:none }nav#layout-mainmenu.navbar .menu-toggle {display:inline-block;color:#fff !important }} .mainmenu-collapsed {position:absolute;height:100%;top:0;left:0;margin:0;background:#000} .mainmenu-collapsed >div {display:block;height:100%} .mainmenu-collapsed >div ul.mainmenu-nav li a {position:relative;width:65px;height:65px} diff --git a/modules/backend/assets/less/layout/mainmenu.less b/modules/backend/assets/less/layout/mainmenu.less index 5d3492c26..f4b8c37cb 100644 --- a/modules/backend/assets/less/layout/mainmenu.less +++ b/modules/backend/assets/less/layout/mainmenu.less @@ -47,7 +47,7 @@ body.mainmenu-open { height: @height; ul.mainmenu-toolbar { - li.mainmenu-preview { + li.mainmenu-quick-action { a { height: @height; line-height: @height; @@ -191,8 +191,12 @@ nav#layout-mainmenu { // ul.mainmenu-toolbar { - li.mainmenu-preview { - margin: 0 0 0 21px; + li.mainmenu-quick-action { + margin: 0; + + &:first-child { + margin-left: 21px; + } i { font-size: 20px; diff --git a/modules/backend/classes/NavigationManager.php b/modules/backend/classes/NavigationManager.php index 6d54d9550..f88baf6c9 100644 --- a/modules/backend/classes/NavigationManager.php +++ b/modules/backend/classes/NavigationManager.php @@ -28,6 +28,11 @@ class NavigationManager */ protected $items; + /** + * @var QuickActionItem[] List of registered quick actions. + */ + protected $quickActions; + protected $contextSidenavPartials = []; protected $contextOwner; @@ -54,6 +59,9 @@ class NavigationManager */ protected function loadItems() { + $this->items = []; + $this->quickActions = []; + /* * Load module items */ @@ -68,11 +76,18 @@ class NavigationManager foreach ($plugins as $id => $plugin) { $items = $plugin->registerNavigation(); - if (!is_array($items)) { + $quickActions = $plugin->registerQuickActions(); + + if (!is_array($items) && !is_array($quickActions)) { continue; } - $this->registerMenuItems($id, $items); + if (is_array($items)) { + $this->registerMenuItems($id, $items); + } + if (is_array($quickActions)) { + $this->registerQuickActions($id, $quickActions); + } } /** @@ -91,17 +106,21 @@ class NavigationManager Event::fire('backend.menu.extendItems', [$this]); /* - * Sort menu items + * Sort menu items and quick actions */ uasort($this->items, static function ($a, $b) { return $a->order - $b->order; }); + uasort($this->quickActions, static function ($a, $b) { + return $a->order - $b->order; + }); /* - * Filter items user lacks permission for + * Filter items and quick actions that the user lacks permission for */ $user = BackendAuth::getUser(); $this->items = $this->filterItemPermissions($user, $this->items); + $this->quickActions = $this->filterItemPermissions($user, $this->quickActions); foreach ($this->items as $item) { if (!$item->sideMenu || !count($item->sideMenu)) { @@ -183,10 +202,6 @@ class NavigationManager */ public function registerMenuItems($owner, array $definitions) { - if (!$this->items) { - $this->items = []; - } - $validator = Validator::make($definitions, [ '*.label' => 'required', '*.icon' => 'required_without:*.iconSvg', @@ -319,7 +334,7 @@ class NavigationManager $this->items[$itemKey]->addSideMenuItem($item); return true; } - + /** * Remove multiple side menu items * @@ -361,10 +376,14 @@ class NavigationManager */ public function listMainMenuItems() { - if ($this->items === null) { + if ($this->items === null && $this->quickActions === null) { $this->loadItems(); } + if ($this->items === null) { + return []; + } + foreach ($this->items as $item) { if ($item->badge) { $item->counter = (string) $item->badge; @@ -444,6 +463,137 @@ class NavigationManager return $items; } + /** + * Registers quick actions in the main navigation. + * + * Quick actions are single purpose links displayed to the left of the user menu in the + * backend main navigation. + * + * The argument is an array of the quick action items. The array keys represent the + * quick action item codes, specific for the plugin/module. Each element in the + * array should be an associative array with the following keys: + * - label - specifies the action label localization string key, used as a tooltip, required. + * - icon - an icon name from the Font Awesome icon collection, required if iconSvg is unspecified. + * - iconSvg - a custom SVG icon to use for the icon, required if icon is unspecified. + * - url - the back-end relative URL the quick action item should point to, required. + * - permissions - an array of permissions the back-end user should have, optional. + * The item will be displayed if the user has any of the specified permissions. + * - order - a position of the item in the menu, optional. + * + * @param string $owner Specifies the quick action items owner plugin or module in the format Author.Plugin. + * @param array $definitions An array of the quick action item definitions. + * @return void + * @throws SystemException If the validation of the quick action configuration fails + */ + public function registerQuickActions($owner, array $definitions) + { + $validator = Validator::make($definitions, [ + '*.label' => 'required', + '*.icon' => 'required_without:*.iconSvg', + '*.url' => 'required' + ]); + + if ($validator->fails()) { + $errorMessage = 'Invalid quick action item detected in ' . $owner . '. Contact the plugin author to fix (' . $validator->errors()->first() . ')'; + if (Config::get('app.debug', false)) { + throw new SystemException($errorMessage); + } + + Log::error($errorMessage); + } + + $this->addQuickActionItems($owner, $definitions); + } + + /** + * Dynamically add an array of quick action items + * + * @param string $owner + * @param array $definitions + * @return void + */ + public function addQuickActionItems($owner, array $definitions) + { + foreach ($definitions as $code => $definition) { + $this->addQuickActionItem($owner, $code, $definition); + } + } + + /** + * Dynamically add a single quick action item + * + * @param string $owner + * @param string $code + * @param array $definition + * @return void + */ + public function addQuickActionItem($owner, $code, array $definition) + { + $itemKey = $this->makeItemKey($owner, $code); + + if (isset($this->quickActions[$itemKey])) { + $definition = array_merge((array) $this->quickActions[$itemKey], $definition); + } + + $item = array_merge($definition, [ + 'code' => $code, + 'owner' => $owner + ]); + + $this->quickActions[$itemKey] = QuickActionItem::createFromArray($item); + } + + /** + * Gets the instance of a specified quick action item. + * + * @param string $owner + * @param string $code + * @return QuickActionItem + * @throws SystemException + */ + public function getQuickActionItem(string $owner, string $code) + { + $itemKey = $this->makeItemKey($owner, $code); + + if (!array_key_exists($itemKey, $this->quickActions)) { + throw new SystemException('No quick action item found with key ' . $itemKey); + } + + return $this->quickActions[$itemKey]; + } + + /** + * Removes a single quick action item + * + * @param $owner + * @param $code + * @return void + */ + public function removeQuickActionItem($owner, $code) + { + $itemKey = $this->makeItemKey($owner, $code); + unset($this->quickActions[$itemKey]); + } + + /** + * Returns a list of quick action items. + * + * @return array + * @throws SystemException + */ + public function listQuickActionItems() + { + if ($this->items === null && $this->quickActions === null) { + $this->loadItems(); + } + + if ($this->quickActions === null) { + return []; + } + + return $this->quickActions; + } + /** * Sets the navigation context. * The function sets the navigation owner, main menu item code and the side menu item code. diff --git a/modules/backend/classes/QuickActionItem.php b/modules/backend/classes/QuickActionItem.php new file mode 100644 index 000000000..bf30cadc1 --- /dev/null +++ b/modules/backend/classes/QuickActionItem.php @@ -0,0 +1,105 @@ +attributes[$attribute] = $value; + } + + public function removeAttribute($attribute) + { + unset($this->attributes[$attribute]); + } + + /** + * @param string $permission + * @param array $definition + */ + public function addPermission(string $permission, array $definition) + { + $this->permissions[$permission] = $definition; + } + + /** + * @param string $permission + * @return void + */ + public function removePermission(string $permission) + { + unset($this->permissions[$permission]); + } + + /** + * @param array $data + * @return static + */ + public static function createFromArray(array $data) + { + $instance = new static(); + $instance->code = $data['code']; + $instance->owner = $data['owner']; + $instance->label = $data['label']; + $instance->url = $data['url']; + $instance->icon = $data['icon'] ?? null; + $instance->iconSvg = $data['iconSvg'] ?? null; + $instance->attributes = $data['attributes'] ?? $instance->attributes; + $instance->permissions = $data['permissions'] ?? $instance->permissions; + $instance->order = $data['order'] ?? $instance->order; + return $instance; + } +} diff --git a/modules/backend/layouts/_mainmenu.htm b/modules/backend/layouts/_mainmenu.htm index 0cf31ac6d..3327218e4 100644 --- a/modules/backend/layouts/_mainmenu.htm +++ b/modules/backend/layouts/_mainmenu.htm @@ -49,15 +49,24 @@