From a636ecd99107552bdc02e6cdab778db89b1e3ece Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Mon, 8 Jun 2026 17:09:08 +0100 Subject: [PATCH] fix: harden CMS security controls --- .gitignore | 17 + README.md | 262 +- cms/.htaccess | 11 + cms/config.local.php.example | 21 + cms/config.php | 67 +- cms/controller/index.php | 13 +- cms/css/editor.css | 154 +- cms/css/login.css | 10 +- cms/data/.htaccess | 2 + cms/data/index.php | 3 + cms/image_upload.php | 127 +- cms/index.php | 166 +- cms/js/cms.js | 970 +- cms/login/index.php | 59 +- cms/logs/.htaccess | 2 + cms/logs/index.php | 4 +- cms/publish_scheduled.php | 32 + cms/src/NeoCMS/Authentication.php | 194 +- cms/src/NeoCMS/CMSController.php | 1144 +- cms/src/NeoCMS/FileStore.php | 114 + cms/src/NeoCMS/Logger.php | 84 +- cms/src/NeoCMS/LoginRateLimiter.php | 142 + cms/src/NeoCMS/SecurityHeaders.php | 53 + cms/templates/another-example-template.html | 32 +- cms/templates/example-template.html | 32 +- cms/tinymce/CHANGELOG.md | 4003 ++ cms/tinymce/README.md | 77 + cms/tinymce/bower.json | 27 + cms/tinymce/composer.json | 52 + cms/tinymce/icons/default/icons.js | 270 + cms/tinymce/icons/default/icons.min.js | 2 +- cms/tinymce/icons/default/index.js | 7 + cms/tinymce/langs/README.md | 3 - cms/tinymce/license.md | 11 +- cms/tinymce/models/dom/index.js | 7 + cms/tinymce/models/dom/model.js | 8994 ++++ cms/tinymce/models/dom/model.min.js | 5 +- cms/tinymce/notices.txt | 21 + cms/tinymce/package.json | 32 + cms/tinymce/plugins/accordion/index.js | 7 + cms/tinymce/plugins/accordion/plugin.js | 1373 + cms/tinymce/plugins/accordion/plugin.min.js | 5 +- cms/tinymce/plugins/advlist/index.js | 7 + cms/tinymce/plugins/advlist/plugin.js | 473 + cms/tinymce/plugins/advlist/plugin.min.js | 5 +- cms/tinymce/plugins/anchor/index.js | 7 + cms/tinymce/plugins/anchor/plugin.js | 237 + cms/tinymce/plugins/anchor/plugin.min.js | 5 +- cms/tinymce/plugins/autolink/index.js | 7 + cms/tinymce/plugins/autolink/plugin.js | 315 + cms/tinymce/plugins/autolink/plugin.min.js | 5 +- cms/tinymce/plugins/autoresize/index.js | 7 + cms/tinymce/plugins/autoresize/plugin.js | 221 + cms/tinymce/plugins/autoresize/plugin.min.js | 5 +- cms/tinymce/plugins/autosave/index.js | 7 + cms/tinymce/plugins/autosave/plugin.js | 249 + cms/tinymce/plugins/autosave/plugin.min.js | 5 +- cms/tinymce/plugins/charmap/index.js | 7 + cms/tinymce/plugins/charmap/plugin.js | 997 + cms/tinymce/plugins/charmap/plugin.min.js | 5 +- cms/tinymce/plugins/code/index.js | 7 + cms/tinymce/plugins/code/plugin.js | 98 + cms/tinymce/plugins/code/plugin.min.js | 5 +- cms/tinymce/plugins/codesample/index.js | 7 + cms/tinymce/plugins/codesample/plugin.js | 3655 ++ cms/tinymce/plugins/codesample/plugin.min.js | 11 +- cms/tinymce/plugins/directionality/index.js | 7 + cms/tinymce/plugins/directionality/plugin.js | 670 + .../plugins/directionality/plugin.min.js | 5 +- cms/tinymce/plugins/emoticons/index.js | 7 + .../plugins/emoticons/js/emojiimages.min.js | 2 - .../plugins/emoticons/js/emojis.min.js | 1 - cms/tinymce/plugins/emoticons/plugin.js | 809 + cms/tinymce/plugins/emoticons/plugin.min.js | 5 +- cms/tinymce/plugins/fullscreen/index.js | 7 + cms/tinymce/plugins/fullscreen/plugin.js | 1607 + cms/tinymce/plugins/fullscreen/plugin.min.js | 5 +- cms/tinymce/plugins/help/index.js | 7 + cms/tinymce/plugins/help/js/i18n/keynav/ar.js | 184 +- .../plugins/help/js/i18n/keynav/bg-BG.js | 93 + .../plugins/help/js/i18n/keynav/bg_BG.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/ca.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/cs.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/da.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/de.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/el.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/en.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/es.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/eu.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/fa.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/fi.js | 184 +- .../plugins/help/js/i18n/keynav/fr-FR.js | 93 + .../plugins/help/js/i18n/keynav/fr_FR.js | 184 +- .../plugins/help/js/i18n/keynav/he-IL.js | 93 + .../plugins/help/js/i18n/keynav/he_IL.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/hi.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/hr.js | 184 +- .../plugins/help/js/i18n/keynav/hu-HU.js | 93 + .../plugins/help/js/i18n/keynav/hu_HU.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/id.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/it.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/ja.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/kk.js | 184 +- .../plugins/help/js/i18n/keynav/ko-KR.js | 93 + .../plugins/help/js/i18n/keynav/ko_KR.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/ms.js | 184 +- .../plugins/help/js/i18n/keynav/nb-NO.js | 93 + .../plugins/help/js/i18n/keynav/nb_NO.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/nl.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/pl.js | 184 +- .../plugins/help/js/i18n/keynav/pt-BR.js | 93 + .../plugins/help/js/i18n/keynav/pt-PT.js | 93 + .../plugins/help/js/i18n/keynav/pt_BR.js | 184 +- .../plugins/help/js/i18n/keynav/pt_PT.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/ro.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/ru.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/sk.js | 184 +- .../plugins/help/js/i18n/keynav/sl-SI.js | 93 + .../plugins/help/js/i18n/keynav/sl_SI.js | 184 +- .../plugins/help/js/i18n/keynav/sv-SE.js | 93 + .../plugins/help/js/i18n/keynav/sv_SE.js | 184 +- .../plugins/help/js/i18n/keynav/th-TH.js | 93 + .../plugins/help/js/i18n/keynav/th_TH.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/tr.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/uk.js | 184 +- cms/tinymce/plugins/help/js/i18n/keynav/vi.js | 184 +- .../plugins/help/js/i18n/keynav/zh-CN.js | 87 + .../plugins/help/js/i18n/keynav/zh-TW.js | 93 + .../plugins/help/js/i18n/keynav/zh_CN.js | 172 +- .../plugins/help/js/i18n/keynav/zh_TW.js | 184 +- cms/tinymce/plugins/help/plugin.js | 827 + cms/tinymce/plugins/help/plugin.min.js | 5 +- cms/tinymce/plugins/image/index.js | 7 + cms/tinymce/plugins/image/plugin.js | 1692 + cms/tinymce/plugins/image/plugin.min.js | 5 +- cms/tinymce/plugins/importcss/index.js | 7 + cms/tinymce/plugins/importcss/plugin.js | 401 + cms/tinymce/plugins/importcss/plugin.min.js | 5 +- cms/tinymce/plugins/insertdatetime/index.js | 7 + cms/tinymce/plugins/insertdatetime/plugin.js | 187 + .../plugins/insertdatetime/plugin.min.js | 5 +- cms/tinymce/plugins/link/index.js | 7 + cms/tinymce/plugins/link/plugin.js | 1711 + cms/tinymce/plugins/link/plugin.min.js | 5 +- cms/tinymce/plugins/lists/index.js | 7 + cms/tinymce/plugins/lists/plugin.js | 602 + cms/tinymce/plugins/lists/plugin.min.js | 5 +- cms/tinymce/plugins/media/index.js | 7 + cms/tinymce/plugins/media/plugin.js | 1467 + cms/tinymce/plugins/media/plugin.min.js | 5 +- cms/tinymce/plugins/nonbreaking/index.js | 7 + cms/tinymce/plugins/nonbreaking/plugin.js | 128 + cms/tinymce/plugins/nonbreaking/plugin.min.js | 5 +- cms/tinymce/plugins/pagebreak/index.js | 7 + cms/tinymce/plugins/pagebreak/plugin.js | 123 + cms/tinymce/plugins/pagebreak/plugin.min.js | 5 +- cms/tinymce/plugins/preview/index.js | 7 + cms/tinymce/plugins/preview/plugin.js | 878 + cms/tinymce/plugins/preview/plugin.min.js | 5 +- cms/tinymce/plugins/quickbars/index.js | 7 + cms/tinymce/plugins/quickbars/plugin.js | 654 + cms/tinymce/plugins/quickbars/plugin.min.js | 5 +- cms/tinymce/plugins/save/index.js | 7 + cms/tinymce/plugins/save/plugin.js | 136 + cms/tinymce/plugins/save/plugin.min.js | 5 +- cms/tinymce/plugins/searchreplace/index.js | 7 + cms/tinymce/plugins/searchreplace/plugin.js | 1367 + .../plugins/searchreplace/plugin.min.js | 5 +- cms/tinymce/plugins/table/index.js | 7 + cms/tinymce/plugins/table/plugin.js | 4011 ++ cms/tinymce/plugins/table/plugin.min.js | 5 +- cms/tinymce/plugins/visualblocks/index.js | 7 + cms/tinymce/plugins/visualblocks/plugin.js | 106 + .../plugins/visualblocks/plugin.min.js | 5 +- cms/tinymce/plugins/visualchars/index.js | 7 + cms/tinymce/plugins/visualchars/plugin.js | 808 + cms/tinymce/plugins/visualchars/plugin.min.js | 5 +- cms/tinymce/plugins/wordcount/index.js | 7 + cms/tinymce/plugins/wordcount/plugin.js | 586 + cms/tinymce/plugins/wordcount/plugin.min.js | 5 +- cms/tinymce/skins/content/dark/content.css | 75 + cms/tinymce/skins/content/dark/content.js | 9 + .../skins/content/dark/content.min.css | 9 + cms/tinymce/skins/content/dark/content.min.ts | 3 + cms/tinymce/skins/content/dark/content.ts | 3 + cms/tinymce/skins/content/default/content.css | 70 + cms/tinymce/skins/content/default/content.js | 9 + .../skins/content/default/content.min.css | 9 + .../skins/content/default/content.min.ts | 3 + cms/tinymce/skins/content/default/content.ts | 3 + .../skins/content/document/content.css | 75 + cms/tinymce/skins/content/document/content.js | 9 + .../skins/content/document/content.min.css | 9 + .../skins/content/document/content.min.ts | 3 + cms/tinymce/skins/content/document/content.ts | 3 + .../skins/content/tinymce-5-dark/content.css | 75 + .../skins/content/tinymce-5-dark/content.js | 9 + .../content/tinymce-5-dark/content.min.css | 9 + .../content/tinymce-5-dark/content.min.ts | 3 + .../skins/content/tinymce-5-dark/content.ts | 3 + .../skins/content/tinymce-5/content.css | 70 + .../skins/content/tinymce-5/content.js | 9 + .../skins/content/tinymce-5/content.min.css | 9 + .../skins/content/tinymce-5/content.min.ts | 3 + .../skins/content/tinymce-5/content.ts | 3 + cms/tinymce/skins/content/writer/content.css | 71 + cms/tinymce/skins/content/writer/content.js | 9 + .../skins/content/writer/content.min.css | 9 + .../skins/content/writer/content.min.ts | 3 + cms/tinymce/skins/content/writer/content.ts | 3 + cms/tinymce/skins/ui/oxide-dark/content.css | 1310 + .../skins/ui/oxide-dark/content.inline.css | 1316 + .../skins/ui/oxide-dark/content.inline.js | 11 +- .../ui/oxide-dark/content.inline.min.css | 11 +- .../skins/ui/oxide-dark/content.inline.min.ts | 136 + .../skins/ui/oxide-dark/content.inline.ts | 136 + cms/tinymce/skins/ui/oxide-dark/content.js | 11 +- .../skins/ui/oxide-dark/content.min.css | 11 +- .../skins/ui/oxide-dark/content.min.ts | 136 + cms/tinymce/skins/ui/oxide-dark/content.ts | 136 + cms/tinymce/skins/ui/oxide-dark/skin.css | 7112 +++ cms/tinymce/skins/ui/oxide-dark/skin.js | 2 +- cms/tinymce/skins/ui/oxide-dark/skin.min.css | 2 +- cms/tinymce/skins/ui/oxide-dark/skin.min.ts | 637 + .../skins/ui/oxide-dark/skin.shadowdom.css | 30 + .../skins/ui/oxide-dark/skin.shadowdom.js | 2 +- .../skins/ui/oxide-dark/skin.shadowdom.min.ts | 9 + .../skins/ui/oxide-dark/skin.shadowdom.ts | 9 + cms/tinymce/skins/ui/oxide-dark/skin.ts | 637 + cms/tinymce/skins/ui/oxide/content.css | 1322 + cms/tinymce/skins/ui/oxide/content.inline.css | 1316 + cms/tinymce/skins/ui/oxide/content.inline.js | 11 +- .../skins/ui/oxide/content.inline.min.css | 11 +- .../skins/ui/oxide/content.inline.min.ts | 136 + cms/tinymce/skins/ui/oxide/content.inline.ts | 136 + cms/tinymce/skins/ui/oxide/content.js | 11 +- cms/tinymce/skins/ui/oxide/content.min.css | 11 +- cms/tinymce/skins/ui/oxide/content.min.ts | 136 + cms/tinymce/skins/ui/oxide/content.ts | 136 + cms/tinymce/skins/ui/oxide/skin.css | 7109 +++ cms/tinymce/skins/ui/oxide/skin.js | 2 +- cms/tinymce/skins/ui/oxide/skin.min.css | 2 +- cms/tinymce/skins/ui/oxide/skin.min.ts | 637 + cms/tinymce/skins/ui/oxide/skin.shadowdom.css | 30 + cms/tinymce/skins/ui/oxide/skin.shadowdom.js | 2 +- .../skins/ui/oxide/skin.shadowdom.min.ts | 9 + cms/tinymce/skins/ui/oxide/skin.shadowdom.ts | 9 + cms/tinymce/skins/ui/oxide/skin.ts | 637 + .../skins/ui/tinymce-5-dark/content.css | 1310 + .../ui/tinymce-5-dark/content.inline.css | 1316 + .../skins/ui/tinymce-5-dark/content.inline.js | 11 +- .../ui/tinymce-5-dark/content.inline.min.css | 11 +- .../ui/tinymce-5-dark/content.inline.min.ts | 136 + .../skins/ui/tinymce-5-dark/content.inline.ts | 136 + .../skins/ui/tinymce-5-dark/content.js | 11 +- .../skins/ui/tinymce-5-dark/content.min.css | 11 +- .../skins/ui/tinymce-5-dark/content.min.ts | 136 + .../skins/ui/tinymce-5-dark/content.ts | 136 + cms/tinymce/skins/ui/tinymce-5-dark/skin.css | 7228 +++ cms/tinymce/skins/ui/tinymce-5-dark/skin.js | 2 +- .../skins/ui/tinymce-5-dark/skin.min.css | 2 +- .../skins/ui/tinymce-5-dark/skin.min.ts | 638 + .../ui/tinymce-5-dark/skin.shadowdom.css | 30 + .../ui/tinymce-5-dark/skin.shadowdom.min.ts | 9 + .../skins/ui/tinymce-5-dark/skin.shadowdom.ts | 9 + cms/tinymce/skins/ui/tinymce-5-dark/skin.ts | 638 + cms/tinymce/skins/ui/tinymce-5/content.css | 1322 + .../skins/ui/tinymce-5/content.inline.css | 1316 + .../skins/ui/tinymce-5/content.inline.js | 11 +- .../skins/ui/tinymce-5/content.inline.min.css | 11 +- .../skins/ui/tinymce-5/content.inline.min.ts | 136 + .../skins/ui/tinymce-5/content.inline.ts | 136 + cms/tinymce/skins/ui/tinymce-5/content.js | 11 +- .../skins/ui/tinymce-5/content.min.css | 11 +- cms/tinymce/skins/ui/tinymce-5/content.min.ts | 136 + cms/tinymce/skins/ui/tinymce-5/content.ts | 136 + cms/tinymce/skins/ui/tinymce-5/skin.css | 7228 +++ cms/tinymce/skins/ui/tinymce-5/skin.js | 2 +- cms/tinymce/skins/ui/tinymce-5/skin.min.css | 2 +- cms/tinymce/skins/ui/tinymce-5/skin.min.ts | 638 + .../skins/ui/tinymce-5/skin.shadowdom.css | 30 + .../skins/ui/tinymce-5/skin.shadowdom.min.ts | 9 + .../skins/ui/tinymce-5/skin.shadowdom.ts | 9 + cms/tinymce/skins/ui/tinymce-5/skin.ts | 638 + cms/tinymce/themes/silver/index.js | 7 + cms/tinymce/themes/silver/theme.js | 35019 +++++++++++++ cms/tinymce/themes/silver/theme.min.js | 7 +- cms/tinymce/tinymce.d.ts | 4999 +- cms/tinymce/tinymce.js | 41904 ++++++++++++++++ cms/tinymce/tinymce.min.js | 12 +- cms/welcome.html | 182 +- docs/ARCHITECTURE.md | 97 + docs/DEVELOPMENT.md | 76 + index.html | 10 +- tests/run.php | 312 + uploads/.htaccess | 12 + uploads/index.php | 3 +- 297 files changed, 174942 insertions(+), 7485 deletions(-) create mode 100644 .gitignore create mode 100644 cms/.htaccess create mode 100644 cms/config.local.php.example create mode 100644 cms/data/.htaccess create mode 100644 cms/data/index.php create mode 100644 cms/logs/.htaccess create mode 100644 cms/publish_scheduled.php create mode 100644 cms/src/NeoCMS/FileStore.php create mode 100644 cms/src/NeoCMS/LoginRateLimiter.php create mode 100644 cms/src/NeoCMS/SecurityHeaders.php create mode 100644 cms/tinymce/CHANGELOG.md create mode 100644 cms/tinymce/README.md create mode 100644 cms/tinymce/bower.json create mode 100644 cms/tinymce/composer.json create mode 100644 cms/tinymce/icons/default/icons.js create mode 100644 cms/tinymce/icons/default/index.js delete mode 100644 cms/tinymce/langs/README.md create mode 100644 cms/tinymce/models/dom/index.js create mode 100644 cms/tinymce/models/dom/model.js create mode 100644 cms/tinymce/notices.txt create mode 100644 cms/tinymce/package.json create mode 100644 cms/tinymce/plugins/accordion/index.js create mode 100644 cms/tinymce/plugins/accordion/plugin.js create mode 100644 cms/tinymce/plugins/advlist/index.js create mode 100644 cms/tinymce/plugins/advlist/plugin.js create mode 100644 cms/tinymce/plugins/anchor/index.js create mode 100644 cms/tinymce/plugins/anchor/plugin.js create mode 100644 cms/tinymce/plugins/autolink/index.js create mode 100644 cms/tinymce/plugins/autolink/plugin.js create mode 100644 cms/tinymce/plugins/autoresize/index.js create mode 100644 cms/tinymce/plugins/autoresize/plugin.js create mode 100644 cms/tinymce/plugins/autosave/index.js create mode 100644 cms/tinymce/plugins/autosave/plugin.js create mode 100644 cms/tinymce/plugins/charmap/index.js create mode 100644 cms/tinymce/plugins/charmap/plugin.js create mode 100644 cms/tinymce/plugins/code/index.js create mode 100644 cms/tinymce/plugins/code/plugin.js create mode 100644 cms/tinymce/plugins/codesample/index.js create mode 100644 cms/tinymce/plugins/codesample/plugin.js create mode 100644 cms/tinymce/plugins/directionality/index.js create mode 100644 cms/tinymce/plugins/directionality/plugin.js create mode 100644 cms/tinymce/plugins/emoticons/index.js create mode 100644 cms/tinymce/plugins/emoticons/plugin.js create mode 100644 cms/tinymce/plugins/fullscreen/index.js create mode 100644 cms/tinymce/plugins/fullscreen/plugin.js create mode 100644 cms/tinymce/plugins/help/index.js create mode 100644 cms/tinymce/plugins/help/js/i18n/keynav/bg-BG.js create mode 100644 cms/tinymce/plugins/help/js/i18n/keynav/fr-FR.js create mode 100644 cms/tinymce/plugins/help/js/i18n/keynav/he-IL.js create mode 100644 cms/tinymce/plugins/help/js/i18n/keynav/hu-HU.js create mode 100644 cms/tinymce/plugins/help/js/i18n/keynav/ko-KR.js create mode 100644 cms/tinymce/plugins/help/js/i18n/keynav/nb-NO.js create mode 100644 cms/tinymce/plugins/help/js/i18n/keynav/pt-BR.js create mode 100644 cms/tinymce/plugins/help/js/i18n/keynav/pt-PT.js create mode 100644 cms/tinymce/plugins/help/js/i18n/keynav/sl-SI.js create mode 100644 cms/tinymce/plugins/help/js/i18n/keynav/sv-SE.js create mode 100644 cms/tinymce/plugins/help/js/i18n/keynav/th-TH.js create mode 100644 cms/tinymce/plugins/help/js/i18n/keynav/zh-CN.js create mode 100644 cms/tinymce/plugins/help/js/i18n/keynav/zh-TW.js create mode 100644 cms/tinymce/plugins/help/plugin.js create mode 100644 cms/tinymce/plugins/image/index.js create mode 100644 cms/tinymce/plugins/image/plugin.js create mode 100644 cms/tinymce/plugins/importcss/index.js create mode 100644 cms/tinymce/plugins/importcss/plugin.js create mode 100644 cms/tinymce/plugins/insertdatetime/index.js create mode 100644 cms/tinymce/plugins/insertdatetime/plugin.js create mode 100644 cms/tinymce/plugins/link/index.js create mode 100644 cms/tinymce/plugins/link/plugin.js create mode 100644 cms/tinymce/plugins/lists/index.js create mode 100644 cms/tinymce/plugins/lists/plugin.js create mode 100644 cms/tinymce/plugins/media/index.js create mode 100644 cms/tinymce/plugins/media/plugin.js create mode 100644 cms/tinymce/plugins/nonbreaking/index.js create mode 100644 cms/tinymce/plugins/nonbreaking/plugin.js create mode 100644 cms/tinymce/plugins/pagebreak/index.js create mode 100644 cms/tinymce/plugins/pagebreak/plugin.js create mode 100644 cms/tinymce/plugins/preview/index.js create mode 100644 cms/tinymce/plugins/preview/plugin.js create mode 100644 cms/tinymce/plugins/quickbars/index.js create mode 100644 cms/tinymce/plugins/quickbars/plugin.js create mode 100644 cms/tinymce/plugins/save/index.js create mode 100644 cms/tinymce/plugins/save/plugin.js create mode 100644 cms/tinymce/plugins/searchreplace/index.js create mode 100644 cms/tinymce/plugins/searchreplace/plugin.js create mode 100644 cms/tinymce/plugins/table/index.js create mode 100644 cms/tinymce/plugins/table/plugin.js create mode 100644 cms/tinymce/plugins/visualblocks/index.js create mode 100644 cms/tinymce/plugins/visualblocks/plugin.js create mode 100644 cms/tinymce/plugins/visualchars/index.js create mode 100644 cms/tinymce/plugins/visualchars/plugin.js create mode 100644 cms/tinymce/plugins/wordcount/index.js create mode 100644 cms/tinymce/plugins/wordcount/plugin.js create mode 100644 cms/tinymce/skins/content/dark/content.css create mode 100644 cms/tinymce/skins/content/dark/content.min.ts create mode 100644 cms/tinymce/skins/content/dark/content.ts create mode 100644 cms/tinymce/skins/content/default/content.css create mode 100644 cms/tinymce/skins/content/default/content.min.ts create mode 100644 cms/tinymce/skins/content/default/content.ts create mode 100644 cms/tinymce/skins/content/document/content.css create mode 100644 cms/tinymce/skins/content/document/content.min.ts create mode 100644 cms/tinymce/skins/content/document/content.ts create mode 100644 cms/tinymce/skins/content/tinymce-5-dark/content.css create mode 100644 cms/tinymce/skins/content/tinymce-5-dark/content.min.ts create mode 100644 cms/tinymce/skins/content/tinymce-5-dark/content.ts create mode 100644 cms/tinymce/skins/content/tinymce-5/content.css create mode 100644 cms/tinymce/skins/content/tinymce-5/content.min.ts create mode 100644 cms/tinymce/skins/content/tinymce-5/content.ts create mode 100644 cms/tinymce/skins/content/writer/content.css create mode 100644 cms/tinymce/skins/content/writer/content.min.ts create mode 100644 cms/tinymce/skins/content/writer/content.ts create mode 100644 cms/tinymce/skins/ui/oxide-dark/content.css create mode 100644 cms/tinymce/skins/ui/oxide-dark/content.inline.css create mode 100644 cms/tinymce/skins/ui/oxide-dark/content.inline.min.ts create mode 100644 cms/tinymce/skins/ui/oxide-dark/content.inline.ts create mode 100644 cms/tinymce/skins/ui/oxide-dark/content.min.ts create mode 100644 cms/tinymce/skins/ui/oxide-dark/content.ts create mode 100644 cms/tinymce/skins/ui/oxide-dark/skin.css create mode 100644 cms/tinymce/skins/ui/oxide-dark/skin.min.ts create mode 100644 cms/tinymce/skins/ui/oxide-dark/skin.shadowdom.css create mode 100644 cms/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.ts create mode 100644 cms/tinymce/skins/ui/oxide-dark/skin.shadowdom.ts create mode 100644 cms/tinymce/skins/ui/oxide-dark/skin.ts create mode 100644 cms/tinymce/skins/ui/oxide/content.css create mode 100644 cms/tinymce/skins/ui/oxide/content.inline.css create mode 100644 cms/tinymce/skins/ui/oxide/content.inline.min.ts create mode 100644 cms/tinymce/skins/ui/oxide/content.inline.ts create mode 100644 cms/tinymce/skins/ui/oxide/content.min.ts create mode 100644 cms/tinymce/skins/ui/oxide/content.ts create mode 100644 cms/tinymce/skins/ui/oxide/skin.css create mode 100644 cms/tinymce/skins/ui/oxide/skin.min.ts create mode 100644 cms/tinymce/skins/ui/oxide/skin.shadowdom.css create mode 100644 cms/tinymce/skins/ui/oxide/skin.shadowdom.min.ts create mode 100644 cms/tinymce/skins/ui/oxide/skin.shadowdom.ts create mode 100644 cms/tinymce/skins/ui/oxide/skin.ts create mode 100644 cms/tinymce/skins/ui/tinymce-5-dark/content.css create mode 100644 cms/tinymce/skins/ui/tinymce-5-dark/content.inline.css create mode 100644 cms/tinymce/skins/ui/tinymce-5-dark/content.inline.min.ts create mode 100644 cms/tinymce/skins/ui/tinymce-5-dark/content.inline.ts create mode 100644 cms/tinymce/skins/ui/tinymce-5-dark/content.min.ts create mode 100644 cms/tinymce/skins/ui/tinymce-5-dark/content.ts create mode 100644 cms/tinymce/skins/ui/tinymce-5-dark/skin.css create mode 100644 cms/tinymce/skins/ui/tinymce-5-dark/skin.min.ts create mode 100644 cms/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.css create mode 100644 cms/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.min.ts create mode 100644 cms/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.ts create mode 100644 cms/tinymce/skins/ui/tinymce-5-dark/skin.ts create mode 100644 cms/tinymce/skins/ui/tinymce-5/content.css create mode 100644 cms/tinymce/skins/ui/tinymce-5/content.inline.css create mode 100644 cms/tinymce/skins/ui/tinymce-5/content.inline.min.ts create mode 100644 cms/tinymce/skins/ui/tinymce-5/content.inline.ts create mode 100644 cms/tinymce/skins/ui/tinymce-5/content.min.ts create mode 100644 cms/tinymce/skins/ui/tinymce-5/content.ts create mode 100644 cms/tinymce/skins/ui/tinymce-5/skin.css create mode 100644 cms/tinymce/skins/ui/tinymce-5/skin.min.ts create mode 100644 cms/tinymce/skins/ui/tinymce-5/skin.shadowdom.css create mode 100644 cms/tinymce/skins/ui/tinymce-5/skin.shadowdom.min.ts create mode 100644 cms/tinymce/skins/ui/tinymce-5/skin.shadowdom.ts create mode 100644 cms/tinymce/skins/ui/tinymce-5/skin.ts create mode 100644 cms/tinymce/themes/silver/index.js create mode 100644 cms/tinymce/themes/silver/theme.js create mode 100644 cms/tinymce/tinymce.js create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/DEVELOPMENT.md create mode 100644 tests/run.php create mode 100644 uploads/.htaccess diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b54c901 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Site-specific credentials and deployment overrides. +/cms/config.local.php + +# Runtime CMS state; retain only the web-access guards committed below. +/cms/data/* +!/cms/data/.htaccess +!/cms/data/index.php +/cms/logs/* +!/cms/logs/.htaccess +!/cms/logs/index.php + +# Uploaded content belongs to the deployed site rather than source control. +/uploads/* +!/uploads/.htaccess +!/uploads/index.php + +# Generated security-audit reports are retained deliberately and should remain visible to Git. diff --git a/README.md b/README.md index ec32df0..7f38781 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,270 @@ # NeoCMS -## Version 2.0 +## Version 3.0 -Welcome to NeoCMS, the super lightweight, database-free content management system! Built with PHP and JavaScript, NeoCMS -is designed to be fast and easy to use. No complicated setup required—just drop it into your static website, and you’re -good to go. +Welcome to NeoCMS, the lightweight, database-free content management system. Built with PHP and JavaScript, NeoCMS is +designed to add controlled visual editing to an existing static website without changing how public pages are served. -Version 2.0 is a complete overhaul to support the latest versions of jQuery and TinyMCE (GPL). We’ve made things even -smoother for your content editing experience. +Version 3.0 adds filesystem-backed drafts, revision history, role-based permissions, scheduled publishing, page and media +management, SEO controls, shared content, navigation menus, responsive previews, accessibility checks, and an operational +dashboard. Public content remains ordinary HTML; the database server may continue its well-earned rest. + +Further technical documentation: + +* [Architecture](docs/ARCHITECTURE.md) +* [Development and testing](docs/DEVELOPMENT.md) ## Features * No Database Required: NeoCMS works without a database, so you don’t have to worry about setup hassles or performance slowdowns. -* Easy Content Editing: With our intuitive WYSIWYG editor, anyone can update content without writing a single line of - code. Adding the additional class *neo-dupe* will allow an element and it's children to be cloned. +* Easy Content Editing: The TinyMCE visual editor updates marked content regions without requiring authors to edit HTML. + Adding `neo-dupe` allows an element and its children to be cloned or removed. * Seamless Integration: Simply add the CMS to any existing static website. Just assign a special class to the elements you want to make editable, and you’re all set. * Consistent Layouts: NeoCMS supports page templates to keep your layouts neat and uniform across all pages. +* Drafts and Publishing: Editors can save drafts without changing the public page. Publishers can publish immediately or + schedule a publication. +* Revision History: Every changed or deleted page receives a timestamped filesystem snapshot that publishers can restore. +* Page Management: Search, create, duplicate, rename, and delete editable HTML pages, including pages in subdirectories. +* Media Library: Browse uploaded images, reuse them, maintain alternative text, see usage counts, and remove files. +* SEO Settings: Edit the page title, description, canonical URL, Open Graph image, and robots indexing setting. +* Shared Content: Mark content with `data-neo-shared="name"` and update matching regions across the site. +* Navigation Menus: Build ordered and nested navigation markup and insert it into an editable region. +* Responsive Preview: Preview the current page at desktop, tablet, and mobile widths. +* Accessibility Checks: Check common authoring problems such as missing alt text, empty links, heading jumps, missing labels, + and absent page metadata before publishing. +* Roles and Activity: Use editor, publisher, and administrator roles. The dashboard displays drafts, schedules, system + problems, and recent activity. ## Installation Guide -Ready to get started? Just follow these simple steps: +To install NeoCMS: -* Drop the CMS folder into your static website. -* Add the uploads folder to your site as well. -* Open /cms/config.php and edit the credentials for any users you want to set up. -* Update the div elements you want to make editable by adding the class editable. -* Visit /cms/, log in, and start editing! +* Place the `cms/` directory in the document root of the static website. +* Place the `uploads/` directory in the same document root. +* Copy `cms/config.local.php.example` to `cms/config.local.php`, add password hashes, and assign roles. +* Update the elements you want to make editable by adding the configured editable class. It is `editable` by default. +* Confirm that PHP may write to the managed HTML files, `cms/data/`, `cms/logs/`, and `uploads/`. +* Visit `/cms/`, log in, and begin editing. That’s it! You’re up and running. +## Configuration + +Tracked defaults live in `cms/config.php`. Deployment credentials and overrides belong in the Git-ignored +`cms/config.local.php`, which must return an array. The supplied Apache rules deny direct access to both files. + +| Setting | Type | Purpose | +| --- | --- | --- | +| `authentication` | array | Maps usernames to `password_hash()` values; plaintext credentials are rejected. | +| `roles` | array | Maps usernames to `editor`, `publisher`, or `administrator`. | +| `security` | array | Configures session expiry, login throttling, request limits, storage quotas, and audit-log retention. | +| `uploads` | array | Configures image size, dimension, file-count, and aggregate-storage limits. | +| `audit` | boolean | Enables daily activity logs under `cms/logs/`. | +| `skipWelcomePage` | boolean | Opens the public root instead of the built-in guide after login. | +| `showFullUrl` | boolean | Displays the current page path in the toolbar. | +| `editableClass` | string | Selects the single CSS class used to identify editable regions. | + +`dataDirectory` is an internal override used by the integration suite and specialised deployments. Normal installations +should allow NeoCMS to use `cms/data/`. + +### Editable Class + +The class used to identify editable regions is configurable: + +```php +'editableClass' => 'editable', +``` + +Set it to any single valid CSS class name without a leading dot, for example: + +```php +'editableClass' => 'cms-content', +``` + +Your page markup must then use that class: + +```html +
This region can be edited.
+``` + +Invalid configured values fall back to `editable`. The `neo-dupe` class remains fixed because it marks repeatable blocks, +not general editable content. When overriding `editableClass`, update the classes in `cms/templates/` as well so newly +created pages are immediately discoverable by the page picker. + +### Users and Roles + +Passwords must be generated with `password_hash()` and stored under `authentication` in `cms/config.local.php`. Assign the +same username a role under `roles`: + +```php +'authentication' => [ + 'author' => '$2y$10$...', + 'publisher' => '$2y$10$...', +], +'roles' => [ + 'author' => 'editor', + 'publisher' => 'publisher', +], +``` + +* `editor`: edit content, save drafts, and upload media. +* `publisher`: editor permissions plus immediate and scheduled publishing and revision restoration. +* `administrator`: publisher permissions plus page, media, shared-content, and menu management. + +Users without an explicit valid role receive `editor`, the least powerful role. Account removal, password-hash changes, +and role changes affect active sessions on their next request. Sessions expire after 30 minutes of inactivity and 12 hours +in total by default. + +When HTTPS terminates at a trusted reverse proxy, set `'cookieSecure' => true` beneath `security` in the local override. +This keeps the session cookie marked Secure even when PHP receives the proxy's internal HTTP request. + +Audit files rotate at 10 MiB and are retained for 90 days by default. Override `auditMaxFileBytes` and +`auditRetentionDays` beneath `security` where local retention obligations differ. + +### Password Hash Example + +Generate a hash on a trusted command line, then place only the resulting hash in `cms/config.local.php`: + +```bash +php -r 'echo password_hash(trim(fgets(STDIN)), PASSWORD_DEFAULT), PHP_EOL;' +``` + +There is deliberately no enabled default account. + +## Content Features + +### Repeatable Blocks + +Add `neo-dupe` alongside the configured editable class. The editor provides clone-before, clone-after, and delete controls: + +```html +
Repeatable content
+``` + +### Shared Content + +Use `data-neo-shared` to identify a global region: + +```html + +``` + +The Shared Content tool updates every HTML page containing the same key. Publishing a page also captures the current value +of its shared regions. + +### Navigation Menus + +Menu items use one line each in this format: + +```text +Home | / +About | /about.html +Team | /team.html | About +``` + +The optional third value is the label of the parent item. After saving, the generated `