commit c1042c9cdeb63b0124f135e2c6cd410cf71afe9a Author: Benjamin Date: Sun Jan 11 19:39:55 2026 +0100 Start diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..55ef2b7 --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +ROMHACKPLAZA_NONCE_KEY="" +ROMHACKPLAZA_API_URL="" +ROMHACKPLAZA_SHORT_URL="" +ROMHACKPLAZA_SCRIPTS_FOLDER="" +ROMHACKPLAZA_SCRIPTS_ADMIN_FOLDER="" +ROMHACKPLAZA_DEFAULT_ACCOUNT= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..770d9cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.env +.idea +doc/out +node_modules +vendor +assets/**/*.js \ No newline at end of file diff --git a/assets/css/submissions.css b/assets/css/submissions.css new file mode 100644 index 0000000..23ce0fc --- /dev/null +++ b/assets/css/submissions.css @@ -0,0 +1,695 @@ +body { + font-family: 'Arial', sans-serif; + background-color: #f5f5f5; + margin: 0; + color: #333; +} + +/* Accomodating for theme css */ +.single-post .post-inner { + padding-left: 0px !important; + +} + +.wplink-autocomplete { + color: #111; + font-size: 14px; +} + +.ui-autocomplete.wplink-autocomplete li { + padding: 0px 10px !important; +} + +.container.custom-uploader { + width: 100%; + margin: 0 auto; + padding: 20px; + background-color: #ffffff; + color: #000; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +@media (max-width: 600px) { + .container { + width: 100%; + padding: 10px; + border-radius: 0; + } +} + +.container h1, .container h3 { + /* color: #2C3E50; */ +} + +.container h3 { + + margin-bottom: 15px; +} + +.container input:focus { + + color: #454545; +} + +.container input[type="text"], +.container textarea, +.container input[type="file"], +.container input[type="url"] { + box-sizing: border-box; + width: 100%; + padding: 8px; + border: 1px solid #ccc; + border-radius: 5px; + font-family: 'Arial', sans-serif; +} + +.container input[type="date"] { + display: block; + padding: 8px; + margin-bottom: 20px; + border: 1px solid #ccc; + border-radius: 5px; + font-family: 'Arial', sans-serif; +} + +.acf-field-image, +.container #gameTitle, +.container #platform, +.container #language, +.container #versionNumber, +.container #hashes, +.container #featuredImage, +.container #staff, +.container #releaseSite, +.container #youtubeVideo, +.container fieldset, +.container #entryTitle, +.container #current-files, +.container .modifications-wrapper, +.container form#translationForm, +.container #rhdopage, +.container .news-selector, +.container .wp-editor-wrap +{ + margin-bottom: 30px !important; +} + + + +.modifications-wrapper input[type="checkbox"], +fieldset input[type="radio"] { + transform: scale(1.5); +} + + +.container .label { + font-weight: bold; + display: block; + margin-bottom: 15px; +} + + +.modifications-wrapper label { + font-weight: 500; +} + +fieldset label { + font-weight: 500; + margin-left: 5px; +} + +.modifications-wrapper { + display: grid; + grid-template-columns: repeat(4, 1fr); /* Create four columns */ + gap: 15px; /* Spacing between columns and rows */ +} + +.modifications-wrapper label { + box-sizing: border-box; /* Include padding and border in the element's total width and height */ +} + +/* Adjusts any labels not inside .modifications-wrapper */ +.custom-uploader label:not(.modifications-wrapper label):not(fieldset label) { + margin-bottom: 15px; + display: block; +} + +fieldset { + display: flex; + justify-content: space-between; +} + +.status-option { + flex: 1; + display: flex; + align-items: center; /* Vertically center the label and radio button */ +} + + +.custom-select { + display: block; + width: 100%; + height: 28px; + outline-color: #444; + color: #454545; +} + +.container textarea { + resize: vertical; +} + + +.file-upload-container { + /* display: flex; */ + align-items: center; + justify-content: space-between; + margin-bottom: 30px; +} + +.container #fileInput, .container #filetohash { + flex-grow: 1; + margin-right: 10px; + border: 1px solid #ccc; + padding: 8px; + background: #fff; + border-radius: 5px; +} + +.button-container { + display: flex; + align-items: center; + gap: 10px; +} + +.blueButton, .acf-gallery-add, a.acf-button.button { + background-color: #db7f00; + font-size: 16px; + color: #fff !important; + border: none; + padding: 10px 20px; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.2s; + text-decoration: none; +} + +.acf-gallery-add:focus { + text-decoration: none !important; +} + +.container button:hover { + background-color: #ae6400; +} + +.container #cancelButton { + background-color: #E74C3C; + padding: 13px; + width: 160px; +} + +.container #cancelButton:hover { + background-color: #C0392B; +} + +.container #fullbar { + width: 100%; /* Adjusted to full width */ + position: relative; + background-color: #e0e0e0; + border-radius: 5px; + margin-top: 10px; +} + +.container .bar { + position: relative; + background-color: #2ECC71; + height: 20px; + border-radius: 3px; +} + +.container #status { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: black; + z-index: 2; + font-weight: bold; + white-space: nowrap; +} + +.container #speed { + margin-top: 10px; + font-style: italic; +} + +.container input[type="text"]:focus, textarea:focus, input[type="file"]:focus, input[type="date"]:focus, input[type="url"]:focus { + outline: none; + border-color: #3498DB; + box-shadow: 0 0 5px rgba(52, 152, 219, 0.5); +} + +.container button:disabled, button[disabled] { + background-color: #B3B3B3; + cursor: not-allowed; +} + +.container button:disabled:hover, button[disabled]:hover { + background-color: #B3B3B3; +} + +.uploaded-file { + align-items: center; + margin: 10px 20px; +} + +.delete-file { + color: red; + cursor: pointer; + padding-left: 10px; +} + +.put-as-private-file, .put-as-public-file { + color: blue; + cursor: pointer; +} + +.put-as-archive-file { + color: purple; + cursor: pointer; +} + +.field-helper { + font-size: 0.75em; + display: block; + color: #777; + margin: 5px; + font-style: italic; + max-width: 90%; + word-wrap: break-word; +} + +.fields-importance { + font-weight: bold; + color: black; + text-align: right; + border-bottom: 2px solid black; + width: 100%; + padding-bottom: 5px; + margin: 30px 0; +} + +.select2-container { + width: 100% !important; /* This makes the select2 dropdown take the full width of its container */ + margin-bottom: 30px !important; +} + +.select2-results__option { + + line-height: 20px !important; + font-size: 0.75em !important; +} + +.select2-results__message { + + font-size: smaller; + padding: 3px; +} + +.select2-results { + color: #555; +} + +.acf-fields > .acf-field { + padding: 0px !important; +} + +.acf-gallery, .acf-gallery-toolbar, .acf-gallery-main { + border-radius: 5px; +} + +.acf-label, .acf-hl > li.acf-fr { + display: none; +} + +/* Styles the scrollbar for the whole page */ +/* NO, apply when needed not whole form */ + +.attachments-wrapper::-webkit-scrollbar { + width: 30px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; /* Adjust the track color if necessary */ +} + +::-webkit-scrollbar-thumb { + background: #aaa; /* Adjust the thumb color if necessary */ +} + +/*unused?*/ +.hide-if-value { + font-size: 16px; + font-style: italic; + color: #ea5973; +} + +/*media library*/ + + +.media-modal button, +.media-modal .button, +.media-modal .faux-button, +.media-modal :root .wp-block-button__link, +.media-modal :root .wp-block-file__button, +.media-modal input[type="button"], +.media-modal input[type="reset"], +.media-modal input[type="submit"] { + padding: 0; + margin: 0; + border: initial; + border-radius: 0; + background: initial; + font-size: initial; + line-height: initial; + font-weight: initial; + text-transform: none; + letter-spacing: normal; + color: inherit; + text-shadow: none; + -webkit-appearance: initial; + color: initial; +} +#wp-link-wrap { + color: initial; + font-size: initial !important; +} + +.media-modal-content { + color: initial; +} + +.languages-selector, +.category-selector { + height: 190px; + width: 100%; + overflow-y: scroll; + margin-bottom: 30px; + padding: 5px; + border: 1px #aaaaaa solid; + border-radius: 3px; +} + +.languages-selector label, .category-selector label { + margin-bottom: 0px !important; + margin-top: 5px; +} + + +.custom-post-status-select, .custom-post-author-select { + padding: 8px 12px; /* Padding for better readability */ + margin: 10px 0; /* Space around the select */ + background-color: #fff; /* Background color */ + border: 1px solid #ddd; /* Border properties */ + border-radius: 4px; /* Rounded corners */ + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* Subtle shadow for depth */ + font-size: 16px; /* Adequate font size */ + color: #333; /* Font color */ +} + +.custom-post-status-select:hover, .custom-post-author-select:hover { + border-color: #bbb; /* Change border on hover for visual feedback */ +} + +.custom-post-status-select:focus, .custom-post-author-select:focus { + border-color: #888; /* Change border when focused for accessibility */ + outline: none; /* Remove default focus outline */ + box-shadow: 0 0 5px rgba(0, 0, 0, 0.2); /* Enhanced shadow on focus */ +} + +.drafts-checkbox label { + display: inline-block !important; +} + +.container.custom-uploader.translation { + background-color: #e5f8d5; /*green*/ +} +.container.custom-uploader.romhack { + background-color: #dbe3ff; /* blue*/ +} +.container.custom-uploader.homebrew { + background-color: #ffeaee; /*red*/ +} +.container.custom-uploader.utility { + background-color: #fffdea; /*yellow*/ +} +.container.custom-uploader.document { + background-color: #f1eaff; /*purple*/ +} +.container.custom-uploader.lua-script { + background-color: #eed6c5; /*orange*/ +} +.container.custom-uploader.tutorial { + background-color: #d1c9ba; /* brown */ +} +.container.custom-uploader.news { + background-color:#b0b0b0; /* brown */ +} + + + +.select2-style { + display: block; + width: 100%; /* Responsive width */ + padding: 8px 12px; /* Adequate padding for better appearance */ + border: 1px solid #ccc; /* Subtle border */ + border-radius: 4px; /* Rounded corners */ + box-shadow: 1px 1px 2px rgba(0,0,0,0.1); /* Slight shadow for depth */ + background-color: white; + font-size: 16px; /* Readable font size */ + line-height: 1.5; /* Adequate line height for readability */ + color: #555; /* Font color */ + -webkit-appearance: none; /* Removes default styling on Webkit browsers */ + -moz-appearance: none; /* Removes default styling on Mozilla browsers */ + appearance: none; /* Standard way to remove default arrow */ + background-image: url('data:image/svg+xml;charset=US-ASCII,'); /* Custom arrow icon */ + background-repeat: no-repeat; + background-position: right 8px center; /* Position of the arrow icon */ + cursor: pointer; /* Pointer cursor on hover */ +} + +.select2-style:focus { + outline: none; + border-color: #66afe9; /* Highlight color when focused */ + box-shadow: 0 0 5px rgba(102, 175, 233, .6); /* Shadow for focused state */ +} + +/* Style for disabled state */ +.select2-style:disabled { + background-color: #eee; + opacity: 0.5; + cursor: not-allowed; +} + +/* Optional: Style for option elements */ +.select2-style option { + padding: 0 8px; /* Padding inside options */ +} + +.drop-container { + position: relative; + display: flex; + gap: 10px; + flex-direction: column; + justify-content: center; + align-items: center; + height: 80px; + padding: 20px; + border-radius: 10px; + border: 2px dashed #555; + color: #444; + cursor: pointer; + transition: background .2s ease-in-out, border .2s ease-in-out; +} + +.drop-container:hover, +.drop-container.drag-active { + background: #eee; + border-color: #111; +} + +.drop-container:hover .drop-title, +.drop-container.drag-active .drop-title { + color: #222; +} + +.drop-title { + color: #444; + font-size: 20px; + font-weight: bold; + text-align: center; + transition: color .2s ease-in-out; + display: flex; + align-items: center; + justify-content: center; +} + +input[type="checkbox"], input[type="radio"]{ + + accent-color: #db7f00; +} + +input[type="date"]{ + + width: 100%; +} + +.select2-selection--single, .select2-selection__rendered{ + + border: 0 !important; + border-radius: 5px; +} + +.select2-selection__rendered, .select2-search__field, .select2-dropdown, .select2-search, .select2-results, .select2-selection__choice{ + + background-color: var( --field-background) !important; + color: var( --text-color ) !important; +} + +.select2-selection--multiple{ + + border: 0 !important; + background-color: var( --field-background) !important; +} + +.select2-results_option--highlighted{ + + background-color: #db7f00 !important; +} + +.select2-selection__placeholder{ + + color: var( --text-color ) !important; +} + +.accordion-section { + + margin-bottom: 10px; + padding: 5px; + +} + +.accordion-header { + + display: flex; + justify-content: space-between; + align-items: center; + + background-color: var( --widget-background-color ); + padding: 10px; + border-radius: 4px; + color: var( --text-color ); + font-size: 24px; + font-weight: 700; + padding-left: 1.5%; + padding-right: 1.5%; + width: 100%; + transition: all ease 0.2s; + + +} + +.accordion-header:hover { + + cursor: pointer; + background-color: var( --grid-background ); + +} + +.accordion-title { + display: flex; + align-items: center; + gap: 8px; +} + +.accordion-open-close-icon { + +} + +.accordion-content { + + display:none; + padding:10px; + +} + +.edit-quick-tools { + + display: flex; + justify-content: flex-end; + gap: 10px; + text-decoration: none !important; + margin-bottom: 3% ; + +} + +.edit-quick-tools a { + + text-decoration: none; + color: var( --text-color ) !important; +} + +.quick-tools-button { + + background-color: var( --widget-background-color ); + border-radius: 3px; + font-size: 14px; + padding: 5px; + width: 32px; + text-align: center; + +} + +.quick-tools-button.correct { + + background-color: rgba( 60, 159, 101, 0.4 ); + +} + +.quick-tools-button.warning { + + background-color: rgba( 244, 255, 0, 0.4 ); + color: #454545; + +} + +.quick-tools-button.danger { + + background-color: rgba( 255, 0, 0, 0.4 ); + +} + +.quick-tools-button:hover, .quick-tools-button.danger:hover { + + background-color: var( --grid-background ); + cursor: pointer; + color: var( --text-color ); + +} + +.edit_entry_title { + + float: left; + width: 60%; + word-wrap: break-word; + overflow-wrap: break-word; + font-weight: 700; + font-size: 24px; +color( --text-color ); + +} + +#edit_entry_title_status { + + text-transform: capitalize; +} \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100755 index 0000000..6e3d6de --- /dev/null +++ b/composer.json @@ -0,0 +1,10 @@ +{ + "autoload": { + "psr-4": { + "RomhackPlaza\\": "src/" + } + }, + "require": { + "vlucas/phpdotenv": "^5.6" + } +} diff --git a/composer.lock b/composer.lock new file mode 100755 index 0000000..82fd965 --- /dev/null +++ b/composer.lock @@ -0,0 +1,492 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "108be68e4e2b97fed51d36a10eed0849", + "packages": [ + { + "name": "graham-campbell/result-type", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:45:45+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.4", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2025-08-21T11:53:16+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.2", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.3", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2025-04-30T23:37:27+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/doc/config/boxes.tex b/doc/config/boxes.tex new file mode 100644 index 0000000..2279584 --- /dev/null +++ b/doc/config/boxes.tex @@ -0,0 +1,13 @@ +\tcbset{ + warningstyle/.style={ + colback=yellow!10!white, + colframe=red!50!black, + fonttitle=\bfseries, + } +} + +\newtcolorbox{warnbox}[1][]{ + warningstyle, + title=\faWarning{} Warning, + #1 +} \ No newline at end of file diff --git a/doc/config/commands.tex b/doc/config/commands.tex new file mode 100644 index 0000000..8a397e5 --- /dev/null +++ b/doc/config/commands.tex @@ -0,0 +1,34 @@ +% Print WordPress name. +\NewDocumentCommand{\WP}{ s }{% + \IfBooleanTF{#1}{\textsc{wp}\xspace}{WordPress\xspace}% +} + +% Print ACF name. +\NewDocumentCommand{\acf}{ s }{% + \IfBooleanTF{#1}{\textsc{acf}\xspace}{Advanced Custom Fields\xspace}% +} + +% Print RHPZ name. +\NewDocumentCommand{\rhpz}{ s }{% + \IfBooleanTF{#1}{\textsc{RHPZ}\xspace}{Romhack Plaza\xspace}% +} + +% Show directory structure. +\NewDocumentCommand{\dir}{ s m }{% + \textcolor{violet}{\textsl{#2\IfBooleanF{#1}{.php}}}\xspace% +} + +\NewDocumentCommand{\sourceargs}{ m }{ + \textbf{Parameters}: \begin{itemize}% + #1% + \end{itemize}% +} +\NewDocumentCommand{\sourcereturn}{ m }{ + \textbf{Return}: \begin{itemize}% + #1% + \end{itemize}% +} +\NewDocumentCommand{\sourceexample}{ m m m }{ + \textbf{Example}: % + \lstinputlisting[firstnumber=1,linerange={#2-#3}]{#1}% +} \ No newline at end of file diff --git a/doc/config/listings.tex b/doc/config/listings.tex new file mode 100644 index 0000000..ffbbb62 --- /dev/null +++ b/doc/config/listings.tex @@ -0,0 +1,52 @@ +\lstset{ + language=PHP, + alsoletter={\$}, + basicstyle=\ttfamily, + numbers=left, + frame=single, + breaklines=true, + breakatwhitespace=true, + columns=flexible, + keywordstyle=\color{blue}, + keywordstyle=[2]\color{teal}, % Var & Useful functions. + keywordstyle=[3]\color{purple}, + commentstyle=\color{lightgray}\itshape, + stringstyle=\color{orange}, + numberstyle=\color{lightgray}, + showstringspaces=false, + morekeywords={ + require_once, + public, + protected, + private, + final, + static, + function, + return, + function, + namespace, + global, + defined, + class, + abstract, + interface, + new, + string, + int, + }, % PHP Keywords. + morekeywords=[2]{ + \$_romhackplaza, + \$_romhackplaza_theme, + \$post_id, + \$this, + \$post_types, + \$context, + }, % Plugin useful vars. + morekeywords=[3]{ + do_action, + add_action, + add_filter, + apply_filters, + CPT + } % Plugin & WP useful functions. +} \ No newline at end of file diff --git a/doc/doc.tex b/doc/doc.tex new file mode 100644 index 0000000..2297dc3 --- /dev/null +++ b/doc/doc.tex @@ -0,0 +1,50 @@ + +\documentclass[11pt]{scrreprt} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{lmodern} +\usepackage[a4paper]{geometry} +\usepackage{xspace} +\usepackage{xparse} +\usepackage{xstring} +\usepackage{xcolor} +\usepackage{graphicx} +\usepackage{tabularray} +\usepackage{tcolorbox} +\usepackage{fontawesome} +\usepackage{babel} +\usepackage{listings} +\usepackage{subcaption} +\usepackage{hyperref} +\usepackage{hypcap} + +\input{config/listings} +\input{config/commands} +\input{config/boxes} + +\NewDocumentEnvironment{sourcedef}{ m m m O{false} }{% + \StrSubstitute{#1}{../}{}[\sourcepath]% + \noindent File location: \dir*{\sourcepath}% + \lstinputlisting[label=lst:\sourcepath,includerangemarker=#4,linerange={#2-#3}]{#1} +}{% + \par +} + +\begin{document} + + \title{ + RHPZ + } + \author{ + Test + } + + \maketitle + \tableofcontents + + \include{hooks} + \include{timber} + \include{how-to} + + +\end{document} \ No newline at end of file diff --git a/doc/examples/how-to.php b/doc/examples/how-to.php new file mode 100644 index 0000000..c5b92bc --- /dev/null +++ b/doc/examples/how-to.php @@ -0,0 +1,3 @@ + '] = new CPT('', '', ); + ?> \ No newline at end of file diff --git a/doc/examples/timber.php b/doc/examples/timber.php new file mode 100644 index 0000000..3b4d1b4 --- /dev/null +++ b/doc/examples/timber.php @@ -0,0 +1,2 @@ +|: the ID of your custom post type. + \item \lstinline||: the singular English name of your custom post type\footnote{This field may be removed in a later version.}. + \item \lstinline||: an array like the one on \href{https://developer.wordpress.org/reference/functions/register_post_type/}{\lstinline|register_post_type|}. +\end{itemize} \ No newline at end of file diff --git a/doc/timber.tex b/doc/timber.tex new file mode 100644 index 0000000..5028907 --- /dev/null +++ b/doc/timber.tex @@ -0,0 +1,72 @@ +\chapter{Timber extension}\label{ch:timber} + +From \href{https://timber.github.io/docs/v2/}{Timber documentation}: + +\begin{quotation} +Timber helps you create fully-customized WordPress themes faster with more sustainable code. +With Timber, you write your HTML with the Twig Template Engine. This cleans up your theme code. Your PHP file can focus on providing the data or logic and your Twig files can focus 100\% on the HTML and display. +\end{quotation} + +At Romhack Plaza, we decided to separate the content from the form as much as possible by using Timber templates. +We therefore added new features to Timber via our plugin. +Most of them can be found in the \dir{src/Extenders/Timber} file. + +\begin{warnbox} + Timber is included in \WP via the \emph{\rhpz theme} and not by the plugin! You must enable the \rhpz theme to have access to each of these functions.% +\end{warnbox} + +\section{Views}\label{sec:views} + +\rhpz plugin add a new view location folder: \dir*{views} at the root of the plugin folder. +\lstinputlisting[label=lst:view-def,includerangemarker=false,linerange={-}]{../src/Extenders/Timber.php} + +If you want render a template file from that folder, you can specify the folder with "@plugin": +\lstinputlisting[label=lst:view-usage,linerange=2-2]{examples/timber.php} + +\section{Functions}\label{sec:timber-funcs} + +Some functions have been added to use them in a Timber template. + +\subsection{svg\_icon}\label{subsec:svg-icon} +\begin{sourcedef}{../src/Extenders/Timber.php}{}{} + + \sourceargs{ + \item\lstinline|string icon_id| + } + + \sourceexample{examples/twig-examples.twig}{1}{1} + + Call the \lstinline|svg_icon| snippet from the \rhpz theme. + The SVG icon list is from \href{https://genericons.com/}{genericons neue}. + If the theme is not enabled, this callable return an empty string. + +\end{sourcedef} + +\subsection{theme\_setting}\label{subsec:theme_setting} +\begin{sourcedef}{../src/Extenders/Timber.php}{}{} + + \sourceargs{ + \item\lstinline|string option_name| + } + + \sourcereturn{ + \item\lstinline|mixed| If not found, return \lstinline|false|. + } + + \sourceexample{examples/twig-examples.twig}{2}{2} + + Call the \lstinline|$_romhackplaza_theme->settings->get()$| from the \rhpz theme. + Get a setting registered from the theme\footnote{Every settings linked to Customizer.}. + If the theme is not enabled, this callable return an empty string. + +\end{sourcedef} + +\section{Conditional tags}\label{sec:cond-tags} + +Some \href{https://codex.wordpress.org/Conditional_Tags}{conditional tags} are added to the \href{https://timber.github.io/docs/v2/guides/context/}{Timber context}: +\lstinputlisting[label=lst:cond-tags,includerangemarker=false,linerange={-}]{../src/Extenders/Timber.php} + +\sourceexample{examples/twig-examples.twig}{3}{5} + + + diff --git a/glossary.txt b/glossary.txt new file mode 100644 index 0000000..188a2ab --- /dev/null +++ b/glossary.txt @@ -0,0 +1,8 @@ +custom_featured_image : Custom ACF field to save temporarly the main image and set it as the thumbnail. +edit_posts : Standard cap in Wordpress. Used by every members, except read-only (subscriber) +edit_others_posts : Standard cap in WordPress. Used here by all roles that are staff members. +game : Custom Taxonomy for game listing. +language : Custom Taxonomy for language listing. +my_gallery : Gallery field internal name. +platform : Custom Taxonomy for consoles. +version_number : Version number internal field. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9a17a9a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1075 @@ +{ + "name": "romhackplaza", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "romhackplaza", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "typescript": "^5.9.3", + "vite": "^7.3.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", + "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", + "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", + "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", + "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", + "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", + "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", + "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", + "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", + "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", + "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", + "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", + "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", + "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", + "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", + "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", + "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", + "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", + "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", + "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", + "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", + "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", + "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", + "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.54.0", + "@rollup/rollup-android-arm64": "4.54.0", + "@rollup/rollup-darwin-arm64": "4.54.0", + "@rollup/rollup-darwin-x64": "4.54.0", + "@rollup/rollup-freebsd-arm64": "4.54.0", + "@rollup/rollup-freebsd-x64": "4.54.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", + "@rollup/rollup-linux-arm-musleabihf": "4.54.0", + "@rollup/rollup-linux-arm64-gnu": "4.54.0", + "@rollup/rollup-linux-arm64-musl": "4.54.0", + "@rollup/rollup-linux-loong64-gnu": "4.54.0", + "@rollup/rollup-linux-ppc64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-musl": "4.54.0", + "@rollup/rollup-linux-s390x-gnu": "4.54.0", + "@rollup/rollup-linux-x64-gnu": "4.54.0", + "@rollup/rollup-linux-x64-musl": "4.54.0", + "@rollup/rollup-openharmony-arm64": "4.54.0", + "@rollup/rollup-win32-arm64-msvc": "4.54.0", + "@rollup/rollup-win32-ia32-msvc": "4.54.0", + "@rollup/rollup-win32-x64-gnu": "4.54.0", + "@rollup/rollup-win32-x64-msvc": "4.54.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6f06d62 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "romhackplaza", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build:rhpz-url": "vite build --config vite-configs/vite.rhpz-url.config.js", + "build:send-notification": "vite build --config vite-configs/vite.send-notification.config.js", + "build:manage-notifications": "vite build --config vite-configs/vite.manage-notifications.config.js", + "build:admin-scripts": "vite build --config vite-configs/vite.admin-scripts.config.js", + "dev": "vite build --watch" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "typescript": "^5.9.3", + "vite": "^7.3.0" + } +} diff --git a/romhackplaza.php b/romhackplaza.php new file mode 100755 index 0000000..7fb60d7 --- /dev/null +++ b/romhackplaza.php @@ -0,0 +1,60 @@ +load(); + +} + +function _romhackplaza_plugin_activate() { + + // Verify tables. + \RomhackPlaza\Extenders\Post\Hashes_Table::verify_table(); + \RomhackPlaza\Extenders\User_Notifications::verify_table(); + +} + +_romhackplaza_define_constants(); +_romhackplaza_read_env(); + +register_activation_hook( __FILE__, '_romhackplaza_plugin_activate' ); + +use RomhackPlaza\Plugin; + +$_romhackplaza = new Plugin() + ->setup_extenders_direct(); + +do_action( 'RomhackPlaza\\Init' ); \ No newline at end of file diff --git a/src/API/File_Server.php b/src/API/File_Server.php new file mode 100644 index 0000000..34cd893 --- /dev/null +++ b/src/API/File_Server.php @@ -0,0 +1,93 @@ + "servers/take-a-random-server.php?return_type=%s", + 'server-by-id' => "servers/get-server-by-id.php?server_id=%s", + ]; + + private(set) public string $server_url; + private(set) public int $server_id; + + public function __construct( int $post_id = 0, bool $get_a_server = true ) { + + if( $get_a_server ){ + $resp = self::get_favorite_server( $post_id ); + if( $resp !== null ) { + $this->server_url = $resp[0]; + $this->server_id = intval($resp[1]); + } else { + $this->server_url = ""; + $this->server_id = -1; + } + } + + } + + public function print_js(){ + + echo sprintf( '', $this->server_url, $this->server_id ); + + } + + public function print(){ + + echo $this->server_url . "|" . $this->server_id; + + } + + public static function build_url( string $endpoint, ...$args ){ + + return sprintf( $_ENV['ROMHACKPLAZA_API_URL'] . $endpoint, ...$args ); + + } + + public static function get_favorite_server( int $post_id ){ + + if( $post_id == 0 ) + return self::get_a_random_server(); + + $favorite_server = get_post_meta( $post_id, 'favorite_server', true ) ?? false; + $favorite_server_timestamp = get_post_meta( $post_id, 'favorite_server_timestamp', true ) ?? false; + + if( $favorite_server === false || $favorite_server_timestamp === false ) + return self::get_a_random_server(); + + $time = time() - intval( $favorite_server_timestamp ); + if( $time > self::FAV_SERVER_NEED_CHANGE ) + return self::get_a_random_server(); + + $server_url = wp_remote_get( self::build_url( self::ENDPOINTS['server-by-id'], $favorite_server ) )['body']; + return [ $server_url, $favorite_server ]; + + } + + public static function get_a_random_server( $return_type = "PHP" ){ + + switch( $return_type ) { + case "PHP": + case "JS": + break; + default: + $return_type = "RAW"; + break; + } + + $resp = wp_remote_get( self::build_url( self::ENDPOINTS['random-server'], $return_type ) ); + if( !is_wp_error( $resp ) ) { + if ($return_type === "PHP") + return explode("|", $resp['body']); + else + echo $resp['body']; + } + return null; + + } + +} \ No newline at end of file diff --git a/src/API/Website_Scripts.php b/src/API/Website_Scripts.php new file mode 100644 index 0000000..d95a054 --- /dev/null +++ b/src/API/Website_Scripts.php @@ -0,0 +1,14 @@ +get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table_name ) ) === $table_name; + + } + +} \ No newline at end of file diff --git a/src/Extenders/Abstract_Extender.php b/src/Extenders/Abstract_Extender.php new file mode 100644 index 0000000..51739b1 --- /dev/null +++ b/src/Extenders/Abstract_Extender.php @@ -0,0 +1,57 @@ +can_extend() ) + return; + + $this->extend(); + + } + + /** + * Alias for Wordpress function. + * @param $name + * @param $callback + * @param $priority + * @param $accepted_args + * @return void + */ + protected function add_filter( $name, $callback, $priority = 10, $accepted_args = 1 ) { + add_filter( $name, $callback, $priority, $accepted_args ); + } + + /** + * Alias for Wordpress function. + * @param $name + * @param $callback + * @param $priority + * @param $accepted_args + * @return void + */ + protected function add_action( $name, $callback, $priority = 10, $accepted_args = 1 ) { + add_action( $name, $callback, $priority, $accepted_args ); + } + + /** + * Check if the extender can be launched. + * @return bool + */ + abstract protected function can_extend(): bool; + + /** + * The Extender. + * @return void + */ + abstract protected function extend(): void; + +} \ No newline at end of file diff --git a/src/Extenders/Admin/Dashboard.php b/src/Extenders/Admin/Dashboard.php new file mode 100644 index 0000000..145ce1e --- /dev/null +++ b/src/Extenders/Admin/Dashboard.php @@ -0,0 +1,43 @@ +add_action( 'wp_dashboard_setup', [ $this, 'remove_meta_boxes' ] ); + $this->add_action( 'wp_dashboard_setup', [ $this, 'add_meta_boxes' ] ); + + } + + public function remove_meta_boxes(): void { + + remove_meta_box('dashboard_primary', 'dashboard', 'side'); // WordPress Events and News + remove_meta_box('dashboard_quick_press', 'dashboard', 'side'); // Quick Draft + remove_meta_box('dashboard_right_now', 'dashboard', 'normal'); // At a Glance + + } + + public function add_meta_boxes(): void { + + $this->children['content_stats'] = new Meta_Boxes\Content_Stats(); + $this->children['rhpz-url'] = new Meta_Boxes\RHPZ_Url(); + + } + +} diff --git a/src/Extenders/Admin/List_Table/Notifications_List.php b/src/Extenders/Admin/List_Table/Notifications_List.php new file mode 100644 index 0000000..003d93f --- /dev/null +++ b/src/Extenders/Admin/List_Table/Notifications_List.php @@ -0,0 +1,189 @@ + '', + 'sender' => __('Sender', "romhackplaza"), + 'recipient' => __('Recipient', "romhackplaza"), + 'type' => __('Type', "romhackplaza"), + 'content' => __('Content', "romhackplaza"), + 'auto' => __('Auto', "romhackplaza"), + 'readed' => __('Readed', "romhackplaza"), + 'date' => __('Date', "romhackplaza") + ]; + + return $columns; + + } + + public function prepare_items() + { + + if ( isset($_POST['s']) ) { + $this->table_data = $this->get_table_data($_POST['s']); + } else { + $this->table_data = $this->get_table_data(); + } + + $columns = $this->get_columns(); + $hidden = array(); + $sortable = $this->get_sortable_columns(); + $primary = 'id'; + $this->_column_headers = array($columns, $hidden, $sortable); + + usort($this->table_data, array(&$this, 'usort_reorder')); + + /* pagination */ + $per_page = 100; + $current_page = $this->get_pagenum(); + $total_items = count($this->table_data); + + $this->table_data = array_slice($this->table_data, (($current_page - 1) * $per_page), $per_page); + + $this->set_pagination_args(array( + 'total_items' => $total_items, // total number of items + 'per_page' => $per_page, // items to show on a page + 'total_pages' => ceil( $total_items / $per_page ) // use ceil to round up + )); + + $this->items = $this->table_data; + + } + + private function get_table_data( $search = '' ) { + + global $wpdb; + $table_name = $wpdb->prefix . User_Notifications::TABLE_NAME; + + if( ! empty( $search ) ) + return $wpdb->get_results( + "SELECT * FROM {$table_name} WHERE id LIKE '%{$search}%' OR sender LIKE '%{$search}%' OR recipient LIKE '%{$search}%'", ARRAY_A + ); + return $wpdb->get_results( + "SELECT * FROM {$table_name}", ARRAY_A + ); + + } + + public function column_default( $item, $column_name ) { + + switch ( $column_name ) { + case 'id': + case 'sender': + case 'recipient': + case 'type': + case 'content': + case 'auto': + case 'readed': + case 'date': + default: + return $item[ $column_name ]; + } + + } + + public function column_cb( $item ) { + return sprintf( + '', $item['id'] + ); + } + + protected function get_sortable_columns() + { + $sortable = [ + 'id' => array('id', false), + 'date' => array('date', false), + ]; + return $sortable; + } + + public function usort_reorder( $a, $b ) { + + // If no sort, default to user_login + $orderby = (!empty($_GET['orderby'])) ? $_GET['orderby'] : 'id'; + + // If no order, default to asc + $order = (!empty($_GET['order'])) ? $_GET['order'] : 'desc'; + + // Determine sort order + $result = strcmp($a[$orderby], $b[$orderby]); + + // Send final sort direction to usort + return ($order === 'asc') ? $result : -$result; + + } + + public function column_id( $item ) { + + $orderby = sanitize_text_field( $_GET['orderby'] ) ?? "id"; + $order = sanitize_text_field( $_GET['order'] ) ?? 'desc'; + + $callback = admin_url( sprintf( "admin.php?page=%s&orderby=%s&order=%s", $_REQUEST['page'], $orderby, $order ) ); + $delete_url = add_query_arg( [ + 'id' => $item['id'], + '_wpnonce' => wp_create_nonce( Format::format_nonce_name( 'delete-notification-' . $item['id'] ) ), + 'callback' => $callback, + ], + Website_Scripts::build_url( User_Notifications::website_scripts_list()['delete'] ) + ); + + $actions = [ + 'delete' => sprintf( '%s', $delete_url, __( 'Delete', 'romhackplaza' ) ), + ]; + + return sprintf( "%s %s", $item['id'], $this->row_actions( $actions ) ); + + } + + public function column_sender( $item ) { + + $user_name = get_the_author_meta( 'display_name', $item['sender'] ) ?? "Unknown"; + return sprintf( "%s (%s)", $item['sender'], $user_name ); + + } + + public function column_recipient( $item ) { + + $user_name = get_the_author_meta( 'display_name', $item['recipient'] ) ?? "Unknown"; + return sprintf( "%s (%s)", $item['recipient'], $user_name ); + + } + + public function column_type( $item ) { + + return User_Notifications::type_name( $item['type'] ); + + } + + public function column_auto( $item ) { + + return $item['auto'] == 1 ? __( "Yes", 'romhackplaza') : __( "No", 'romhackplaza' ); + + } + + public function column_readed( $item ) { + + return $item['readed'] == 1 ? __( "Yes", 'romhackplaza') : __( "No", 'romhackplaza' ); + + } + + public function column_date( $item ) { + + return wp_date( get_option( 'date_format' ), $item['date'] ); + + } + +} \ No newline at end of file diff --git a/src/Extenders/Admin/Meta_Boxes/Abstract_Meta_Box.php b/src/Extenders/Admin/Meta_Boxes/Abstract_Meta_Box.php new file mode 100644 index 0000000..03aafbf --- /dev/null +++ b/src/Extenders/Admin/Meta_Boxes/Abstract_Meta_Box.php @@ -0,0 +1,109 @@ +name = $name; + $this->key = $key; + $this->location = $location; + + if( $this->can_register() ) { + $this->prepare_filters(); + $this->register(); + } + + } + + /** + * If the meta box can be registered or not. + * @return bool + */ + protected function can_register(): bool { + return true; + } + + /** + * Prepare filters "Before content" and "After content" if needed. + * @return void + */ + protected function prepare_filters(): void { + return; + } + + /** + * Register the meta box at the correct location. + * @return void + */ + final public function register(): void { + + switch( $this->location ){ + case Meta_Box_Location::Dashboard: + wp_add_dashboard_widget( $this->name, $this->key, [$this, 'pre_render'] ); + } + + } + + /** + * Prepare render content. + * @return void + */ + public function pre_render(): void { + + $data = []; + + /** + * Add data that need to be loaded before the content. + * + * @param array - Data array. + * @return array + */ + $data = \apply_filters( "RomhackPlaza\\Extenders\\Admin\\Meta_Boxes\\{$this->name}\\Before_Content", $data ); + + $this->content( $data ); + + /** + * Declare data that need to be loaded after the meta box content. + * + * @param array - Data array. + */ + do_action( "RomhackPlaza\\Extenders\\Admin\\Meta_Boxes\\{$this->name}\\After_Content", $data ); + + } + + /** + * Content of the meta box. Can be done with Timber. + * /!\ Verify that Timber is available. + * @param array $data + * @return void + */ + abstract protected function content( array $data = [] ): void; + +} \ No newline at end of file diff --git a/src/Extenders/Admin/Meta_Boxes/Content_Stats.php b/src/Extenders/Admin/Meta_Boxes/Content_Stats.php new file mode 100644 index 0000000..b36c94c --- /dev/null +++ b/src/Extenders/Admin/Meta_Boxes/Content_Stats.php @@ -0,0 +1,183 @@ +name}\\Before_Content", [ $this, 'prepare_data' ] ); + add_action( "RomhackPlaza\\Extenders\\Admin\\Meta_Boxes\\{$this->name}\\After_Content", [ $this, 'load_js' ] ); + + } + + private function post_count_per_days( int $days, array $post_types ) { + + if( $days > 31 || $days < 0 ) + $days = 7; + + // Default value foreach days. + $post_counts = []; + foreach( $post_types as $a_post_type ){ + + $post_counts[ $a_post_type ] = array_fill( 0, $days, 0 ); + + } + + for( $i = $days - 1; $i >= 0; $i-- ){ + + $date_query = [ + + 'after' => date('Y-m-d', strtotime("-$i days")), + 'before' => date('Y-m-d', strtotime("-" . ($i - 1) . " days")), + 'inclusive' => true, + + ]; + + foreach( $post_types as $a_post_type ){ + + $wp_query_args = [ + + 'post_type' => $a_post_type, + 'date_query' => $date_query, + 'posts_per_page' => -1 + + ]; + + $wp_query = new \WP_Query( $wp_query_args ); + $post_counts[ $a_post_type ][ $days - 1 - $i ]= $wp_query->found_posts; + + } + } + + return $post_counts; + + } + + public function prepare_data( array $data = [] ) { + + global $_romhackplaza; + $primary_cpt = $_romhackplaza->cpt->primary_cpt(); + + $count_week = $this->post_count_per_days( 7, $primary_cpt ); + $count_month = $this->post_count_per_days( 30, $primary_cpt ); + + return [ + 'week' => json_encode( $count_week ), + 'month' => json_encode( $count_month ), + ]; + + } + + /** + * Load JS needed for this widget. + * @return void + */ + public function load_js( array $data = [] ) { + + ?> + + + + + +
+ +
+ +
+ +
+ + +
+ + + +
+
+ + true, 'in_footer' => true ] + ) + ->enqueue() + ->add_localize( + '_romhackplaza_rhpz_url', + [ 'create_url' => self::RHPZ_CREATE_URL ] + ); + + } + +} \ No newline at end of file diff --git a/src/Extenders/Admin/Pages.php b/src/Extenders/Admin/Pages.php new file mode 100644 index 0000000..ec4b23b --- /dev/null +++ b/src/Extenders/Admin/Pages.php @@ -0,0 +1,37 @@ +children['main_page'] = new Pages\RomhackPlaza_Main(); + $this->children['settings_page'] = new Pages\Settings(); + + if( is_admin() ){ + + if( !class_exists( 'WP_List_Table' ) ) + require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php'; + + } + + $this->children['notifications_page'] = new Pages\Notifications_List(); + $this->children['send_notification_page'] = new Pages\Send_Notification(); + $this->children['admin_scripts_page'] = new Pages\Admin_Scripts(); + + } + +} \ No newline at end of file diff --git a/src/Extenders/Admin/Pages/Abstract_Page.php b/src/Extenders/Admin/Pages/Abstract_Page.php new file mode 100644 index 0000000..9731b71 --- /dev/null +++ b/src/Extenders/Admin/Pages/Abstract_Page.php @@ -0,0 +1,147 @@ +page_title = $page_title; + $this->menu_title = $menu_title; + $this->page_slug = $page_slug; + $this->capability = $capability; + $this->parent_slug = $parent_slug; + $this->icon = $icon; + $this->position = $position; + + $this->prepare_filters(); + add_action( 'admin_menu', [ $this, 'add_page' ] ); + + if( method_exists( $this, 'enqueue_scripts' ) ) + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); + + } + + /** + * Prepare filters "Before content" and "After content" if needed. + * @return void + */ + protected function prepare_filters (): void { + + return; + + } + + /** + * Register page. + * @return void + */ + final public function add_page () { + + if( !is_null( $this->parent_slug ) ) + add_submenu_page( + $this->parent_slug, + $this->page_title, + $this->menu_title, + $this->capability, + $this->page_slug, + [ $this, 'pre_content' ], + $this->position + ); + else + add_menu_page( + $this->page_title, + $this->menu_title, + $this->capability, + $this->page_slug, + [ $this, 'pre_content' ], + $this->icon, + $this->position + ); + + } + + /** + * Prepare content and execute before content and after content hooks. + * @return void + */ + public function pre_content () { + + $data = []; + + $data = \apply_filters( "RomhackPlaza\\Extenders\\Admin\\Pages\\{$this->page_slug}\\Before_Content", $data ); + $this->content( $data ); + + do_action( "RomhackPlaza\\Extenders\\Admin\\Pages\\{$this->page_slug}\\After_Content", $data ); + + } + + /** + * The content. Can be done with Timber. + * /!\ Verify that Timber is available. + * @param array $data + * @return void + */ + abstract public function content( mixed $data = [] ): void; + +} \ No newline at end of file diff --git a/src/Extenders/Admin/Pages/Admin_Scripts.php b/src/Extenders/Admin/Pages/Admin_Scripts.php new file mode 100644 index 0000000..66b1d2d --- /dev/null +++ b/src/Extenders/Admin/Pages/Admin_Scripts.php @@ -0,0 +1,58 @@ +page_slug ) + return; + + new Script( + Script_Type::JS, + 'romhackplaza-admin-scripts', + ROMHACKPLAZA_PLUGIN_URI . '/assets/js/admin/admin-scripts.js', + [], + '20260105', + args: [ 'defer' => true, 'in_footer' => true ] + ) + ->enqueue() + ->add_localize( + '_romhackplaza_admin_scripts', + [ + 'execute_url' => admin_url( 'admin-ajax.php' ), + 'execute_nonce' => wp_create_nonce( Format::format_nonce_name( 'load_admin_script' ) ) + ] + ); + } + + public function content( mixed $data = [] ): void { + + $context = Timber::context(); + $context['scripts'] = \RomhackPlaza\Extenders\Admin_Scripts::get_scripts(); + + Timber::render( '@plugin/admin/pages/admin-scripts.twig', $context ); + } + + +} \ No newline at end of file diff --git a/src/Extenders/Admin/Pages/Notifications_List.php b/src/Extenders/Admin/Pages/Notifications_List.php new file mode 100644 index 0000000..b834ea8 --- /dev/null +++ b/src/Extenders/Admin/Pages/Notifications_List.php @@ -0,0 +1,42 @@ + + +

+
+ prepare_items(); + $table->search_box( __( 'Search by ID, sender or recipient', 'romhackplaza' ), 'search_id' ); + $table->display(); + ?> +
+
+ page_slug}", array( $this, 'load_select2' ) ); + add_action( "admin_enqueue_scripts", array( $this, 'enqueue_scripts' ) ); + + } + + public function load_select2() { + + // Load select2 + ?> + + + page_slug ) + return; + + new Script( + Script_Type::JS, + 'romhackplaza-send-notification', + ROMHACKPLAZA_PLUGIN_URI . '/assets/js/admin/send-notification.js', + [ 'jquery' ], + '20260101-4', + args: [ 'defer' => true, 'in_footer' => true ] + ) + ->enqueue() + ->add_localize( + '_romhackplaza_send_notification', + [ + 'fetch_url' => admin_url( 'admin-ajax.php' ), + 'fetch_nonce' => wp_create_nonce( Format::format_nonce_name( 'search_user' ) ), + 'submit_nonce' => wp_create_nonce( Format::format_nonce_name( 'submit_notification' ) ), + ], + ); + + } + + public function content( mixed $data = [] ): void { + + $context = Timber::context(); + $context['type_list'] = User_Notifications::NOTIFICATIONS_TYPES; + + new Modal( 'notifications', "", "" )->render(); + Timber::render( '@plugin/admin/pages/send-notification.twig', $context ); + + } + +} \ No newline at end of file diff --git a/src/Extenders/Admin/Pages/Settings.php b/src/Extenders/Admin/Pages/Settings.php new file mode 100644 index 0000000..29fe0b6 --- /dev/null +++ b/src/Extenders/Admin/Pages/Settings.php @@ -0,0 +1,209 @@ +settings->get( $option_name ) ?? "" ); + + echo sprintf( '', 'romhackplaza_plugin_options', $option_name, $value ); + + } + + /* --- + START SETTINGS REGISTRATION + --- */ + + public function register_settings_sections_fields () { + + add_settings_section( + 'nsfw', + __( 'NSFW', 'romhackplaza' ), + function(){}, // Change it to add style. + 'romhackplaza-config' + ); + + add_settings_field( + 'nsfw_tag_id', + __( 'Tag ID', 'romhackplaza' ), + function(){ $this->_generic_html_input_text( 'nsfw_tag_id' ); }, + 'romhackplaza-config', + 'nsfw', + ); + + add_settings_section( + 'submissions', + __( 'Submissions', 'romhackplaza' ), + function(){ _e("All fields needs to be separated by a semi-colon WITHOUT SPACES !", 'romhackplaza' ); }, + 'romhackplaza-config' + ); + + add_settings_field( + 'complete_submissions_form_page_ids', + __( 'Complete submissions page IDs', 'romhackplaza' ), + function(){ $this->_generic_html_input_text( 'complete_submissions_form_page_ids' ); }, + 'romhackplaza-config', + 'submissions', + ); + + add_settings_field( + 'simple_submissions_form_page_ids', + __( 'Simple submissions page IDs (News)', 'romhackplaza' ), + function(){ $this->_generic_html_input_text( 'simple_submissions_form_page_ids' ); }, + 'romhackplaza-config', + 'submissions', + ); + + add_settings_field( + 'translations_acf_group', + __( 'Translations ACF Group', 'romhackplaza' ), + function(){ $this->_generic_html_input_text( 'translations_acf_group' ); }, + 'romhackplaza-config', + 'submissions', + ); + + add_settings_field( + 'romhacks_acf_group', + __( 'Romhacks ACF Group', 'romhackplaza' ), + function(){ $this->_generic_html_input_text( 'romhacks_acf_group' ); }, + 'romhackplaza-config', + 'submissions', + ); + + add_settings_field( + 'homebrew_acf_group', + __( 'Homebrew ACF Group', 'romhackplaza' ), + function(){ $this->_generic_html_input_text( 'homebrew_acf_group' ); }, + 'romhackplaza-config', + 'submissions', + ); + + add_settings_field( + 'utilities_acf_group', + __( 'Utilities ACF Group', 'romhackplaza' ), + function(){ $this->_generic_html_input_text( 'utilities_acf_group' ); }, + 'romhackplaza-config', + 'submissions', + ); + + add_settings_field( + 'documents_acf_group', + __( 'Documents ACF Group', 'romhackplaza' ), + function(){ $this->_generic_html_input_text( 'documents_acf_group' ); }, + 'romhackplaza-config', + 'submissions', + ); + + add_settings_field( + 'lua_scripts_acf_group', + __( 'LUA Scripts ACF Group', 'romhackplaza' ), + function(){ $this->_generic_html_input_text( 'lua_scripts_acf_group' ); }, + 'romhackplaza-config', + 'submissions', + ); + + add_settings_field( + 'tutorials_acf_group', + __( 'Tutorials ACF Group', 'romhackplaza' ), + function(){ $this->_generic_html_input_text( 'tutorials_acf_group' ); }, + 'romhackplaza-config', + 'submissions', + ); + + add_settings_field( + 'news_acf_group', + __( 'News ACF Group', 'romhackplaza' ), + function(){ $this->_generic_html_input_text( 'news_acf_group' ); }, + 'romhackplaza-config', + 'submissions', + ); + + add_settings_field( + 'reviews_acf_group', + __( 'Reviews ACF Group', 'romhackplaza' ), + function(){ $this->_generic_html_input_text( 'reviews_acf_group' ); }, + 'romhackplaza-config', + 'submissions', + ); + + add_settings_field( + 'public_edit_disabled_tag_id', + __( 'Public Edit Disabled Tag ID', 'romhackplaza' ), + function(){ $this->_generic_html_input_text( 'public_edit_disabled_tag_id' ); }, + 'romhackplaza-config', + 'submissions', + ); + + add_settings_section( + 'discord', + __( 'Discord', 'romhackplaza' ), + function(){}, + 'romhackplaza-config' + ); + + add_settings_field( + 'discord_webhook_romhacks_translations', + __( 'Discord webhook for Romhacks and Translations', 'romhackplaza' ), + function(){ $this->_generic_html_input_text( 'discord_webhook_romhacks_translations' ); }, + 'romhackplaza-config', + 'discord', + ); + + add_settings_field( + 'discord_webhook_global', + __( 'Discord webhook for others submissions', 'romhackplaza' ), + function(){ $this->_generic_html_input_text( 'discord_webhook_global' ); }, + 'romhackplaza-config', + 'discord', + ); + + } + + /* --- + END SETTINGS REGISTRATION + --- */ + + public function content( mixed $data = [] ): void { + + ?> +
+

+
+ + + +
+
+ add_action( 'acf/save_post', [ $this, 'bind_post_parent_attachment_from_gallery' ] ); + + } + + /** + * Add post_parent for attachments added in a ACF gallery element. + * Redirect to save_post class. + * @see Post\Save_Post::bind_post_parent_attachment_from_gallery() + * @param $post_id + * @return void + */ + public function bind_post_parent_attachment_from_gallery( $post_id ) { + + global $_romhackplaza; + if( isset( $_romhackplaza->children['extend_save_post'] ) ) // Post\Save_Post. + return $_romhackplaza->children['extend_save_post']->bind_post_parent_attachment_from_gallery( $post_id ); + + } + +} \ No newline at end of file diff --git a/src/Extenders/Ajax/Abstract_Ajax.php b/src/Extenders/Ajax/Abstract_Ajax.php new file mode 100644 index 0000000..d4eb31e --- /dev/null +++ b/src/Extenders/Ajax/Abstract_Ajax.php @@ -0,0 +1,40 @@ +request_name = $request_name; + $this->public = $public; + $this->nonce_field = $nonce_field; + + if( $this->public ) + add_action('wp_ajax_no_priv_' . $this->request_name, [$this, 'pre_handle'] ); + + add_action( 'wp_ajax_' . $this->request_name, [$this, 'pre_handle'] ); + + } + + public function pre_handle() { + + check_ajax_referer( Format::format_nonce_name( $this->request_name ) ); + $this->handle(); + + } + + abstract protected function handle(); + +} \ No newline at end of file diff --git a/src/Extenders/Ajax/Load_Admin_Script.php b/src/Extenders/Ajax/Load_Admin_Script.php new file mode 100644 index 0000000..d5c1ce8 --- /dev/null +++ b/src/Extenders/Ajax/Load_Admin_Script.php @@ -0,0 +1,33 @@ +user_roles->current_upper() != Roles::Administrator ) + wp_send_json_error( [ 'message' => 'You don\'t have permission to execute an admin script.' ] ); + + define( 'ROMHACKPLAZA_ADMIN_SCRIPT', true ); + $script_name = isset( $_POST['script'] ) ? sanitize_text_field( wp_unslash( $_POST['script'] ) ) : ''; + + if( !Admin_Scripts::execute( $script_name ) ) + wp_send_json_error( [ 'message' => 'An error occured while executing your script.' ] ); + } + +} \ No newline at end of file diff --git a/src/Extenders/Ajax/Notifications_Contact.php b/src/Extenders/Ajax/Notifications_Contact.php new file mode 100644 index 0000000..5217b4d --- /dev/null +++ b/src/Extenders/Ajax/Notifications_Contact.php @@ -0,0 +1,40 @@ + "Wrong content or to." ] ); + + Notif::add([ + 'sender' => get_current_user_id(), + 'recipient' => $to, + 'type' => 'message', + 'content' => $content, + 'auto' => 0, + 'readed' => 0, + 'date' => time() + ] ); + wp_send_json_success( [ 'message' => "Good." ] ); + + } +} \ No newline at end of file diff --git a/src/Extenders/Ajax/Notifications_Reply.php b/src/Extenders/Ajax/Notifications_Reply.php new file mode 100644 index 0000000..ec95817 --- /dev/null +++ b/src/Extenders/Ajax/Notifications_Reply.php @@ -0,0 +1,52 @@ + "Wrong notification id" ] ); + + $notification = Notif::get_by_id( $notification_id ); + if( absint( $notification['recipient'] ) !== get_current_user_id() ) // It's not you. + wp_send_json_error( [ 'message' => "Wrong recipient" ] ); + + // Message copy. + Notif::add( [ + 'sender' => $notification['recipient'], + 'recipient' => $notification['recipient'], + 'type' => 'copy_message', + 'content' => $content, + 'auto' => 1, + 'readed' => 1, + 'date' => time() + ] ); + + Notif::add( [ + 'sender' => $notification['recipient'], + 'recipient' => $notification['sender'], + 'type' => 'reply_message', + 'content' => $content, + 'auto' => 0, + 'readed' => 0, + 'date' => time() + ] ); + + wp_send_json_success( [ 'message' => 'Good' ] ); + } + +} \ No newline at end of file diff --git a/src/Extenders/Ajax/Notifications_Unread_To_Read.php b/src/Extenders/Ajax/Notifications_Unread_To_Read.php new file mode 100644 index 0000000..db691ab --- /dev/null +++ b/src/Extenders/Ajax/Notifications_Unread_To_Read.php @@ -0,0 +1,34 @@ + "Wrong notification ID" ] ); + + $notification = Notif::get_by_id( $notification_id ); + if( absint( $notification['recipient'] ) !== get_current_user_id() ) // It's not you. + wp_send_json_error( [ 'message' => "Wrong recipient" ] ); + + Notif::unread_to_read( $notification_id ); + wp_send_json_success( ['message' => 'Good' ] ); + + } + +} \ No newline at end of file diff --git a/src/Extenders/Ajax/Reserve_Post_ID.php b/src/Extenders/Ajax/Reserve_Post_ID.php new file mode 100644 index 0000000..90a704e --- /dev/null +++ b/src/Extenders/Ajax/Reserve_Post_ID.php @@ -0,0 +1,38 @@ + "Temporary Title", + 'post_content' => "", + 'post_status' => 'auto-draft', + 'post_author' => get_current_user_id(), + 'post_type' => $post_type, + ]; + + $entry_id = wp_insert_post( $entry ); + + if( $entry_id && !is_wp_error( $entry_id ) ) + wp_send_json_success( [ 'post_id' => $entry_id, 'message' => 'good' ] ); + else + wp_send_json_error( [ 'post_id' => 0, 'message' => 'Unable to reserve post ID' ] ); + + } + +} \ No newline at end of file diff --git a/src/Extenders/Ajax/Search_Author_Term.php b/src/Extenders/Ajax/Search_Author_Term.php new file mode 100644 index 0000000..782ad7d --- /dev/null +++ b/src/Extenders/Ajax/Search_Author_Term.php @@ -0,0 +1,30 @@ + 0, 'message' => 'Unable to get Post ID and/or favorite server ID for update the favorite server.'] ); + + update_post_meta( $post_id, 'favorite_server', $favorite_server_id ); + update_post_meta( $post_id, 'favorite_server_timestamp', time() ); + + if( function_exists( 'aal_insert_log' ) ) // Log function. + // If there is a favorite server, every files are updated. + aal_insert_log( [ + 'action' => 'file_update', + 'object_type' => 'Posts', + 'object_subtype' => get_post_type( $post_id ), + 'object_id' => $post_id, + 'object_name' => get_the_title( $post_id ), + ] ); + + wp_send_json_success( [ 'message' => 'good', 'post_id' => $post_id ] ); + + } + +} \ No newline at end of file diff --git a/src/Extenders/Ajax/Submissions_Save_Entry.php b/src/Extenders/Ajax/Submissions_Save_Entry.php new file mode 100644 index 0000000..2c3c152 --- /dev/null +++ b/src/Extenders/Ajax/Submissions_Save_Entry.php @@ -0,0 +1,685 @@ + 'error', + 'action' => 'none', + 'message' => $error + ]); + wp_die(); + } + + protected function handle() { + + global $_romhackplaza; + $this->entry = new StdClass; + + // Edit mode or create mode. + $this->edit = isset( $_POST['post_id'] ) && intval( $_POST['post_id'] ) > 0; + + // Get previous post if edit mode. + if( $this->edit ) + $this->find_previous_post(); // $this->post_object + + // Check primary permissions. (Author, not Read-Only or staff) + $this->check_permissions(); + + // Get post type. + $this->entry->post_type = isset( $_POST['post_type'] ) ? sanitize_text_field( $_POST['post_type'] ) : 'post'; + if( !in_array( $this->entry->post_type, $_romhackplaza->cpt->primary_cpt() ) ) + $this->stop_error( 'Invalid post type provided.'); + + // Bind some checks. + $this->entry->save_to_drafts = isset( $_POST['saveToDrafts'] ) && $_POST['saveToDrafts'] == 'yes'; + $this->entry->keep_current_status = isset( $_POST['keepCurrentStatus'] ) && $_POST['keepCurrentStatus'] == 'yes'; + $this->entry->nsfw = isset( $_POST['nsfwEntry'] ) && $_POST['nsfwEntry'] == 'yes'; + $this->entry->is_a_tutorial = $this->entry->post_type == 'tutorials'; + + // Check post status. + $this->entry->post_status = 'draft'; // Default choice. + $this->determine_post_status(); // $this->entry->post_status. + + // Prepare Game term search. + $this->entry->game_title = isset( $_POST['gameTitle'] ) ? sanitize_text_field( $_POST['gameTitle'] ) : ''; + $this->entry->game_title = $this->entry->game_title |> Save_Post::edit_minor_words_in_title( ... ); + + // Get game term or create it. + if( !empty( $this->entry->game_title ) ) + $this->create_or_get_game_term(); // $this->entry->game_id|game_title + + // Get languages terms. + $this->get_language_terms(); + + // Get platform term. + $this->get_platform_term(); + + // Get genre ID. + $this->entry->genre_id = isset( $_POST['genre'] ) ? intval( $_POST['genre'] ) : 0; + + // Build entry title. + $this->build_entry_title(); + + // Tutorial specific fields. + if( $this->entry->is_a_tutorial ){ + $this->entry->tutorial_content = $_POST['postContent'] |> Save_Post::header_and_edited_content_for_tutorial(...); + $this->entry->post_content = $this->entry->tutorial_content["content"]; + } else { + $this->entry->tutorial_content = []; + $this->entry->post_content = $_POST['postContent']; + } + + // Reserved post ID or not. + $reserved_post_id_flag = isset( $_POST['reserved_post_id'] ) && intval( $_POST['reserved_post_id'] ) > 0; + + // Flags. + $this->entry->version_updated = false; // Flag for new update. + $this->entry->creation = true; + + // Save the entry !!! + if( $this->edit ) + $this->edit_an_entry(); + elseif( !$reserved_post_id_flag ) + $this->create_an_entry(); + else + $this->stop_error( "No reserved post ID provided." ); + + if( !$this->entry->ID || is_wp_error( $this->entry->ID ) ){ + $this->stop_error( "The post could not be created/edited." ); + } + + // Save meta values. + $this->save_meta_values(); + $this->index_entry(); + + echo json_encode( [ + 'status' => 'success', + 'action' => ( $this->edit ? 'edit' : 'submit' ), + 'message' => "" + ]); + wp_die(); + + } + + /** + * Create and register game term if needed. + * $entry->game_id : Term ID. + * $entry->game_title : Term title or old title if not changed. + * @return void + */ + protected function create_or_get_game_term() { + + // Try by ID and term name. + $term = get_term_by( 'id', $this->entry->game_title, 'game' ) ?: get_term_by( 'name', $this->entry->game_title, 'game' ); + + if( $term ){ + + $this->entry->game_title = $term->name; + $this->entry->game_id = $term->term_id; + + } else { + + // Term creation. + $term = wp_insert_term( $this->entry->game_title, 'game' ); + if( !is_wp_error( $term ) ) + $this->entry->game_id = $term['term_id']; + else + $this->entry->game_title = ''; // Error or no game. + + } + } + + /** + * Get each languages names and bind it into $this->entry + * $entry->language_ids : Term IDs + * $entry->language_titles : Term titles. + * @return void + */ + protected function get_language_terms() { + + $this->entry->language_ids = isset( $_POST['language'] ) ? array_map( 'intval', (array) $_POST['language'] ) : []; + $this->entry->language_titles = []; + + foreach( $this->entry->language_ids as $language_id ) { + + $language_term = get_term_by( 'id', $language_id, 'language' ); + if( $language_term && !is_wp_error( $language_term ) ) + $this->entry->language_titles[] = $language_term->name; + + } + + } + + /** + * Get platform for the current entry. + * $entry->platform_id : Term ID + * $entry->platform_title : Term title + * @return void + */ + protected function get_platform_term() { + + $this->entry->platform_id = isset( $_POST['platform'] ) ? intval( $_POST['platform'] ) : 0; + $this->entry->platform_title = ""; + + $platform_term = get_term_by( 'id', $this->entry->platform_id, 'platform' ); + if( $platform_term && !is_wp_error( $platform_term ) ) { + $this->entry->platform_title = $platform_term->name; + $this->entry->platform_short_title = Save_Post::get_short_platform_name( $this->entry->platform_title ); + } + + } + + /** + * Find and register previous post ID. + * $entry->ID : Entry ID + * $entry->post_object : WP_Post entry. + * @return void + */ + protected function find_previous_post() { + + $this->entry->ID = intval( $_POST['post_id'] ); + $this->post_object = get_post( $this->entry->ID ); + + if( !$this->post_object ) + $this->stop_error( 'Post not found.' ); + + } + + /** + * Check if a user can submit a content. + * If it's a subscriber, no. + * If it's not the previous author, no. + * @return void + */ + protected function check_permissions() { + + global $_romhackplaza; + + if( $this->edit && $this->post_object ){ + + if( get_current_user_id() != $this->post_object->post_author && !$_romhackplaza->user_roles->current_is_staff() ) // Not the author or not a staff member. + $this->stop_error( 'You are not allowed to edit this post.' ); + + } + + if( $_romhackplaza->user_roles->current_is( Roles::Read_Only ) ) + $this->stop_error( 'You are not allowed to post an entry.' ); + } + + /** + * Determine with post type, user main role and NSFW checks what is the best status for this entry. + * $entry->post_status. + * @return void + */ + protected function determine_post_status() { + + global $_romhackplaza; + + if( $this->edit && $this->entry->keep_current_status ){ + $this->entry->post_status = $this->post_object->post_status ?? 'draft'; // Don't change post status. + return; + } + + if( $this->entry->save_to_drafts ){ // Mandatory to be a draft. + $this->entry->post_status = 'draft'; + return; + } + + if( $this->edit ){ + + if( $this->entry->nsfw || $this->entry->is_a_tutorial ){ + + // Staff user -> Published in any case. + if( $_romhackplaza->user_roles->current_is_staff() ){ + $this->entry->post_status = 'publish'; + return; + } + + // Normal user with published post -> Keep published. + if( + ( $_romhackplaza->user_roles->current_is( Roles::Verified ) || $_romhackplaza->user_roles->current_is( Roles::Member ) ) + && $this->post_object == 'publish' + ){ + $this->entry->post_status = 'publish'; + return; + } + + // Normal user with non-published post -> Go into the queue. + if( + ( $_romhackplaza->user_roles->current_is( Roles::Verified ) || $_romhackplaza->user_roles->current_is( Roles::Member ) ) + && $this->post_object != 'publish' + ){ + $this->entry->post_status = 'pending'; + return; + } + + // Error handle. + $this->entry->post_status = 'draft'; + return; + + } else { + + // Staff or verified user -> Published in any case. + if( $_romhackplaza->user_roles->current_is_staff() || $_romhackplaza->user_roles->current_is( Roles::Verified ) ){ + $this->entry->post_status = 'publish'; + return; + } + + // Normal user with published post -> Keep published. + if( $_romhackplaza->user_roles->current_is( Roles::Member ) && $this->post_object == 'publish' ){ + $this->entry->post_status = 'publish'; + return; + } + + // Normal user with non-published post -> Go into the queue. + if( $_romhackplaza->user_roles->current_is( Roles::Member ) && $this->post_object != 'publish' ){ + $this->entry->post_status = 'pending'; + return; + } + + // Error handle. + $this->entry->post_status = 'draft'; + return; + } + + } else { // Creation mode. + + if( $this->entry->nsfw || $this->entry->is_a_tutorial ){ + + // Staff user -> Published in any case. + if( $_romhackplaza->user_roles->current_is_staff() ){ + $this->entry->post_status = 'publish'; + return; + } + + // Normal user -> Go into the queue. + if( + ( $_romhackplaza->user_roles->current_is( Roles::Verified ) || $_romhackplaza->user_roles->current_is( Roles::Member ) ) + ){ + $this->entry->post_status = 'pending'; + return; + } + + // Error handle. + $this->entry->post_status = 'draft'; + return; + + } else { + + // Staff or verified user -> Published in any case. + if( $_romhackplaza->user_roles->current_is_staff() || $_romhackplaza->user_roles->current_is( Roles::Verified ) ){ + $this->entry->post_status = 'publish'; + return; + } + + // Normal user -> Go into the queue. + if( $_romhackplaza->user_roles->current_is( Roles::Member ) ) { + $this->entry->post_status = 'pending'; + return; + } + + // Error handle. + $this->entry->post_status = 'draft'; + return; + + } + + } + + } + + /** + * Build primary post title + * $entry->post_title + * @return void + */ + protected function build_entry_title(){ + + $entry_title = ""; + if( isset( $_POST['entryTitle'] ) ) + $entry_title = $_POST['entryTitle'] |> sanitize_text_field(...) |> Save_Post::edit_minor_words_in_title(...); + + $language_str = !empty( $this->entry->language_titles ) ? implode( ', ', $this->entry->language_titles ) : ''; + + if( $this->entry->post_type === 'translations' && $entry_title != '' ){ + $this->entry->post_title = sprintf( '%s (%s Translation) %s', $entry_title, $language_str, $this->entry->platform_short_name ); + } + else if( $this->entry->post_type === 'translations' ){ + $this->entry->post_title = sprintf( '%s (%s Translation) %s', $this->entry->game_title, $language_str, $this->entry->platform_short_name ); + } + else if( $this->entry->post_type === 'homebrew' ){ + $this->entry->post_title = sprintf( '%s (%s) Homebrew', $this->entry->game_title, $this->entry->platform_short_name ); + } + else if( $this->entry->post_type === 'romhacks' ){ + $this->entry->post_title = sprintf('%s (%s) Romhack', $entry_title, $this->entry->platform_short_name ); + } + else if( $this->entry->post_type === 'lua-scripts' ){ + $this->entry->post_title = sprintf( '%s (%s) LUA Script', $entry_title, $this->entry->platform_short_name ); + } + else if( $this->entry->post_type === 'utilities' ){ + $this->entry->post_title = sprintf( '%s - Utility', $entry_title ); + } + else if( $this->entry->post_type === 'documents' ){ + $this->entry->post_title = sprintf( '%s - Document', $entry_title ); + } + else if( $this->entry->post_type === 'tutorials' ){ + $this->entry->post_title = sprintf( '%s - Tutorial', $entry_title ); + } + else { + $this->entry->post_title = $entry_title; + } + + } + + protected function create_an_entry(){ + + $post_data_for_wp = [ + 'ID' => intval( $_POST['reserved_post_id'] ), + 'post_title' => sanitize_text_field( $this->entry->post_title ), + 'post_content' => wp_kses_post( $this->entry->post_content ), + 'post_status' => $this->entry->post_status, + 'post_type' => $this->entry->post_type, + 'comment_status' => 'open', + ]; + + $this->entry->creation = true; // We are in create mode. + $this->entry->ID = wp_insert_post( $post_data_for_wp ); + + // TODO: Cache + + } + + protected function edit_an_entry(){ + + $post_name = isset( $_POST['entrySlug'] ) && !empty( $_POST['entrySlug'] ) ? sanitize_title( $_POST['entrySlug'] ) : sanitize_title( $this->entry->post_title ); + + $post_data_for_wp = [ + 'ID' => intval( $this->entry->ID ), + 'post_title' => sanitize_text_field( $this->entry->post_title ), + 'post_content' => wp_kses_post( $this->entry->post_content ), + 'post_name' => $post_name, + 'post_status' => $this->entry->post_status, + 'post_type' => $this->entry->post_type, + 'comment_status' => 'open', + ]; + + if( $this->entry->post_type !== 'news' && !$this->entry->is_a_tutorial ){ + + // The version number changed or not ? + $current_version_number = get_field( 'version_number', $this->entry->ID ); + $new_version_number = isset( $_POST['versionNumber' ] ) ? sanitize_text_field( $_POST['versionNumber'] ) : ''; + + if( $current_version_number === false || $current_version_number === null || $current_version_number !== $new_version_number ){ + // The version number changed. + $current_time = current_time( 'mysql' ); + $post_data_for_wp['post_date'] = $current_time; + $post_data_for_wp['post_date_gmt'] = get_gmt_from_date($current_time); + $this->entry->version_updated = true; + // TODO : Cache + } + + } + + $this->entry->creation = false; // We are in edit mode. + $this->entry->ID = wp_update_post( $post_data_for_wp ); + + } + + protected function create_and_set_authors(){ + + $authors = isset( $_POST['author-name'] ) && is_array( $_POST['author-name'] ) ? $_POST['author-name'] : []; + wp_set_object_terms( $this->entry->ID, [], 'author-name' ); // Reset authors. + + foreach ( $authors as $author ) { + if( is_numeric( $author ) ) // Already exists. + wp_set_object_terms( $this->entry->ID, intval( $author ), 'author-name', true ); + else { + $term = wp_insert_term( $author, 'author-name' ); // Create term. + if( !is_wp_error( $term ) && isset( $term['term_id'] ) ) + wp_set_object_terms( $this->entry->ID, $term['term_id'], 'author-name', true ); + } + } + } + + protected function save_meta_values(){ + + global $_romhackplaza; + + // Discord noticed field. + $discord_noticed = get_post_meta( $this->entry->ID, 'discord_noticed', true ); + + $discord_new_post_flag = $this->entry->post_status === 'publish' && ( + $discord_noticed === "" || $discord_noticed === false || $discord_noticed === "no" + ) && $this->entry->post_type !== 'news'; + $discord_update_post_flag = $this->entry->post_status === 'publish' && $this->entry->version_updated && $this->entry->post_type !== 'news'; + + // ACF specific fields saving. Gallery + Featured image. + if( function_exists( 'acf_save_post' ) ) + acf_save_post( $this->entry->ID ); + + // Custom featured image field. + $featured_image_id = get_field( 'custom_featured_image', $this->entry->ID ); + if( $featured_image_id ) + set_post_thumbnail( $this->entry->ID, $featured_image_id ); // Set it as post thumbnail. + + // Version number field + if( isset( $_POST['versionNumber'] ) ) + update_field( 'version_number', sanitize_text_field( $_POST['versionNumber'] ), $this->entry->ID ); + + // Entry title field + if( isset( $_POST['entryTitle'] ) ) + update_field( 'entry_title', Save_Post::edit_minor_words_in_title( sanitize_text_field( $_POST['entryTitle'] ) ), $this->entry->ID ); + + // Release date field + if( isset( $_POST['release_date'] ) ){ + + $date = \DateTime::createFromFormat( 'Y-m-d', sanitize_text_field( $_POST['release_date'] ) ); + if( $date ) + update_field( 'release_date', $date->format( 'Ymd' ), $this->entry->ID ); + + } + + // Project link field + if( isset( $_POST['release_site'] ) ) + update_field( 'release_site', esc_url_raw( $_POST['release_site'] ), $this->entry->ID ); + + // YouTube video field + if( isset( $_POST['youtube_video'] ) ) + update_field( 'youtube_video', esc_url_raw( $_POST['youtube_video'] ), $this->entry->ID ); + + // For News. Local Plaza project page link field. + if( isset( $_POST['romhacks_page'] ) ) + update_field( 'romhacks_page', esc_url_raw( $_POST['romhacks_page'] ), $this->entry->ID ); + + // Hashes field. + if( isset( $_POST['hashes'] ) ){ + $snz = sanitize_textarea_field( $_POST['hashes'] ); + update_field( 'hashes', $snz, $this->entry->ID ); + do_action( 'RomhackPlaza\\Extenders\\Post\\Hashes_Table\\Update_CRC32_SHA1', intval( $this->entry->ID ), $snz ); + } + + // Credits field. + if( isset( $_POST['staff'] ) ) + update_field( 'staff', sanitize_textarea_field( $_POST['staff'] ), $this->entry->ID ); + + // For tutorials. Summary as Post excerpt. + if( isset( $_POST['summary'] ) ) + wp_update_post( [ + 'ID' => $this->entry->ID, + 'post_excerpt' => sanitize_textarea_field( $_POST['summary'] ), + ] ); + + // For tutorials. TOC. + if( $this->entry->tutorial_content !== [] && $this->entry->tutorial_content["headers"] != [] ) + update_field( 'table_of_contents', $this->entry->tutorial_content["headers"], $this->entry->ID ); + + // If there is an update of the entry + if( $this->entry->version_updated ) + update_post_meta( $this->entry->ID, 'entry_version_updated_once', 1 ); + + // Update queue status. Add Edited by his author. + if( get_post_status( $this->entry->ID ) === "pending" && !$_romhackplaza->user_roles->current_is_staff() ){ + + // Current user can edit the entry. It's in the queue and it's not a staff member. + $queue_status = get_field( 'queue_status', $this->entry->ID ); + if( $queue_status && $queue_status !== "" && $queue_status !== "not checked" ){ // If this entry have a custom queue status. + + $queue_status .= "\nEdited by his author"; + update_field( 'queue_status', $queue_status, $this->entry->ID ); + + } + + } + + // Online patcher. Only check on create mode and for a romhack or a translation. + if( $this->entry->creation && in_array( $this->entry->post_type, [ 'romhacks', 'translations' ] ) ){ + + // Can be auto enabled. + if( in_array( intval( $this->entry->platform_id ), self::AUTO_ENABLE_ONLINE_PATCHER ) ) { + + // Not a read-only or a member. + if( $_romhackplaza->user_roles->current_is_staff() || $_romhackplaza->user_roles->current_is( Roles::Verified ) || $_romhackplaza->user_roles->current_is( Roles::Bot ) ) + update_post_meta( $this->entry->ID, 'online_patcher', 'enabled' ); + else + update_post_meta( $this->entry->ID, 'online_patcher', 'disabled' ); + } else + update_post_meta( $this->entry->ID, 'online_patcher', 'disabled' ); + + } + + // Custom Taxonomies ! + + // Game term. + if( $this->entry->game_id ) + wp_set_object_terms( $this->entry->ID, intval( $this->entry->game_id ), 'game', false ); + + // For romhacks. + if( isset( $_POST['modifications'] ) ){ + $modifications = array_map( 'intval', $_POST['modifications'] ); + wp_set_object_terms( $this->entry->ID, $modifications, 'modifications', false ); + } + + // For LUA scripts. + if( isset( $_POST['lua-modifications'] ) ){ + $modifications = array_map( 'intval', $_POST['lua-modifications'] ); + wp_set_object_terms( $this->entry->ID, $modifications, 'lua-modifications', false ); + } + + // For homebrew. + if( isset( $_POST['homebrew-type'] ) ){ + $modifications = array_map( 'intval', $_POST['homebrew-type'] ); + wp_set_object_terms( $this->entry->ID, $modifications, 'homebrew-type', false ); + } + + // Languages + if( !empty( $_POST['language'] ) ){ + $langs = array_map( 'intval', $_POST['language'] ); + wp_set_object_terms( $this->entry->ID, $langs, 'language', false ); + } + + if( $this->entry->platform_id && $this->entry->platform_id != 0 ) + wp_set_object_terms( $this->entry->ID, $this->entry->platform_id, 'platform', false ); + + if( $this->entry->genre_id && $this->entry->genre_id != 0 ) + wp_set_object_terms( $this->entry->ID, $this->entry->genre_id, 'genre', false ); + + if( isset( $_POST['hack-status'] ) ){ + $hks = intval( $_POST['hack-status'] ); + wp_set_object_terms( $this->entry->ID, $hks, 'hack-status', false ); + } + + // Utilities. + if( isset( $_POST['experience-level'] ) ){ + $exp_level = intval( $_POST['experience-level'] ); + wp_set_object_terms( $this->entry->ID, $exp_level, 'experience-level', false ); + } + + if( !empty( $_POST['utility-category'] ) ){ + $cat = array_map( 'intval', $_POST['utility-category'] ); + wp_set_object_terms( $this->entry->ID, $cat, 'utility-category', false ); + } + + if( !empty( $_POST['document-category'] ) ){ + $cat = array_map( 'intval', $_POST['document-category'] ); + wp_set_object_terms( $this->entry->ID, $cat, 'document-category', false ); + } + + if( !empty( $_POST['tutorial-category'] ) ){ + $cat = array_map( 'intval', $_POST['tutorial-category'] ); + wp_set_object_terms( $this->entry->ID, $cat, 'tutorial-category', false ); + } + + if( !empty( $_POST['news-category'] ) ){ + $cat = intval( $_POST['news-category'] ); + wp_set_object_terms( $this->entry->ID, $cat, 'news-category', false ); + } + + if( isset( $_POST['utility-os'] ) ){ + $os = intval( $_POST['utility-os'] ); + wp_set_object_terms( $this->entry->ID, $os, 'utility-os', false ); + } + + if( $this->entry->nsfw ) + wp_set_object_terms( $this->entry->ID, [ intval( $_romhackplaza->settings->get( 'nsfw_tag_id' ) ) ], 'post_tag', false ); + + $this->create_and_set_authors(); + + // For utilities & Documents. + // TODO: Change order. + if( isset( $_POST['removeGamePlatformTerms'] ) && $_POST['removeGamePlatformTerms'] === 'yes' ){ + wp_set_object_terms( $this->entry->ID, [], 'game', false ); + wp_set_object_terms( $this->entry->ID, [], 'platform', false ); + } + + // Last update text. + if( !is_null( wp_get_current_user() ) ) { + $last_update = sprintf("Last updated on %s by %s.", current_time('mysql'), wp_get_current_user()->display_name); + update_post_meta( $this->entry->ID, 'last_update_by', $last_update ); + } + + do_action( 'RomhackPlaza\\Extenders\\Discord_Notification\\Send_Notification_After_Submission', + $this->entry->ID, + $this->entry->post_title, + [ 'new_post' => $discord_new_post_flag, 'update_post' => $discord_update_post_flag ], + isset( $_POST['versionNumber'] ) ? sanitize_text_field( $_POST['versionNumber'] ) : 0 + ); + + } + + protected function index_entry(){ + + if( function_exists( "FWP" ) ) // FacetWP + FWP()->indexer->index( $this->entry->ID ); + + } + +} \ No newline at end of file diff --git a/src/Extenders/Ajax/Submit_Notification.php b/src/Extenders/Ajax/Submit_Notification.php new file mode 100644 index 0000000..9755df7 --- /dev/null +++ b/src/Extenders/Ajax/Submit_Notification.php @@ -0,0 +1,52 @@ +user_roles->current_is_staff() ) + wp_send_json_error( [ 'message' => "You don't have permission to submit notifications." ] ); + + $type_select = isset( $_POST['type_select'] ) ? sanitize_text_field( $_POST['type_select'] ) : 'none'; + $anonymous = isset( $_POST['sender_anonymous'] ) ? sanitize_text_field( $_POST['anonymous'] ) : 'no'; + $all_users = isset( $_POST['recipients_all'] ) ? sanitize_text_field( $_POST['recipients_all'] ) : 'no'; + $recipients = isset( $_POST['recipients'] ) ? array_map( 'intval', $_POST['recipients'] ) : []; + $content = isset( $_POST['content'] ) ? wp_kses_post( $_POST['content'] ) : ''; + + if( $all_users === "yes" || $all_users === "on" ) + $recipients = get_users( [ 'fields' => 'ID' ] ); + + $sender = $anonymous === "yes" || $anonymous === "on" ? $_ENV['ROMHACKPLAZA_DEFAULT_ACCOUNT'] : get_current_user_id(); + + foreach ( $recipients as $recipient ) + User_Notifications::add([ + 'sender' => $sender, + 'recipient' => $recipient, + 'type' => $type_select, + 'content' => $content, + 'auto' => 0, + 'readed' => 0, + 'date' => time() + ]); + + wp_send_json_success( [ 'message' => 'Good.' ] ); + + } + +} \ No newline at end of file diff --git a/src/Extenders/Ajax/Update_Entry_Mod_Fields.php b/src/Extenders/Ajax/Update_Entry_Mod_Fields.php new file mode 100644 index 0000000..95f9400 --- /dev/null +++ b/src/Extenders/Ajax/Update_Entry_Mod_Fields.php @@ -0,0 +1,22 @@ +name = $name; + $this->args = $args; + + parent::__construct(); + + } + + protected function can_extend(): bool + { + + return true; + + } + + protected function extend(): void { + + $this->add_action('init', fn() => register_post_status($this->name, $this->args) ); + + } + +} \ No newline at end of file diff --git a/src/Extenders/Custom_Post_Type.php b/src/Extenders/Custom_Post_Type.php new file mode 100644 index 0000000..4916d9e --- /dev/null +++ b/src/Extenders/Custom_Post_Type.php @@ -0,0 +1,65 @@ +name = $name; + $this->singular_name = $singular_name; + $this->args = $args; + + parent::__construct(); + + } + + public function getAcfFieldGroupKey(): string + { + return $this->acf_field_group_key; + } + + public function setAcfFieldGroupKey(string $acf_field_group_key): void + { + $this->acf_field_group_key = $acf_field_group_key; + } + + protected function can_extend(): bool + { + + return true; + + } + + protected function extend(): void { + + $this->add_action('init', fn() => register_post_type($this->name, $this->args) ); + + } + +} \ No newline at end of file diff --git a/src/Extenders/Discord_Notification.php b/src/Extenders/Discord_Notification.php new file mode 100644 index 0000000..9e48d7e --- /dev/null +++ b/src/Extenders/Discord_Notification.php @@ -0,0 +1,99 @@ +add_action( 'RomhackPlaza\\Extenders\\Discord_Notification\\Send_Notification_After_Submission', [ $this, 'send_notification_after_submission' ], 10, 4 ); + } + + protected function get_webhook_url( string $type = 'global' ): string { + + $webhook_type = ""; + switch( $type ) { + case 'romhacks': + case 'translations': + $webhook_type = 'discord_webhook_romhacks_translations'; + break; + default: + $webhook_type = 'discord_webhook_global'; + break; + } + + global $_romhackplaza; + + $webhook_url = $_romhackplaza->settings->get( $webhook_type ); + if( !$webhook_url || $webhook_url == "" ) + return "unknown"; + + return $webhook_url; + + } + + protected function send_to_discord( string $webhook, string $content ){ + + $curl = curl_init( $webhook ); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode( [ "content" => $content ] ) ); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type:application/json')); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + @curl_exec( $curl ); // Nevermind the answer. + + } + + public function send_notification_after_submission( + int $post_id, + string $post_title, + array $flags = [ 'new_post' => false, 'update_post' => false ], + int $new_version_number = 0 + ): void { + + if( !$flags['new_post'] && !$flags['update_post'] ) return; + + $post_type = get_post_type( $post_id ); + if( !$post_type ) return; + + $strings = [ + 'new_post' => sprintf( + "## A new entry has been posted.\n\n[%s](%s)\n\n%s", + $post_title, $_ENV['ROMHACKPLAZA_SHORT_URL'] . $post_id, strip_tags( get_the_content( null, false, $post_id ) ) + ), + 'update_post' => sprintf( + "## A new version of this entry has been posted.\n\n[%s](%s)\n\n%s\n\n**New version number: %s**", + $post_title, $_ENV['ROMHACKPLAZA_SHORT_URL'] . $post_id, strip_tags( get_the_content( null, false, $post_id ) ), $new_version_number + ) + ]; + + $webhook = null; $content = null; + + if( $flags['new_post'] && in_array( $post_type, [ 'romhacks', 'homebrew' ] ) ){ + $webhook = $this->get_webhook_url( 'romhacks' ); + $content = $strings['new_post']; + } else if( $flags['new_post'] ){ + $webhook = $this->get_webhook_url( 'global' ); + $content = $strings['new_post']; + } else if( $flags['update_post'] && in_array( $post_type, [ 'romhacks', 'homebrew' ] ) ){ + $webhook = $this->get_webhook_url( 'romhacks' ); + $content = $strings['update_post']; + } else if( $flags['update_post'] ){ + $webhook = $this->get_webhook_url( 'global' ); + $content = $strings['update_post']; + } + + if( $webhook == null || $content == null ) return; + + $this->send_to_discord( $webhook, $content ); + update_post_meta( $post_id, 'discord_noticed', "yes" ); + + } +} \ No newline at end of file diff --git a/src/Extenders/Post/Hashes_Table.php b/src/Extenders/Post/Hashes_Table.php new file mode 100644 index 0000000..a0c7a5d --- /dev/null +++ b/src/Extenders/Post/Hashes_Table.php @@ -0,0 +1,74 @@ +prefix . self::TABLE_NAME; + $charset_collate = $wpdb->get_charset_collate(); + + $sql = " + CREATE TABLE {$table_name} ( + entry_id INT(11) NOT NULL, + crc VARCHAR(20) NOT NULL, + sha VARCHAR(100) NOT NULL, + PRIMARY KEY (entry_id) + ) {$charset_collate}; + ) + "; + + dbDelta( $sql ); + } + + public static function verify_table() + { + global $wpdb; + + if( !self::table_exists( $wpdb->prefix . self::TABLE_NAME ) ){ + self::create_table(); + return; + } + + // TODO: Columns verif. + + } + + protected function can_extend(): bool + { + return true; // The table is created each time the plugin is activated if it does not exist. + } + + protected function extend(): void { + + $this->add_action( 'RomhackPlaza\\Extenders\\Post\\Hashes_Table\\Update_CRC32_SHA1', [ $this, 'update_crc32_sha1_from_entry' ], 10, 2 ); + + } + + public function update_crc32_sha1_from_entry( int $post_id, string $s ) { + + global $wpdb; + $table_name = $wpdb->prefix . self::TABLE_NAME; + + $crc = Fetch::crc32_in_string( $s ); + $sha = Fetch::sha1_in_string( $s ); + if( $crc || $sha ) + $wpdb->replace( $table_name, [ 'entry_id' => $post_id, 'crc' => $crc[0] ?? false, 'sha' => $sha[0] ?? false ], [ '%d', '%s', '%s' ] ); + + } + +} \ No newline at end of file diff --git a/src/Extenders/Post/Properties.php b/src/Extenders/Post/Properties.php new file mode 100644 index 0000000..1a11ac1 --- /dev/null +++ b/src/Extenders/Post/Properties.php @@ -0,0 +1,64 @@ +add_action( 'wp', function(){ + global $post; + static::change_current_post( $post ); + }, 20 ); + + } + + public static function change_current_post( \WP_Post $post ): void { + + static::$current_post = $post; + + } + + private static function tag_exists_in_entry( string $setting ): bool { + + global $_romhackplaza; + $tag_id = absint( $_romhackplaza->settings->get( $setting ) ); + + if( $tag_id === 0 ) + return false; // Not enabled. + + $post_tags = wp_get_post_tags( static::$current_post->ID ); + if( !$post_tags || $post_tags === [] ) + return false; // No tags. + + foreach ( $post_tags as $post_tag ) { + if( $post_tag->term_id == $tag_id ) + return true; + } + + return false; + } + + public static function public_edit_disabled(): bool { + + return static::tag_exists_in_entry( 'public_edit_disabled_tag_id' ); + + } + + public static function nsfw(): bool { + + return static::tag_exists_in_entry( 'nsfw_tag_id' ); + + } + +} \ No newline at end of file diff --git a/src/Extenders/Post/Save_Post.php b/src/Extenders/Post/Save_Post.php new file mode 100644 index 0000000..c5ffca4 --- /dev/null +++ b/src/Extenders/Post/Save_Post.php @@ -0,0 +1,153 @@ +add_filter( 'sanitize_title', [ $this, 'remove_words_from_slug' ] ); + return; + } + + /** + * Remove some keys words in post slug. + * @param string $slug + * @return string + */ + public function remove_words_from_slug( string $slug ): string { + + $list = [ + '-romhack', + '-homebrew', + '-document', + '-tutorial', + '-lua-script' + ]; + + if( is_admin() ) + return $slug; + + return str_replace( $list, '', $slug ); + + } + + /** + * Add post_parent for attachments added in a ACF gallery element. + * @see Advanced_Custom_Fields_Pro + * @param $post_id + * @return void + */ + public function bind_post_parent_attachment_from_gallery( $post_id ) { + + // Exclude autosave or non-ACF save. + if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) return; + if ( is_int(wp_is_post_revision($post_id)) ) return; + if ( is_int(wp_is_post_autosave($post_id)) ) return; + if ( empty($_POST['acf']) ) return; + + $files = get_field( 'my_gallery', $post_id, false ); + if( $files ) { + + foreach ($files as $file_id) { + $attachment = [ 'ID' => $file_id, 'post_parent' => $post_id ]; + wp_update_post( $attachment ); // Update attachment. + } + } + + return; + + } + + /** + * Capitalize only some minor words. + * @param string $title + * @return string + * @see RomhackPlaza\Extenders\Ajax\Submissions_Save_Entry + */ + public static function edit_minor_words_in_title( string $title ): string { + + /** + * Change Minor words list. + * + * @param array - Minor words list. + * @return array + */ + $minor_words_list = apply_filters( 'RomhackPlaza\\Extenders\\Post\\edit_minor_words_in_title\\List', [ + 'a', 'an', 'and', 'at', 'but', 'by', 'for', 'in', 'nor', 'of', 'on', 'or', 'to' + ] ); + + $words = explode( ' ', $title ); + foreach( $words as $k => $word ) { + + if( $k == 0 || $k == count( $words ) - 1 || !in_array( $word |> strtolower( ... ), $minor_words_list ) ) + $words[$k] = ucfirst( $word ); + + } + + return implode( ' ', $words ); + + } + + /** + * Change platform name to short platform name. + * @param string $platform_name + * @return string + * @see RomhackPlaza\Extenders\Ajax\Submissions_Save_Entry + */ + public static function get_short_platform_name( string $platform_name ): string { + + $platform_changes = apply_filters( 'RomhackPlaza\\Extenders\\Post\\Save_Post\\get_short_platform_name', [ + 'Family Computer Disk System' => 'FDS', + 'Nintendo Entertainment System' => 'NES', + 'Super Nintendo' => 'SNES', + 'Game Boy Color' => 'GBC', + 'SEGA Genesis' => 'Genesis', + 'SEGA Master System' => 'SMS', + 'SEGA Game Gear' => 'GG', + 'PlayStation Portable' => 'PSP' + ] ); + + if( empty( $platform_name ) ) + return ''; + + return $platform_changes[ $platform_name ] ?? $platform_name; + + } + + /** + * Add some IDs to the HTML title (3 to 6) to be used in the TOC. + * @param string $post_content + * @return array + */ + public static function header_and_edited_content_for_tutorial( string $post_content ): array { + + $headers = []; + $content = preg_replace_callback( '/(\(.*)(<\/h[3-6]>)/i', function( $matches ) use ( &$headers ) { + + // Add HTML ID if needed. + if( !stripos( $matches[0], 'id=') ){ + $matches[0] = $matches[1] . $matches[2] . ' id="' . sanitize_title( $matches[3] ) . '">' . $matches[3] . $matches[4]; + } + + $headers[] = [ + "title" => sanitize_title( $matches[3] ), + "text" => $matches[3] + ]; + + }, $post_content); + + return [ "content" => $content, "headers" => $headers ]; + } + +} \ No newline at end of file diff --git a/src/Extenders/Submissions.php b/src/Extenders/Submissions.php new file mode 100644 index 0000000..9f2248a --- /dev/null +++ b/src/Extenders/Submissions.php @@ -0,0 +1,476 @@ +add_action( 'wp_enqueue_scripts', [ $this, 'load_assets' ] ); + $this->add_filter( 'timber/context', [ $this, 'fill_fields_for_timber' ], 5 ); + $this->add_action( 'RomhackPlaza\\Extenders\\Submissions\\Get_A_File_Server', [ $this, 'get_file_server' ] ); + + } + + /** + * Return list of IDs for submissions. + * @param string $type 'simple', 'complete' or 'all' ( Default 'all' ) + * @return int[] + */ + public function get_all_forms_page_ids( string $type = 'all' ): array { + + $option_name = ''; + + switch ( $type ) { + case 'simple': + $option_name = 'simple_submissions_form_page_ids'; + break; + case 'complete': + $option_name = 'complete_submissions_form_page_ids'; + break; + default: + $type = 'all'; // Make sure it's the affected value. + break; + } + + global $_romhackplaza; + + if( $type === 'simple' || $type === 'complete' ) { + $string_list = $_romhackplaza->settings->get( $option_name ); + + $arr = explode( ';', $string_list ); + if( $arr[0] == '' ) + $arr = []; + return array_map( 'intval', $arr ); + + } + + $first_string_list = $_romhackplaza->settings->get( 'simple_submissions_form_page_ids' ); + $first_arr = explode( ';', $first_string_list ); + if( $first_arr[0] == '' ) + $first_arr = []; + + $second_string_list = $_romhackplaza->settings->get( 'complete_submissions_form_page_ids' ); + $second_arr = explode( ';', $second_string_list ); + if( $second_arr[0] == '' ) + $second_arr = []; + + $arr = array_merge( $first_arr, $second_arr ); + return array_map( 'intval', $arr ); + + } + + /** + * Load CSS and JS scripts only on the good pages. + * @return void + */ + public function load_assets(): void { + + global $post; + if( !isset( $post->ID ) ) + return; + + $simple_page_ids = $this->get_all_forms_page_ids( 'simple' ); + $complete_page_ids = $this->get_all_forms_page_ids( 'complete' ); + + if( in_array( $post->ID, $complete_page_ids ) ){ // Translations, Romhacks, Homebrew, Utilities, Documents, LUA Scripts, Tutorials + + $this->children['script_hash_calc'] = new Script( + Script_Type::JS, + 'hash-calculator', + ROMHACKPLAZA_PLUGIN_URI . '/assets/js/submissions/hashes.js', + [], + '20251106', + args: [ 'defer' => true, 'in_footer' => true ] + )->enqueue(); + + $this->children['script_uploader'] = new Script( + Script_Type::JS, + 'submissions-uploader', + ROMHACKPLAZA_PLUGIN_URI . '/assets/js/submissions/uploader.js', + [], + '20251106', + args: [ 'defer' => true, 'in_footer' => true ] + )->enqueue()->add_localize( + '_romhackplaza_script_uploader', + [ 'submit_url' => admin_url( 'admin-ajax.php' ) ], + ); + + } + + if( in_array( $post->ID, $simple_page_ids ) ){ // News + + $this->children['script_simple_uploader'] = new Script( + Script_Type::JS, + 'submissions-simple-uploader', + ROMHACKPLAZA_PLUGIN_URI . '/assets/js/submissions/simple-uploader.js', + [], + '20251106', + args: [ 'defer' => true, 'in_footer' => true ] + )->enqueue()->add_localize( + '_romhackplaza_script_uploader', + [ 'submit_url' => admin_url( 'admin-ajax.php' ) ], + ); + + } + + if( in_array( $post->ID, $simple_page_ids ) || in_array( $post->ID, $complete_page_ids ) ){ + + $this->children['script_css'] = new Script( + Script_Type::CSS, + 'submissions-css', + ROMHACKPLAZA_PLUGIN_URI . '/assets/css/submissions.css', + [], + '20251106', + )->enqueue(); + + $this->children['script_select2_css'] = new Script( + Script_Type::CSS, + 'select2', + 'https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css' + )->enqueue(); + + $this->children['script_select2_js'] = new Script( + Script_Type::JS, + 'select2', + 'https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js', + [ 'jquery' ], + '', + args: [ 'defer' => true, 'in_footer' => true ] + )->enqueue(); + + wp_enqueue_media(); // WP Media Library scripts. + + // ACF / ACF Pro Scripts. + if( function_exists( 'acf_form_head' ) ) + acf_form_head(); + if( function_exists( 'acf_enqueue_scripts' ) ) + acf_enqueue_scripts(); + + } + + } + + protected function test_terms_array( mixed $terms_array ){ + + if( $terms_array && !is_wp_error( $terms_array ) ) + return $terms_array; + return []; + } + + protected function priority_language_names(){ + + return [ 'English', 'French', 'German', 'Spanish', 'Italian', 'Japanese' ]; + + } + + protected function priority_language_terms_obj( array $terms_array ): array{ + + $priority = $this->priority_language_names(); + return array_filter( $terms_array, function( $term ) use ( $priority ){ + return in_array( $term->name, $priority ); + }); + + } + + protected function add_and_format_language( &$context ) + { + $terms = \get_terms([ + 'taxonomy' => 'language', + 'hide_empty' => false, + ]); + + $priority_names = $this->priority_language_names(); + $priority_terms = $this->priority_language_terms_obj( $terms ); + $other_terms = array_udiff( $terms, $priority_terms, function( $a, $b ) { + return strcmp( $a->term_id, $b->term_id ); + }); + + usort( $priority_terms, function( $a, $b ) use( $priority_names ){ + $pos_a = array_search( $a->name, $priority_names ); + $pos_b = array_search( $b->name, $priority_names ); + return $pos_a - $pos_b; + }); + + $context['terms_language'] = array_merge( $priority_terms, $other_terms ); + + $context['value_terms_language'] = $context['submission_post_id'] != 0 ? wp_get_post_terms( $context['submission_post_id'], 'language', [ 'fields' => 'ids' ] ) |> $this->test_terms_array(...) : []; + + return $context; + } + + protected function add_and_format_game( &$context ){ + + $game_term = $context['submission_post_id'] != 0 ? get_the_terms( $context['submission_post_id'], 'game' ) : 0; + $context['value_game_id'] = $game_term != 0 && !is_wp_error( $game_term ) ? $game_term[0]->term_id : ''; + $context['value_game_name'] = $game_term != 0 && !is_wp_error( $game_term ) ? $game_term[0]->name : ''; + + return $context; + + } + + protected function add_and_format_platform( &$context ){ + + $context['terms_platform'] = \Timber\Timber::get_terms([ + 'taxonomy' => 'platform', + 'hide_empty' => false, + ]); + + $platform_term = $context['submission_post_id'] != 0 ? wp_get_post_terms( $context['submission_post_id'], 'platform', ['fields' => 'ids'] ) : 0; + $context['value_platform_id'] = $platform_term != 0 && !empty( $platform_term ) ? $platform_term[0] : ''; + + return $context; + } + + protected function add_and_format_hashes( &$context ) + { + $context['value_hashes'] = $context['submission_post_id'] != 0 ? esc_textarea( get_post_meta( $context['submission_post_id'], 'hashes', true ) ) : ''; + return $context; + } + + protected function add_and_format_genre( &$context ){ + + $context['terms_genre'] = \Timber\Timber::get_terms([ + 'taxonomy' => 'genre', + 'hide_empty' => false, + ]); + + $genre_term = $context['submission_post_id'] != 0 ? wp_get_post_terms( $context['submission_post_id'], 'genre', ['fields' => 'ids'] ) : 0; + $context['value_genre_id'] = $genre_term != 0 && !empty( $genre_term ) ? $genre_term[0] : ''; + + return $context; + } + + protected function add_and_format_status( &$context ){ + + $context['list_status'] = \Timber\Timber::get_terms([ + 'taxonomy' => 'hack-status', + 'hide_empty' => false, + ]); + $context['value_terms_status'] = $context['submission_post_id'] != 0 ? wp_get_post_terms( $context['submission_post_id'], 'hack-status', [ 'fields' => 'ids' ] ) |> $this->test_terms_array(...) : []; + + return $context; + } + + protected function add_and_format_main_image_gallery( &$context ){ + + if( !function_exists( 'acf_form' ) ){ + $context['can_acf'] = false; + return $context; + } + + $context['can_acf'] = true; + $context['attachments_settings'] = [ + 'post_id' => $context['submission_post_id'] != 0 ? $context['submission_post_id'] : 'new-post', + 'fields_groups' => "REPLACED", + 'fields' => [ 'custom_featured_image', 'my_gallery' ], + 'submit_value' => __( 'Add Images', 'romhackplaza' ), + 'updated_message' => __( "Images added!", 'romhackplaza' ), + 'form' => false + ]; + + return $context; + + } + + protected function add_and_format_author( &$context ){ + + $context['value_authors'] = $context['submission_post_id'] != 0 ? get_the_terms( $context['submission_post_id'], 'author' ) |> $this->test_terms_array(...) : []; + return $context; + + } + + protected function add_and_format_credits( &$context ){ + + $context['value_credits'] = $context['submission_post_id'] != 0 ? esc_textarea( get_post_meta( $context['submission_post_id'], 'staff', true ) ) : ''; + return $context; + + } + + public function fill_fields_for_timber( $context ){ + + global $post; + if( !isset( $post->ID ) ) + return $context; + + $simple_page_ids = $this->get_all_forms_page_ids( 'simple' ); + $complete_page_ids = $this->get_all_forms_page_ids( 'complete' ); + + if( !in_array( $post->ID, $simple_page_ids ) && !in_array( $post->ID, $complete_page_ids ) ) + return $context; + + /* + * The filter is removed to avoid duplications. + */ + global $_romhackplaza_theme, $_romhackplaza; + remove_filter( 'timber/context', [ $_romhackplaza_theme->children['template_context'], 'timber_context_handler_by_page' ] ); + + // General ones. + + // If we are on the result page and why. + $context['submission_result'] = isset( $_GET['result'] ) ? sanitize_text_field( $_GET['result'] ) : ''; + + // If we edit an entry, the post ID. + $context['submission_post_id'] = isset( $_GET['edit_entry'] ) ? intval( $_GET['edit_entry'] ) : 0; + + // If we edit an entry on the result page. URL. + $context['submission_edit_entry'] = isset( $_GET['edit_entry'] ) ? add_query_arg( [ 'edit_entry' => intval( $_GET['edit_entry'] ) ], get_permalink() ) : ''; + + // If the current user is verified or staff. + $context['submission_author_is_verified'] = $_romhackplaza->user_roles->current_is( Roles::Verified ) || $_romhackplaza->user_roles->current_is_staff(); + + // If the current user can edit this entry, if it already exist. + $context['submission_can_edit'] = !isset($context['submission_post_id']) || $_romhackplaza->user_roles->current_can_edit_entry($context['submission_post_id']); + + // If the current user can bypass a locked entry. Only if it is a staff member. + $context['submission_can_bypass_locked'] = $_romhackplaza->user_roles->current_is_staff(); + + // If the current user can bypass a trashed entry and a private entry. Only an administrator can. + $context['submission_can_bypass_trashed_private'] = $_romhackplaza->user_roles->current_is(Roles::Administrator); + + // If the public edit is disabled or not. + $context['submission_public_edit_disabled'] = Properties::public_edit_disabled(); + + // Submission team message, like queue status. Private message. Locked reason, etc... + $context['submission_queue_status'] = isset( $context['submission_post_id'] ) ? get_field( 'queue_status', $context['submission_post_id'] ) : ""; + if( $context['submission_queue_status'] === "not checked" ) // Default message. + $context['submission_queue_status'] = ""; + + // Save Nonce + $context['submission_save_nonce'] = wp_nonce_field( Format::format_nonce_name( 'submissions_save_entry' ), display: false ); + + // Entry title field default name. + $context['entry_title_field_name'] = __( "Name:", "romhackplaza" ); + + // Entry title value. + $context['value_entry_title'] = $context['submission_post_id'] != 0 ? esc_attr( get_post_meta( $context['submission_post_id'], 'entry_title', true ) ) : ""; + + // Patch version label. + $context['version_number_field_name'] = __( "Version:", "romhackplaza" ); + + // Patch version value + $context['value_version_number'] = $context['submission_post_id'] != 0 ? esc_attr( get_post_meta( $context['submission_post_id'], 'versionNumber', true ) ) : ""; + + // Release date value + $context['value_release_date'] = ""; + $release_date = $context['submission_post_id'] != 0 ? get_post_meta( $context['submission_post_id'], 'release_date', true ) : false; + if( $release_date ) { + $date = \DateTime::createFromFormat( 'Ymd', $release_date ); + if( $date ) + $context['value_release_date'] = $date->format( 'Y-m-d' ); + } + + // Editor settings and value + $context['value_post_content'] = $context['submission_post_id'] != 0 ? get_post_field( 'post_content', $context['submission_post_id'], 'edit' ) : ""; + $context['post_content_settings'] = [ + 'textarea_name' => 'postContent', + 'media_buttons' => false, + 'textarea_rows' => 7, + 'tinymce' => array( + 'theme_advanced_buttons1' => 'formatselect,|,bold,italic,underline,|,' . + 'bullist,numlist,|,link,unlink,|,undo,redo,|,' . + 'removeformat,|,charmap,|,outdent,indent,|,cut,copy,paste', + 'content_css' => get_stylesheet_directory_uri() . '/editor.css' + ), + 'quicktags' => array('buttons' => 'strong,em,link,block,del,ins,img,ul,ol,li,code,close'), + ]; + + $post_type_rhpz_obj = null; + + if( str_contains( $post->post_name, 'translation' ) ){ + + $post_type_rhpz_obj = &$_romhackplaza->cpt->select->translations; + $context['entry_title_field_name'] = __( "Custom Name:", "romhackplaza" ); + $context['version_number_field_name'] = __( "Patch Version:", "romhackplaza" ); + + $context = $this->add_and_format_status( $context ); + $context = $this->add_and_format_language( $context ); + $context = $this->add_and_format_game( $context ); + $context = $this->add_and_format_platform( $context ); + $context = $this->add_and_format_genre( $context ); + $context = $this->add_and_format_hashes( $context ); + $context = $this->add_and_format_main_image_gallery( $context ); + $context = $this->add_and_format_author( $context ); + $context = $this->add_and_format_credits( $context ); + + } + + else if( str_contains( $post->post_name, 'romhack' ) ){ + + $post_type_rhpz_obj = &$_romhackplaza->cpt->select->romhacks; + $context['version_number_field_name'] = __( "Patch Version:", "romhackplaza" ); + + $context = $this->add_and_format_status( $context ); + $context = $this->add_and_format_language( $context ); + $context = $this->add_and_format_game( $context ); + $context = $this->add_and_format_platform( $context ); + $context = $this->add_and_format_genre( $context ); + $context = $this->add_and_format_hashes( $context ); + $context = $this->add_and_format_main_image_gallery( $context ); + $context = $this->add_and_format_author( $context ); + $context = $this->add_and_format_credits( $context ); + + } + + else if( str_contains( $post->post_name, 'news' ) ){ + + $post_type_rhpz_obj = &$_romhackplaza->cpt->select->news; + $context['entry_title_field_name'] = __( "Title:", "romhackplaza" ); + + } + + // General ones sequel. + if( $post_type_rhpz_obj ) { + + // Post type name. + $context['post_type'] = $post_type_rhpz_obj->name; + + // Post type singular name. + $context['post_type_singular'] = $post_type_rhpz_obj->singular_name; + + // ACF Field group. + $context['post_type_acf'] = $post_type_rhpz_obj->getAcfFieldGroupKey(); + + // Add for main image and gallery + if( isset( $context['attachments_settings'] ) ) + $context['attachments_settings']['field_groups'] = [ $context['post_type_acf'] ]; + + } + + return $context; + } + + public function get_file_server( int $post_id = 0 ){ + + global $post; + if( !isset( $post->ID ) ) + return; + + $complete_page_ids = $this->get_all_forms_page_ids( 'complete' ); + + if( in_array( $post->ID, $complete_page_ids ) ) { // Translations, Romhacks, Homebrew, Utilities, Documents, LUA Scripts, Tutorials + new File_Server($post_id)->print_js(); + } + + } + +} \ No newline at end of file diff --git a/src/Extenders/Taxonomy.php b/src/Extenders/Taxonomy.php new file mode 100644 index 0000000..9ed4dda --- /dev/null +++ b/src/Extenders/Taxonomy.php @@ -0,0 +1,49 @@ +name = $name; + $this->post_types = $post_types; + $this->args = $args; + + parent::__construct(); + + } + + protected function can_extend(): bool + { + + return true; + + } + + protected function extend(): void { + + $this->add_action('init', fn() => register_taxonomy($this->name, $this->post_types, $this->args) ); + + } + +} \ No newline at end of file diff --git a/src/Extenders/Timber.php b/src/Extenders/Timber.php new file mode 100644 index 0000000..46dd573 --- /dev/null +++ b/src/Extenders/Timber.php @@ -0,0 +1,100 @@ +main_theme_enabled; + + } + + protected function extend(): void{ + + $this->add_filter( 'timber/twig', [ $this, 'add_token_parser'] ); + $this->add_filter( 'timber/locations', [ $this, 'add_views_locations' ] ); + $this->add_filter( 'timber/twig/functions', [ $this, 'add_functions' ] ); + $this->add_filter( 'timber/context', [ $this, 'add_conditional_tags' ] ); + $this->add_filter( 'timber/user/class', [ $this, 'override_user_class' ], 10, 2 ); + + } + + public function add_token_parser( $twig ) { + + $twig->addTokenParser( new SubmissionAccordionTokenParser() ); + return $twig; + + } + + // + public function add_views_locations( $paths ) { + + $paths['plugin'] = [ + ROMHACKPLAZA_PLUGIN_DIR . '/views' + ]; + + return $paths; + + } + // + + public function add_functions( $functions ){ + + if( class_exists( 'RomhackPlaza\Theme\Snippets' ) ) { + // + $functions['svg_icon'] = [ + 'callable' => [ \RomhackPlaza\Theme\Snippets::class, 'svg_icon' ] + ]; + // + } else { + $functions['svg_icon'] = [ + 'callable' => '__return_empty_string' + ]; + } + + if( class_exists( 'RomhackPlaza\Theme\Settings' ) ) { + + // + global $_romhackplaza_theme; + $functions['theme_setting'] = [ + 'callable' => [ $_romhackplaza_theme->settings, 'get' ] + ]; + // + + } else { + + $functions['theme_setting'] = [ + 'callable' => '__return_empty_string' + ]; + + } + + return $functions; + + } + + // + public function add_conditional_tags( $context ){ + + $context['conditional_tag']['is_home'] = is_home(); + $context['conditional_tag']['is_customize_preview'] = is_customize_preview(); + $context['conditional_tag']['is_admin'] = is_admin(); + + return $context; + + } + // + + public function override_user_class( $class, \WP_User $user ){ + + return \RomhackPlaza\Extenders\Timber\User::class; + + } + +} \ No newline at end of file diff --git a/src/Extenders/Timber/SubmissionAccordionNode.php b/src/Extenders/Timber/SubmissionAccordionNode.php new file mode 100644 index 0000000..dc08044 --- /dev/null +++ b/src/Extenders/Timber/SubmissionAccordionNode.php @@ -0,0 +1,84 @@ +addDebugInfo($this) + ->write( "ob_start();\n" ) + ->subcompile( $this->getNode('body') ) + ->write("\$content = ob_get_clean();\n") + ->write("\$type = ") + ->subcompile( $this->getNode('type') ) + ->raw(";\n") + ->write("\$icon = \RomhackPlaza\Extenders\Timber\SubmissionAccordionNode::get_safa_icon( \$type );\n") + ->write("\$pretty_title = \RomhackPlaza\Extenders\Timber\SubmissionAccordionNode::get_sapretty_title( \$type );\n") + ->write("\$opened = \$context['accordion_opened'] ?? false;\n") + ->write("echo Timber::compile('@plugin/node/submission-accordion.twig', [\n") + ->indent() + ->write("'type' => \$type,\n") + ->write("'content' => \$content,\n") + ->write("'pretty_title' => \$pretty_title,\n") + ->write("'icon' => \$icon,\n") + ->write("'accordion_opened' => \$opened,\n") + ->outdent() + ->write("]);\n"); + ; + } + +} \ No newline at end of file diff --git a/src/Extenders/Timber/SubmissionAccordionTokenParser.php b/src/Extenders/Timber/SubmissionAccordionTokenParser.php new file mode 100644 index 0000000..1104c75 --- /dev/null +++ b/src/Extenders/Timber/SubmissionAccordionTokenParser.php @@ -0,0 +1,42 @@ +getLine(); + $type = $this->parser->getExpressionParser()->parseExpression(); + + $this->parser->getStream()->expect( Token::BLOCK_END_TYPE ); + $body = $this->parser->subparse( [$this, 'decide_submission_end'], true ); + $this->parser->getStream()->expect( Token::BLOCK_END_TYPE ); + + return new SubmissionAccordionNode( + [ + 'body' => $body, + 'type' => $type, + ], + [], + $line_no, + $this->getTag() + ); + } + + public function getTag() + { + return 'submission_accordion'; + } + + public function decide_submission_end(Token $token) { + + return $token->test( 'end_submission_accordion' ); + } + +} \ No newline at end of file diff --git a/src/Extenders/Timber/User.php b/src/Extenders/Timber/User.php new file mode 100644 index 0000000..70e5c27 --- /dev/null +++ b/src/Extenders/Timber/User.php @@ -0,0 +1,16 @@ +id ); + + } + +} \ No newline at end of file diff --git a/src/Extenders/User_Notifications.php b/src/Extenders/User_Notifications.php new file mode 100644 index 0000000..f53a7bc --- /dev/null +++ b/src/Extenders/User_Notifications.php @@ -0,0 +1,210 @@ + "Extra", + "announce" => "Important announce", + "perm_change" => "Role changed", + "new_review" => "New review", + "entry_status" => "Entry status edited", + "entry_author" => "Entry author changed", + "locked_entry" => "Locked entry", + "message" => "Message", + "reply_message" => "Reply to a message", + "copy_message" => "Copy from a reply", + "report_response" => "Response from a report" + ]; + + use Database_Tools; + + public static function create_table() + { + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + global $wpdb; + + $table_name = $wpdb->prefix . self::TABLE_NAME; + $charset_collate = $wpdb->get_charset_collate(); + + $sql = " + CREATE TABLE {$table_name} ( + id INT(11) NOT NULL AUTO_INCREMENT, + sender INT(11) NOT NULL, + recipient INT(11) NOT NULL, + type VARCHAR(50) NOT NULL, + content TEXT NOT NULL, + auto INT(1) NOT NULL DEFAULT '0', + readed INT(1) NOT NULL DEFAULT '0', + date INT(11) NOT NULL, + PRIMARY KEY (id) + ) {$charset_collate}; + ) + "; + + dbDelta( $sql ); + } + + public static function verify_table() + { + global $wpdb; + + if( !self::table_exists( $wpdb->prefix . self::TABLE_NAME ) ){ + self::create_table(); + return; + } + + // TODO: Columns verif. + + } + + public static function website_scripts_list() + { + return ['delete' => 'delete-notification.php']; + } + + public static function type_name( string $type ){ + + return self::NOTIFICATIONS_TYPES[$type] ?? $type; + + } + + public static function get_by_id( int $id ){ + + global $wpdb; + $table_name = $wpdb->prefix . self::TABLE_NAME; + + $query = "SELECT * FROM {$table_name} WHERE id = {$id} LIMIT 1"; + $results = $wpdb->get_results( $query, ARRAY_A ); + + return $results[0] ?? $results ?? null; + + } + + public static function get( array $args = [] ){ + + global $wpdb; + $table_name = $wpdb->prefix . self::TABLE_NAME; + + $defaults = [ + 'sender' => 0, + 'recipient' => 0, + 'type' => 'none', + 'auto' => -1, + 'readed' => -1, + 'orderby' => 'id', + 'order' => 'ASC', + 'number' => 5 + ]; + + $args = wp_parse_args( $args, $defaults ); + + $conditions = []; + + if( !empty( $args['sender'] ) && $args['sender'] != 0 ) + $conditions[] = "sender = {$args['sender']}"; + + if( !empty( $args['recipient'] ) && $args['recipient'] != 0 ) + $conditions[] = "recipient = {$args['recipient']}"; + + if( !empty( $args['type'] ) && $args['type'] !== 'none' ) + $conditions[] = "type = {$args['type']}"; + + if( !empty( $args['auto'] ) && ( $args['auto'] == 0 || $args['auto'] == 1 ) ) + $conditions[] = "auto = {$args['auto']}"; + + if( !empty( $args['readed'] ) && ( $args['readed'] == 0 || $args['readed'] == 1 ) ) + $conditions[] = "readed = {$args['readed']}"; + + $where = ''; + if( !empty( $conditions ) ) + $where = " WHERE " . implode( ' AND ', $conditions ); + + $orderby = ''; + if( !empty( $args['orderby'] ) ){ + + $order = $args['order'] === 'DESC' || $args['order'] === 'ASC' ? $args['order'] : 'ASC'; + $orderby = " ORDER BY {$args['orderby']} {$order}"; + + } + + $limit = ''; + if( !empty( $args['number'] ) && is_int( $args['number'] ) ) + $limit = "LIMIT {$args['number']}"; + + $query = "SELECT * FROM {$table_name} {$where} {$orderby} {$limit}"; + return $wpdb->get_results( $query, ARRAY_A ); + + } + + public static function get_unread( int $user_id = 0 ){ + + if( $user_id === 0 ) + $user_id = get_current_user_id(); + + return self::get( [ + 'recipient' => $user_id, + 'readed' => 0, + 'number' => 'infinite' + ] ); + } + + public static function add( array $args ) + { + + global $wpdb; + $table_name = $wpdb->prefix . self::TABLE_NAME; + + $defaults = [ + 'sender' => $_ENV['ROMHACKPLAZA_DEFAULT_ACCOUNT'], + 'recipient' => 0, + 'type' => 'none', + 'content' => 'None', + 'auto' => 1, + 'readed' => 0, + 'date' => time(), + ]; + + $args = wp_parse_args($args, $defaults); + $args['content'] = stripslashes($args['content']); + + $wpdb->insert($table_name, $args, ['%d', '%d', '%s', '%s', '%d', '%d', '%d']); + + } + + public static function delete( int $id ){ + + global $wpdb; + $table_name = $wpdb->prefix . self::TABLE_NAME; + + $wpdb->delete( $table_name, ['id' => $id] ); + + } + + public static function unread_to_read( int $id ){ + + global $wpdb; + $table_name = $wpdb->prefix . self::TABLE_NAME; + + $wpdb->update( $table_name, ['readed' => 1], ['id' => $id] ); + + } + + protected function can_extend(): bool + { + return true; + } + + protected function extend(): void + { + + } + +} \ No newline at end of file diff --git a/src/Fetch.php b/src/Fetch.php new file mode 100644 index 0000000..b04c847 --- /dev/null +++ b/src/Fetch.php @@ -0,0 +1,64 @@ + $tax_name, + 'hide_empty' => false, + 'name__like' => $search, + 'number' => $number, + ] ); + + return $terms; + + } + + public static function get_users_by_request( + string $request_key = "q", + int $number = 10 + ){ + $search = $_REQUEST[$request_key]; + + $users = get_users([ + 'orderby' => 'display_name', + 'search' => $search, + 'search_columns' => [ 'ID', 'user_nicename', "display_name" ], + 'number' => $number, + ] ); + + return $users; + + } + + public static function crc32_in_string( string $s ): mixed { + + $pattern = '/\b[a-fA-F0-9]{8}\b/'; + preg_match_all($pattern, $s, $matches); + + return $matches[0] ?? false; + + } + + public static function sha1_in_string( string $s ): mixed { + + $pattern = '/\b[a-fA-F0-9]{40}\b/'; + preg_match_all($pattern, $s, $matches); + + return $matches[0] ?? false; + } + + + +} \ No newline at end of file diff --git a/src/Format.php b/src/Format.php new file mode 100644 index 0000000..29bd3fb --- /dev/null +++ b/src/Format.php @@ -0,0 +1,29 @@ + $elem->{$id_name}, 'text' => $elem->{$text_name} ?? "None" ]; + } + } + + return $formatted_array; + + } + + public static function format_nonce_name( string $action ){ + + return 'romhackplaza_' . $_ENV['ROMHACKPLAZA_NONCE_KEY'] . '_' . $action; + + } + +} \ No newline at end of file diff --git a/src/Modal.php b/src/Modal.php new file mode 100644 index 0000000..917872c --- /dev/null +++ b/src/Modal.php @@ -0,0 +1,230 @@ +modal_id = $modal_id; + $this->modal_title = $modal_title; + $this->modal_content = $modal_html_content; + + return $this; + } + + protected function the_modal_css(){ + + if( static::$css_loaded === false ){ + + ?> + + + + + + + + + + + the_modal_css(); + ?> +
+
+
+ × +

modal_title; ?>

+
+
+ modal_content; ?> +

+
+
+
+ the_modal_js(); + } + +} \ No newline at end of file diff --git a/src/Overrides/Abstract_Overrider.php b/src/Overrides/Abstract_Overrider.php new file mode 100644 index 0000000..16528f7 --- /dev/null +++ b/src/Overrides/Abstract_Overrider.php @@ -0,0 +1,40 @@ +can_override(); + } + + /** + * Redirect to override functions. + * @see Abstract_Overrider::override() + * @return bool + */ + protected function extend(): void { + $this->override(); + } + + /** + * If the override can be executed or not. + * @return bool + */ + abstract protected function can_override(): bool; + + /** + * Override content. + * @return void + */ + abstract protected function override(): void; + +} \ No newline at end of file diff --git a/src/Overrides/Capabilities.php b/src/Overrides/Capabilities.php new file mode 100644 index 0000000..f7cbc17 --- /dev/null +++ b/src/Overrides/Capabilities.php @@ -0,0 +1,122 @@ + [int/bool] ) + */ + final public array $current; + + protected function can_override(): bool + { + return true; + } + + protected function override(): void + { + $this->add_action( 'init', [ $this, 'bind_current_user_roles' ], 7 ); + return; + } + + /** + * Bind current user roles. + * It's the most used... + * @return void + */ + public function bind_current_user_roles(): void { + + $current_user = wp_get_current_user(); + $roles = []; + + foreach( Roles::cases() as $role ) { + $roles[$role->value] = in_array($role->value, $current_user->roles); + } + + $this->current = $roles; + + } + + /** + * Get the current user high level role. + * @return Roles|string|null + */ + public function current_upper(): Roles|string|null { + + return $this->current |> array_key_first(...) + |> Roles::tryFrom(...); + + } + + /** + * Check if current user have specific role. + * @param Roles $role + * @return bool + */ + public function current_is( Roles $role ): bool { + + if( !isset( $this->current[ $role->value ] ) ) + return false; + + return $this->current[ $role->value ] == true; + + } + + /** + * Check if current user is a staff member or not. + * @return bool + */ + public function current_is_staff(): bool { + + return current_user_can( 'edit_others_posts' ); + + } + + public function current_can_edit_entry( int $post_id ): bool { + + return $this->current_is_staff() || get_current_user_id() === intval( get_post_field( 'post_author', $post_id ) ); + + } + + public function user_upper( int $user_id ): Roles|string|null { + + $user = get_user( $user_id ); + if( !$user ) + return false; + + $user_roles = $user->roles; + if( empty( $user_roles ) ) + return null; + + return $user_roles |> array_key_first(...) + |> Roles::tryFrom(...); + + } + + public function user_is( int $user_id, Roles $role ): bool { + + $user = get_user( $user_id ); + if( !$user ) + return false; + + $user_roles = $user->roles; + return in_array( $role->value, $user_roles ); + + } + + public function user_is_staff( int $user_id ): bool { + + return user_can( $user_id, 'edit_others_posts' ); + + } + + public function user_can_edit_entry( int $user_id, int $post_id ): bool { + + return $this->current_is_staff() || get_current_user_id() === $user_id; + + } + +} \ No newline at end of file diff --git a/src/Overrides/Post_Announcements.php b/src/Overrides/Post_Announcements.php new file mode 100644 index 0000000..6479bad --- /dev/null +++ b/src/Overrides/Post_Announcements.php @@ -0,0 +1,78 @@ +add_action( 'init', [ $this, 'rename_post_to_announcements' ], 20 ); + $this->add_action( 'admin_menu', [ $this, 'rename_post_to_announcements_menu' ], 20 ); + $this->add_action( 'save_post', [ $this, 'can_save_announcement' ], 5, 3 ); + + } + + public function rename_post_to_announcements() { + + global $_romhackplaza; + if( !$_romhackplaza->user_roles->current_is( Roles::Administrator ) ) + return; // Don't change if the user is not an admin. + + global $wp_post_types; + $labels = &$wp_post_types['post']->labels; + $labels->name = __( 'Announcements', 'romhackplaza' ); + $labels->singular_name = __( 'Announcement', 'romhackplaza' ); + $labels->add_new = __( 'Add Announce', 'romhackplaza' ); + $labels->add_new_item = __( 'Add Announce', 'romhackplaza' ); + $labels->edit_item = __( 'Edit Annonce', 'romhackplaza' ); + $labels->new_item = __( 'Announce', 'romhackplaza' ); + $labels->view_item = __( 'View Announce', 'romhackplaza' ); + $labels->search_items = __( 'Search Announcements', 'romhackplaza' ); + $labels->not_found = __( 'No Announce found', 'romhackplaza' ); + $labels->not_found_in_trash = __( 'No Announce found in Trash', 'romhackplaza' ); + + } + + public function rename_post_to_announcements_menu() { + + global $_romhackplaza; + if( !$_romhackplaza->user_roles->current_is( Roles::Administrator ) ) + return; // Don't change if the user is not an admin. + + global $menu; + global $submenu; + + $menu[5][0] = __( 'Announcements', 'romhackplaza' ); + $menu[5][6] = 'dashicons-megaphone'; + $submenu['edit.php'][5][0] = __( 'Announcements', 'romhackplaza' ); + $submenu['edit.php'][10][0] = __( 'Add Announce', 'romhackplaza' ); + echo ''; + + } + + /** + * Can be moved in SavePost Extender ??? + * Only admin can send announcement. + * @return void + */ + public function can_save_announcement( $post_id, $post, $update ) { + + global $_romhackplaza; + + // Get post type after to avoid conflict or pre-save. + if( get_post_type( $post_id ) === 'post' && !$_romhackplaza->user_roles->current_is( Roles::Administrator ) ){ + wp_die( __( "You don't have the permission to post announcements.", 'romhackplaza' ), __( 'Permission error', 'romhackplaza'), [ 'response' => 403 ] ); + } + + } + +} \ No newline at end of file diff --git a/src/Overrides/Roles.php b/src/Overrides/Roles.php new file mode 100644 index 0000000..19e3082 --- /dev/null +++ b/src/Overrides/Roles.php @@ -0,0 +1,17 @@ +settings = new Settings(); + $this->cpt = new Registrars\Custom_Post_Types(); + $this->tax = new Registrars\Taxonomies(); + $this->post_status = new Registrars\Custom_Post_Status(); + $this->user_roles = new Overrides\Capabilities(); + + $this->children['extend_save_post'] = new Extenders\Post\Save_Post(); + $this->children['extend_post_properties'] = new Extenders\Post\Properties(); + + $this->children['extend_acf_pro'] = new Extenders\Advanced_Custom_Fields_Pro(); + + $this->children['override_post_announcements'] = new Overrides\Post_Announcements(); + + $this->children['extend_admin_pages'] = new Extenders\Admin\Pages(); + $this->children['extend_submissions'] = new Extenders\Submissions(); + $this->children['extend_discord'] = new Extenders\Discord_Notification(); + $this->children['extend_notifications'] = new Extenders\User_Notifications(); + $this->children['extend_admin_scripts'] = new Extenders\Admin_Scripts(); + + // Ajax requests + $this->children['ajax_submissions_save_entry'] = new Extenders\Ajax\Submissions_Save_Entry(); + $this->children['ajax_reserve_post_id'] = new Extenders\Ajax\Reserve_Post_ID(); + $this->children['ajax_set_favorite_server'] = new Extenders\Ajax\Set_Favorite_Server(); + $this->children['ajax_search_game_term'] = new Extenders\Ajax\Search_Game_Term(); + $this->children['ajax_search_author_term'] = new Extenders\Ajax\Search_Author_Term(); + $this->children['ajax_search_user'] = new Extenders\Ajax\Search_User(); + $this->children['ajax_notifications_unread'] = new Extenders\Ajax\Notifications_Unread_To_Read(); + $this->children['ajax_notifications_reply'] = new Extenders\Ajax\Notifications_Reply(); + $this->children['ajax_notifications_contact'] = new Extenders\Ajax\Notifications_Contact(); + $this->children['ajax_submit_notification'] = new Extenders\Ajax\Submit_Notification(); + + return $this; + + } + + /** + * Just after theme loaded. + * @return void + */ + public function setup_after_theme() { + $this->main_theme_enabled = $this->_is_theme_enabled(); + $this->setup_extenders_after_theme(); + } + + /** + * Setup hooks that need to be loaded after the theme is loaded and ensure it's the good one. + * @return $this + */ + public function setup_extenders_after_theme(): Plugin { + + $this->children['extend_timber'] = new Extenders\Timber(); + $this->children['extend_dashboard'] = new Extenders\Admin\Dashboard(); + + return $this; + + } + + /** + * Check if the RomhackPlaza plugin is enabled or not. + * @return bool + */ + private function _is_theme_enabled(): bool { + + return class_exists( 'RomhackPlaza\Theme' ) || defined( 'ROMHACKPLAZA_THEME' ); + + } + +} \ No newline at end of file diff --git a/src/Registrars/Custom_Post_Status.php b/src/Registrars/Custom_Post_Status.php new file mode 100644 index 0000000..6c5bd45 --- /dev/null +++ b/src/Registrars/Custom_Post_Status.php @@ -0,0 +1,28 @@ + "Locked", "exclude_from_search" => true, "public" => false, "protected" => true ]); + + $this->select = ( object)$post_statuses; + return $this; + + } + +} \ No newline at end of file diff --git a/src/Registrars/Custom_Post_Types.php b/src/Registrars/Custom_Post_Types.php new file mode 100644 index 0000000..b4f72f8 --- /dev/null +++ b/src/Registrars/Custom_Post_Types.php @@ -0,0 +1,368 @@ + esc_html__( "Translations", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Translations", "romhackplaza" ), + "singular_name" => esc_html__( "Translation", "romhackplaza" ), + ], + "description" => " + Fan translations of games are a fascinating aspect of gaming culture where enthusiasts take non-English games and translate them into different languages, primarily English. This process involves more than just translating text; it\'s about adapting a game\'s dialogue, story, and even cultural references to resonate with a wider audience while staying true to the original\'s essence. + + This meticulous task is carried out by dedicated fans who possess skills in language translation, programming, and sometimes graphic editing. The process starts with extracting the text from the game, which can be a challenge in itself due to different coding structures in old games. Once extracted, the translation begins, paying close attention to nuances and context. After translation, the text is reinserted into the game, often requiring additional programming to ensure everything fits and works seamlessly – from dialogue boxes to character limits. + + What makes fan translations truly special is the commitment to preserving the original game\'s feel and storytelling. It\'s not just about understanding the language, but also about capturing the tone, humor, and emotional beats of the game. This often involves creative solutions to convey cultural nuances that might not have a direct counterpart in the target language. + + On our website, we host a repository of these translation patches. These are downloadable files that you can apply to your game files, enabling you to play them in the translated language. This repository is a testament to the hard work and passion of the fan translation community. It\'s a place where gamers from all over the world can come to find and enjoy games that were previously out of reach due to language barriers. Each patch in our collection represents hours of dedication and a deep love for gaming\'s rich and diverse history.", + "public" => true, + "publicly_queryable" => true, + "show_ui" => true, + "show_in_rest" => false, + "rest_base" => "", + "rest_controller_class" => "WP_REST_Posts_Controller", + "rest_namespace" => "wp/v2", + "has_archive" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "delete_with_user" => false, + "exclude_from_search" => false, + "capability_type" => "post", + "map_meta_cap" => true, + "hierarchical" => false, + "can_export" => false, + "rewrite" => [ "slug" => "translations", "with_front" => true ], + "query_var" => true, + "supports" => [ "title", "editor", "thumbnail", "comments", "author" ], + "taxonomies" => [ "category", "post_tag", "game", "hack-status", "language", "platform", "author-name" ], + "show_in_graphql" => false, + ] ); + + /* --- ROMHACKS --- */ + + $post_types['romhacks'] = new CPT( 'romhacks', "romhack", [ + "label" => esc_html__( "Romhacks", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Romhacks", "romhackplaza" ), + "singular_name" => esc_html__( "Romhack", "romhackplaza" ), + ], + "description" => "The Romhacks section of our website is a hub for all kinds of game modifications created by fans. Here, enthusiasts and talented programmers take classic games and tweak them, adding new features, fixing bugs, or completely transforming the gameplay. These modifications or romhacks, are a way for fans to reimagine, enhance, and personalize their favorite games. + +Our archives are a treasure trove of these fan-made creations, easily downloadable for gamers to explore. The variety of hacks available is vast, catering to different interests and gaming experiences: + +Bug Fix: These hacks address glitches and bugs in the original game, providing a smoother gaming experience. +Gameplay: Modifications that alter or improve the gameplay mechanics to offer a new experience or to fine-tune the existing one. +Graphics: Hacks that update or change the game’s visual elements, like character designs, environments, and animations. +Levels: New levels or redesigns of existing ones to add more challenges or fresh perspectives. +Music: Changes or enhancements to the game’s soundtrack and audio elements. +Optimization: Such as FastROM hacks for SNES games, these are focused on making the game run more efficiently without altering the gameplay itself. +Save/Load: Adding saving functionality, like SRAM, to games that originally lacked this feature. +SFX: These hacks modify the game’s sound effects. +Text: Changes to the game script, including improvements in spelling and grammar or complete rewrites for a different narrative. +Undub: A popular type of romhack that replaces localized voice acting with the original, often changing English back to Japanese. +Unlocker: These hacks unlock features, secret characters, unused languages, and other inaccessible content. + +Each hack in our repository is a testament to the creativity and technical skill of the romhacking community. Whether you\'re looking to revisit a beloved classic with new features or wanting to explore what could have been, our romhacks section is your gateway to a whole new world of gaming possibilities. Just download a hack, apply it to your game, and you\'re ready to experience your favorite games like never before.", + "public" => true, + "publicly_queryable" => true, + "show_ui" => true, + "show_in_rest" => false, + "rest_base" => "", + "rest_controller_class" => "WP_REST_Posts_Controller", + "rest_namespace" => "wp/v2", + "has_archive" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "delete_with_user" => false, + "exclude_from_search" => false, + "capability_type" => "post", + "map_meta_cap" => true, + "hierarchical" => false, + "can_export" => false, + "rewrite" => [ "slug" => "romhacks", "with_front" => true ], + "query_var" => true, + "supports" => [ "title", "editor", "thumbnail", "comments", "author" ], + "taxonomies" => [ "category", "post_tag", "game", "hack-status", "language", "platform", "author-name" ], + "show_in_graphql" => false, + ]); + + /* --- HOMEBREWS --- */ + + $post_types['homebrews'] = new CPT( 'homebrew', "homebrew", [ + "label" => esc_html__( "Homebrew", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Homebrew", "romhackplaza" ), + "singular_name" => esc_html__( "Homebrew", "romhackplaza" ), + ], + "description" => "The Homebrew section of our site is a celebration of creativity and innovation in the gaming world. Here, you\'ll find an impressive collection of fan-made games, lovingly crafted by passionate developers and enthusiasts. These games, often referred to as \"homebrew,\" are original creations that range from tributes to classic titles to entirely new concepts. + +In this vibrant corner of our site, doujins also find their place. Doujin refers to indie games created by small teams or individuals, similar to the concept of fanfiction in literature. These are games born from a deep love of gaming and a desire to contribute something unique to the community. + +Homebrew games cover a wide array of genres and styles, showcasing the diverse talents of their creators. From platformers that nod to the 8-bit era to innovative puzzle games that challenge the norms, each title is a fresh and exciting exploration of what gaming can be. These games aren\'t just about nostalgia; they\'re about pushing boundaries, experimenting with new ideas, and sharing personal visions with the world. + +The beauty of the homebrew section lies in its inclusivity and the sense of community it fosters. It\'s a place where both seasoned developers and novices can showcase their work, learn from each other, and receive feedback from a community of gamers and enthusiasts. Whether you\'re a fan of retro gaming or looking for something entirely new, our homebrew section is sure to have something that piques your interest. + +What\'s more, these games are easily accessible. You can download and play these homebrew titles, experiencing the innovation and dedication of fellow gamers. Each game is a labor of love, a testament to the creativity and technical skill of its creator, and a valuable addition to the world of gaming. So dive in, explore, and be inspired by the remarkable world of homebrew gaming on our site.", + "public" => true, + "publicly_queryable" => true, + "show_ui" => true, + "show_in_rest" => false, + "rest_base" => "", + "rest_controller_class" => "WP_REST_Posts_Controller", + "rest_namespace" => "wp/v2", + "has_archive" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "delete_with_user" => false, + "exclude_from_search" => false, + "capability_type" => "post", + "map_meta_cap" => true, + "hierarchical" => false, + "can_export" => false, + "rewrite" => [ "slug" => "homebrew", "with_front" => true ], + "query_var" => true, + "supports" => [ "title", "editor", "thumbnail", "comments", "author" ], + "taxonomies" => [ "category", "post_tag", "game", "hack-status", "language", "platform", "author-name" ], + "show_in_graphql" => false, + ]); + + /* --- UTILITIES --- */ + + $post_types['utilities'] = new CPT( 'utilities', "utility", [ + "label" => esc_html__( "Utilities", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Utilities", "romhackplaza" ), + "singular_name" => esc_html__( "Utility", "romhackplaza" ), + ], + "description" => "Our Utilities section is the toolkit of the romhacking world. It\'s stocked with an array of specialized tools designed to assist in the creation and implementation of romhacks. These utilities range from software for editing graphics and sound to programs that help modify game code. Whether you\'re a seasoned hacker or just starting, these tools are essential for anyone looking to dive into the intricacies of game modification. They streamline the process, making it more accessible and efficient. Our Utilities section is a testament to the community\'s ingenuity, offering the necessary resources to bring your gaming visions to life.", + "public" => true, + "publicly_queryable" => true, + "show_ui" => true, + "show_in_rest" => false, + "rest_base" => "", + "rest_controller_class" => "WP_REST_Posts_Controller", + "rest_namespace" => "wp/v2", + "has_archive" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "delete_with_user" => false, + "exclude_from_search" => false, + "capability_type" => "post", + "map_meta_cap" => true, + "hierarchical" => false, + "can_export" => false, + "rewrite" => [ "slug" => "utilities", "with_front" => true ], + "query_var" => true, + "supports" => [ "title", "editor", "thumbnail", "comments", "author" ], + "taxonomies" => [ "category", "post_tag", "game", "language", "platform", "author-name", "utility-category", "experience-level", "utility-os" ], + "show_in_graphql" => false, + ] ); + + /* --- DOCUMENTS --- */ + + $post_types['documents'] = new CPT( 'documents', "document", [ + "label" => esc_html__( "Documents", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Documents", "romhackplaza" ), + "singular_name" => esc_html__( "Document", "romhackplaza" ), + ], + "description" => "The Documents section is the knowledge hub of our community. Here, you\'ll find an extensive library of text documentation, rich with insights and instructions on romhacking. These documents are a treasure trove of information, covering everything from basic hacking techniques to advanced concepts in game reverse engineering. Written by experienced members of the romhacking community, these guides are invaluable for anyone looking to understand how games function internally or wanting to create their own hacks. This section is not just about sharing information; it\'s about fostering a culture of learning and collaboration, enabling more gamers to become creators.", + "public" => true, + "publicly_queryable" => true, + "show_ui" => true, + "show_in_rest" => false, + "rest_base" => "", + "rest_controller_class" => "WP_REST_Posts_Controller", + "rest_namespace" => "wp/v2", + "has_archive" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "delete_with_user" => false, + "exclude_from_search" => false, + "capability_type" => "post", + "map_meta_cap" => true, + "hierarchical" => false, + "can_export" => false, + "rewrite" => [ "slug" => "documents", "with_front" => true ], + "query_var" => true, + "supports" => [ "title", "editor", "thumbnail", "comments", "author" ], + "taxonomies" => [ "game", "hack-status", "language", "platform", "author-name", "document-category" ], + "show_in_graphql" => false, + ]); + + /* --- NEWS --- */ + + $post_types['news'] = new CPT( 'news', "news", [ + "label" => esc_html__( "News", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "News", "romhackplaza" ), + "singular_name" => esc_html__( "News", "romhackplaza" ), + ], + "description" => "", + "public" => true, + "publicly_queryable" => true, + "show_ui" => true, + "show_in_rest" => false, + "rest_base" => "", + "rest_controller_class" => "WP_REST_Posts_Controller", + "rest_namespace" => "wp/v2", + "has_archive" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "delete_with_user" => false, + "exclude_from_search" => false, + "capability_type" => "post", + "map_meta_cap" => true, + "hierarchical" => false, + "can_export" => false, + "rewrite" => [ "slug" => "news", "with_front" => true ], + "query_var" => true, + "supports" => [ "title", "editor", "custom-fields", "comments", "author" ], + "show_in_graphql" => false, + ] ); + + /* --- TUTORIALS --- */ + + $post_types['tutorials'] = new CPT( 'tutorials', "tutorial", [ + "label" => esc_html__( "Tutorials", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Tutorials", "romhackplaza" ), + "singular_name" => esc_html__( "Tutorial", "romhackplaza" ), + ], + "description" => "", + "public" => true, + "publicly_queryable" => true, + "show_ui" => true, + "show_in_rest" => true, + "rest_base" => "", + "rest_controller_class" => "WP_REST_Posts_Controller", + "rest_namespace" => "wp/v2", + "has_archive" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "delete_with_user" => false, + "exclude_from_search" => false, + "capability_type" => "post", + "map_meta_cap" => true, + "hierarchical" => false, + "can_export" => false, + "rewrite" => [ "slug" => "tutorials", "with_front" => true ], + "query_var" => true, + "supports" => [ "title", "editor", "thumbnail", "excerpt", "comments", "author" ], + "taxonomies" => [ "category", "post_tag", "game", "platform", "author-name", "experience-level" ], + "show_in_graphql" => false, + ] ); + + /* --- LUA SCRIPTS --- */ + + $post_types['lua_scripts'] = new CPT( 'lua-scripts', "lua-script", [ + "label" => esc_html__( "LUA Scripts", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "LUA Scripts", "romhackplaza" ), + "singular_name" => esc_html__( "LUA Script", "romhackplaza" ), + ], + "description" => "", + "public" => true, + "publicly_queryable" => true, + "show_ui" => true, + "show_in_rest" => true, + "rest_base" => "", + "rest_controller_class" => "WP_REST_Posts_Controller", + "rest_namespace" => "wp/v2", + "has_archive" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "delete_with_user" => false, + "exclude_from_search" => false, + "capability_type" => "post", + "map_meta_cap" => true, + "hierarchical" => false, + "can_export" => false, + "rewrite" => [ "slug" => "lua-scripts", "with_front" => true ], + "query_var" => true, + "supports" => [ "title", "editor", "thumbnail", "comments", "author" ], + "taxonomies" => [ "category", "post_tag", "game", "hack-status", "language", "platform", "author-name", "genre", "lua-modifications" ], + "show_in_graphql" => false, + ] ); + + /* --- REVIEWS --- */ + + $post_types['reviews'] = new CPT( 'reviews', "review", [ + "labels" => [ + "name" => "Reviews", + "singular_name" => "Review", + ], + "public" => true, + "menu_position" => 30, + "menu_icon" => "dashicons-text", + "supports" => [ "title", "editor", "author", "custom-fields" ], + "has_archive" => false, + "capability_type" => "post", + "exclude_from_search" => true, + ] ); + + /* ADD YOUR NEW CUSTOM POST TYPE HERE */ + + /** + * Can't be bind now. + * Settings may not be loaded. + */ + add_action( 'RomhackPlaza\\Init', [ $this, 'add_acf_group_fields' ] ); + + $this->select = (object) $post_types; + return $this;// END_CPT_CREATE + + + + } + + /** + * List every secondary CPTs. + * @return string[] + */ + public function secondary_cpt(): array { + + return ['reviews']; + + } + + /** + * List every primary CPTs based on the secondary ones. + * @return array + */ + public function primary_cpt(): array { + + return $this->select |> get_object_vars(...) + |> ( fn( $arr ) => array_keys( $arr ) ) + |> ( fn( $arr ) => array_diff( $arr, $this->secondary_cpt() ) ); + + } + + public function add_acf_group_fields(): void { + + global $_romhackplaza; + + foreach ( $this->select as $key => $select ) { + $select->setAcfFieldGroupKey( $_romhackplaza->settings->get( $select->name . "_acf_group" ) ); + } + + } + +} \ No newline at end of file diff --git a/src/Registrars/Taxonomies.php b/src/Registrars/Taxonomies.php new file mode 100644 index 0000000..f9406cd --- /dev/null +++ b/src/Registrars/Taxonomies.php @@ -0,0 +1,400 @@ + esc_html__( "Games", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Games", "romhackplaza" ), + "singular_name" => esc_html__( "Game", "romhackplaza" ), + ], + "public" => true, + "publicly_queryable" => true, + "hierarchical" => false, + "show_ui" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "query_var" => true, + "rewrite" => [ 'slug' => 'game', 'with_front' => true, ], + "show_admin_column" => false, + "show_in_rest" => true, + "show_tagcloud" => false, + "rest_base" => "game", + "rest_controller_class" => "WP_REST_Terms_Controller", + "rest_namespace" => "wp/v2", + "show_in_quick_edit" => false, + "sort" => false, + "show_in_graphql" => false, + ] + ); + + $taxonomies['hack_status'] = new Tax( 'hack-status', ["translations", "romhacks", "homebrew"], [ + "label" => esc_html__( "Status", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Status", "romhackplaza" ), + "singular_name" => esc_html__( "Status", "romhackplaza" ), + ], + "public" => true, + "publicly_queryable" => true, + "hierarchical" => false, + "show_ui" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "query_var" => true, + "rewrite" => [ 'slug' => 'hack-status', 'with_front' => true, ], + "show_admin_column" => false, + "show_in_rest" => true, + "show_tagcloud" => false, + "rest_base" => "hack-status", + "rest_controller_class" => "WP_REST_Terms_Controller", + "rest_namespace" => "wp/v2", + "show_in_quick_edit" => false, + "sort" => false, + "show_in_graphql" => false, + ] ); + + $taxonomies['language'] = new Tax( 'language', [ "translations", "romhacks", "homebrew", "documents", "utilities" ], [ + "label" => esc_html__( "Langauges", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Langauges", "romhackplaza" ), + "singular_name" => esc_html__( "Language", "romhackplaza" ), + ], + "public" => true, + "publicly_queryable" => true, + "hierarchical" => false, + "show_ui" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "query_var" => true, + "rewrite" => [ 'slug' => 'language', 'with_front' => true, ], + "show_admin_column" => false, + "show_in_rest" => true, + "show_tagcloud" => false, + "rest_base" => "language", + "rest_controller_class" => "WP_REST_Terms_Controller", + "rest_namespace" => "wp/v2", + "show_in_quick_edit" => false, + "sort" => false, + "show_in_graphql" => false, + ]); + + $taxonomies['platform'] = new Tax( 'platform', [ "translations", "romhacks", "homebrew" ], [ + "label" => esc_html__( "Platforms", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Platforms", "romhackplaza" ), + "singular_name" => esc_html__( "Platform", "romhackplaza" ), + ], + "public" => true, + "publicly_queryable" => true, + "hierarchical" => false, + "show_ui" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "query_var" => true, + "rewrite" => [ 'slug' => 'platform', 'with_front' => true, ], + "show_admin_column" => false, + "show_in_rest" => true, + "show_tagcloud" => false, + "rest_base" => "platform", + "rest_controller_class" => "WP_REST_Terms_Controller", + "rest_namespace" => "wp/v2", + "show_in_quick_edit" => false, + "sort" => false, + "show_in_graphql" => false, + ]); + + $taxonomies['author_name'] = new Tax( 'author-name', [ "translations", "romhacks", "homebrew", "documents", "utilities" ], [ + "label" => esc_html__( "Authors", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Authors", "romhackplaza" ), + "singular_name" => esc_html__( "Author", "romhackplaza" ), + ], + "public" => true, + "publicly_queryable" => true, + "hierarchical" => false, + "show_ui" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "query_var" => true, + "rewrite" => [ 'slug' => 'author-name', 'with_front' => true, ], + "show_admin_column" => false, + "show_in_rest" => true, + "show_tagcloud" => false, + "rest_base" => "author-name", + "rest_controller_class" => "WP_REST_Terms_Controller", + "rest_namespace" => "wp/v2", + "show_in_quick_edit" => false, + "sort" => false, + "show_in_graphql" => false, + ] ); + + $taxonomies['utility_category'] = new Tax( 'utility-category', [ "utilities" ], [ + "label" => esc_html__( "Utility Category", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Utility Category", "romhackplaza" ), + "singular_name" => esc_html__( "Utility Category", "romhackplaza" ), + ], + "public" => true, + "publicly_queryable" => true, + "hierarchical" => false, + "show_ui" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "query_var" => true, + "rewrite" => [ 'slug' => 'utility-category', 'with_front' => true, ], + "show_admin_column" => false, + "show_in_rest" => true, + "show_tagcloud" => false, + "rest_base" => "utility-category", + "rest_controller_class" => "WP_REST_Terms_Controller", + "rest_namespace" => "wp/v2", + "show_in_quick_edit" => false, + "sort" => false, + "show_in_graphql" => false, + ]); + + $taxonomies['experience_level'] = new Tax( 'experience-level', [ "utilities", "documents" ], [ + "label" => esc_html__( "Experience Level", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Experience Level", "romhackplaza" ), + "singular_name" => esc_html__( "Experience Level", "romhackplaza" ), + ], + "public" => true, + "publicly_queryable" => true, + "hierarchical" => false, + "show_ui" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "query_var" => true, + "rewrite" => [ 'slug' => 'experience-level', 'with_front' => true, ], + "show_admin_column" => false, + "show_in_rest" => true, + "show_tagcloud" => false, + "rest_base" => "experience-level", + "rest_controller_class" => "WP_REST_Terms_Controller", + "rest_namespace" => "wp/v2", + "show_in_quick_edit" => false, + "sort" => false, + "show_in_graphql" => false, + ]); + + $taxonomies['utility_os'] = new Tax( 'utility-os', [ "utilities" ], [ + "label" => esc_html__( "Utility OS", "romhackplaza" ), + "labels" =>[ + "name" => esc_html__( "Utility OS", "romhackplaza" ), + "singular_name" => esc_html__( "Utility OS", "romhackplaza" ), + ], + "public" => true, + "publicly_queryable" => true, + "hierarchical" => false, + "show_ui" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "query_var" => true, + "rewrite" => [ 'slug' => 'utility-os', 'with_front' => true, ], + "show_admin_column" => false, + "show_in_rest" => true, + "show_tagcloud" => false, + "rest_base" => "utility-os", + "rest_controller_class" => "WP_REST_Terms_Controller", + "rest_namespace" => "wp/v2", + "show_in_quick_edit" => false, + "sort" => false, + "show_in_graphql" => false, + ]); + + $taxonomies['document_category'] = new Tax( 'document-category', [ "documents" ], [ + "label" => esc_html__( "Document Category", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Document Category", "romhackplaza" ), + "singular_name" => esc_html__( "Document Category", "romhackplaza" ), + ], + "public" => true, + "publicly_queryable" => true, + "hierarchical" => false, + "show_ui" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "query_var" => true, + "rewrite" => [ 'slug' => 'document-category', 'with_front' => true, ], + "show_admin_column" => false, + "show_in_rest" => true, + "show_tagcloud" => false, + "rest_base" => "document-category", + "rest_controller_class" => "WP_REST_Terms_Controller", + "rest_namespace" => "wp/v2", + "show_in_quick_edit" => false, + "sort" => false, + "show_in_graphql" => false, + ]); + + $taxonomies['modifications'] = new Tax( 'modifications', ['romhacks'], [ + "label" => esc_html__( "Modifications", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Modifications", "romhackplaza" ), + "singular_name" => esc_html__( "Modification", "romhackplaza" ), + ], + "public" => true, + "publicly_queryable" => true, + "hierarchical" => false, + "show_ui" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "query_var" => true, + "rewrite" => [ 'slug' => 'modifications', 'with_front' => true, ], + "show_admin_column" => false, + "show_in_rest" => true, + "show_tagcloud" => false, + "rest_base" => "modifications", + "rest_controller_class" => "WP_REST_Terms_Controller", + "rest_namespace" => "wp/v2", + "show_in_quick_edit" => false, + "sort" => false, + "show_in_graphql" => false, + ] ); + + $taxonomies['homebrew_type'] = new Tax( 'homebrew-type', [ "homebrew" ], [ + "label" => esc_html__( "Homebrew Types", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Homebrew Types", "romhackplaza" ), + "singular_name" => esc_html__( "Homebrew Type", "romhackplaza" ), + ], + "public" => true, + "publicly_queryable" => true, + "hierarchical" => false, + "show_ui" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "query_var" => true, + "rewrite" => [ 'slug' => 'homebrew-type', 'with_front' => true, ], + "show_admin_column" => false, + "show_in_rest" => true, + "show_tagcloud" => false, + "rest_base" => "homebrew-type", + "rest_controller_class" => "WP_REST_Terms_Controller", + "rest_namespace" => "wp/v2", + "show_in_quick_edit" => false, + "sort" => false, + "show_in_graphql" => false, + ]); + + $taxonomies['news_category'] = new Tax( 'news-category', [ "news" ], [ + "label" => esc_html__( "News Category", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "News Category", "romhackplaza" ), + "singular_name" => esc_html__( "News Category", "romhackplaza" ), + ], + "public" => true, + "publicly_queryable" => true, + "hierarchical" => false, + "show_ui" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "query_var" => true, + "rewrite" => [ 'slug' => 'news-category', 'with_front' => true, ], + "show_admin_column" => false, + "show_in_rest" => true, + "show_tagcloud" => false, + "rest_base" => "news-category", + "rest_controller_class" => "WP_REST_Terms_Controller", + "rest_namespace" => "wp/v2", + "show_in_quick_edit" => false, + "sort" => false, + "show_in_graphql" => false, + ]); + + $taxonomies['genre'] = new Tax( 'genre', [ "translations", "romhacks", "homebrew", "documents", "utilities" ], [ + "label" => esc_html__( "Genres", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Genres", "romhackplaza" ), + "singular_name" => esc_html__( "Genre", "romhackplaza" ), + ], + "public" => true, + "publicly_queryable" => true, + "hierarchical" => false, + "show_ui" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "query_var" => true, + "rewrite" => [ 'slug' => 'genre', 'with_front' => true, ], + "show_admin_column" => false, + "show_in_rest" => true, + "show_tagcloud" => false, + "rest_base" => "genre", + "rest_controller_class" => "WP_REST_Terms_Controller", + "rest_namespace" => "wp/v2", + "show_in_quick_edit" => false, + "sort" => false, + "show_in_graphql" => false, + ]); + + $taxonomies['tutorial_category'] = new Tax( 'tutorial-category', [ "tutorials" ], [ + "label" => esc_html__( "Tutorial Category", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "Tutorial Category", "romhackplaza" ), + "singular_name" => esc_html__( "Tutorial Category", "romhackplaza" ), + ], + "public" => true, + "publicly_queryable" => true, + "hierarchical" => false, + "show_ui" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "query_var" => true, + "rewrite" => [ 'slug' => 'tutorial-category', 'with_front' => true, ], + "show_admin_column" => false, + "show_in_rest" => true, + "show_tagcloud" => false, + "rest_base" => "tutorial-category", + "rest_controller_class" => "WP_REST_Terms_Controller", + "rest_namespace" => "wp/v2", + "show_in_quick_edit" => false, + "sort" => false, + "show_in_graphql" => false, + ] ); + + $taxonomies['lua_modifications'] = new Tax( 'lua-modifications', [ 'lua-scripts'], [ + "label" => esc_html__( "LUA Modifications", "romhackplaza" ), + "labels" => [ + "name" => esc_html__( "LUA Modifications", "romhackplaza" ), + "singular_name" => esc_html__( "LUA Modification", "romhackplaza" ), + ], + "public" => true, + "publicly_queryable" => true, + "hierarchical" => false, + "show_ui" => true, + "show_in_menu" => true, + "show_in_nav_menus" => true, + "query_var" => true, + "rewrite" => [ 'slug' => 'lua-modifications', 'with_front' => true, ], + "show_admin_column" => false, + "show_in_rest" => true, + "show_tagcloud" => false, + "rest_base" => "lua-modifications", + "rest_controller_class" => "WP_REST_Terms_Controller", + "rest_namespace" => "wp/v2", + "show_in_quick_edit" => false, + "sort" => false, + "show_in_graphql" => false, + ]); + + $this->select = (object) $taxonomies; + return $this; + + } + +} \ No newline at end of file diff --git a/src/Script.php b/src/Script.php new file mode 100644 index 0000000..bff3b82 --- /dev/null +++ b/src/Script.php @@ -0,0 +1,134 @@ +type = $type; + $this->handle = $handle; + $this->source = $source; + $this->deps = $deps; + + /** + * Edit version number. + * Like if we want to put plugin/theme version number. + * + * @since 0.0.1a + * + * @param string|bool - Current version number or false + * @return string|bool + */ + $this->version = apply_filters( 'RomhackPlaza\\Theme\\Scripts\\Version', $version ); + + $this->media = $media; + $this->args = $args; + + return $this; + + } + + /** + * Enqueue current script. + * @return $this + */ + public function enqueue(): Script { + + switch ( $this->type ) { + case Script_Type::JS: + wp_enqueue_script( $this->handle, $this->source, $this->deps, $this->version, $this->args ); + break; + case Script_Type::CSS: + wp_enqueue_style( $this->handle, $this->source, $this->deps, $this->version, $this->media ); + break; + } + + return $this; + + } + + /** + * Add some data to the script. + * + * @param string $key__or_css_data + * @param string $value + * @return $this + */ + public function add_data( string $key__or_css_data, string $value = "" ): Script{ + + if ( $this->type == Script_Type::JS && $value != "" ) + wp_script_add_data( $this->handle, $key__or_css_data, $value ); + else if( $this->type == Script_Type::CSS ) + wp_add_inline_style( $this->handle, $key__or_css_data ); + + return $this; + + } + + /** + * Add localisation to the script. + * @param string $object_name + * @param array $l10n + * @return $this + */ + public function add_localize( string $object_name, array $l10n ): Script{ + if ( $this->type == Script_Type::JS ) + wp_localize_script( $this->handle, $object_name, $l10n ); + return $this; + } + +} \ No newline at end of file diff --git a/src/Script_Type.php b/src/Script_Type.php new file mode 100644 index 0000000..d15f211 --- /dev/null +++ b/src/Script_Type.php @@ -0,0 +1,6 @@ +defaults = [ + 'nsfw_tag_id' => '', + 'complete_submissions_form_page_ids' => '', + 'simple_submissions_form_page_ids' => '', + 'translations_acf_group' => '', + 'romhacks_acf_group' => '', + 'homebrew_acf_group' => '', + 'utilities_acf_group' => '', + 'documents_acf_group' => '', + 'lua_scripts_acf_group' => '', + 'tutorials_acf_group' => '', + 'news_acf_group' => '', + 'reviews_acf_group' => '', + 'public_edit_disabled_tag_id' => '', + 'discord_webhook_romhacks_translations' => '', + 'discord_webhook_global' => '', + ]; + + $this->refresh(); + + add_action( 'admin_init', [ $this, '_register_settings' ] ); + + } + + /** + * Refresh from database option values. + * @return void + */ + public function refresh () { + + $this->options = wp_parse_args( + get_option( 'romhackplaza_plugin_options', [] ), + $this->defaults + ); + + } + + /** + * Get an option. + * @param string $option_name + * @return mixed - False if the option name doesn't exist. + */ + public function get( string $option_name ): mixed { + + if( isset( $this->options[ $option_name ] ) ) + return $this->options[ $option_name ]; + + return false; + + } + + /** + * Get a default option. + * @param string $option_name + * @return mixed - False if the option name doesn't exist. + */ + public function get_default( string $option_name ): mixed { + if( isset( $this->defaults[ $option_name ] ) ) + return $this->defaults[ $option_name ]; + return false; + } + + public function _register_settings (): void { + + register_setting( 'romhackplaza_plugin_options_group', 'romhackplaza_plugin_options' ); + + } + +} diff --git a/src/Snippets.php b/src/Snippets.php new file mode 100644 index 0000000..2c49cea --- /dev/null +++ b/src/Snippets.php @@ -0,0 +1,8 @@ + { + + let nb_params = 0; + function add_arg( e: Event ) { + + nb_params += 1; + + let html_content = document.createElement("div"); + html_content.id = "arg_" + ( nb_params - 1 ).toString(); + + let name_content = document.createElement( "input" ); + name_content.type = "text"; + name_content.name = "arg_name_" + ( nb_params - 1 ).toString(); + name_content.id = "arg_name_" + ( nb_params - 1 ).toString(); + name_content.setAttribute( 'required', "required" ); + + let value_content = document.createElement( "input" ); + value_content.type = "text"; + value_content.name = "arg_value_" + ( nb_params - 1 ).toString(); + value_content.id = "arg_value_" + ( nb_params - 1 ).toString(); + value_content.setAttribute( 'required', "required" ); + + html_content.appendChild( name_content ); + html_content.appendChild( value_content ); + html_content.appendChild( document.createElement( "hr" ) ) + + document.getElementById( "arguments" )?.appendChild( html_content ); + + } + + function _execute_script( e: Event ){ + + e.preventDefault(); + // @ts-ignore + let select = document.getElementById( "script_select" ).value ?? "none"; + if( select == null || select == "none" || select == "" ) + return; + + const XML: XMLHttpRequest = new XMLHttpRequest(); + XML.open( 'POST', _romhackplaza_admin_scripts.execute_url ); + + let form_data = new FormData(); + for( let i = 0; i < nb_params; i++ ){ + + // @ts-ignore + let name = document.getElementById( 'arg_name_' + i.toString() ).value; + // @ts-ignore + let value = document.getElementById( 'arg_value_' + i.toString()).value; + form_data.append( name, value ); + + } + + form_data.append( "script", select ); + form_data.append( "_wpnonce", _romhackplaza_admin_scripts.execute_nonce ); + form_data.append( "action", "load_admin_script" ); + + XML.onreadystatechange = function(){ + if(XML.readyState === XMLHttpRequest.DONE && XML.status === 200){ + document.getElementById( 'script-response')!.innerHTML = XML.responseText; + } + } + XML.send( form_data ); + + } + + document.getElementById( 'add_args' )?.addEventListener( 'click', add_arg ); + document.getElementById( 'script-loader' )?.addEventListener( 'submit', _execute_script ); + +}) \ No newline at end of file diff --git a/ts/admin/send-notification/index.ts b/ts/admin/send-notification/index.ts new file mode 100644 index 0000000..3c19edc --- /dev/null +++ b/ts/admin/send-notification/index.ts @@ -0,0 +1,113 @@ +import { format_state_for_select2 } from "../../ajax_requests"; +declare const _romhackplaza_send_notification: any; +declare const romhackplaza_modal_notifications: any; +declare function romhackplaza_manage_modal( a: object, b: string|undefined, c: string|undefined, d: string|undefined, e: string|undefined ): void; +declare const tinyMCE: any; + +jQuery(document).ready(function( $: any ) { + + $("#recipients").select2({ + placeholder: "Search an user", + ajax: { + url: _romhackplaza_send_notification.fetch_url, + dataType: "json", + delay: 250, + data: function (params: any) { + return { + q: params.term, + action: "search_user", + _wpnonce: _romhackplaza_send_notification.fetch_nonce + }; + }, + processResults: function (results: any) { + return { + results: results + } + }, + cache: true + }, + minimumInputLength: 1, + templateResult: format_state_for_select2, + templateSelection: format_state_for_select2 + }); + + $( "#send-manual-notification" ).on( 'submit', function( e: Event ){ + + e.preventDefault(); + let type: string = $("#type_select").val() || "none"; + + if( type === "none" ){ + romhackplaza_manage_modal( romhackplaza_modal_notifications, "block", "Submit error", "A type is required.", "" ); + return; + } + + let anonymous_sender: string = $( "#anonymous_sender" ).is( ':checked' ) ? "yes" : "no"; + let all_users: string = $( "#recipients_all" ).is( ':checked' ) ? "yes" : "no"; + if( all_users !== "yes" ){ + + // Defined number of users. + let recipients_list: any = $( "#recipients" ).val(); + if( recipients_list.length <= 0 ){ + romhackplaza_manage_modal( romhackplaza_modal_notifications, "block", "Submit error", "A user is required.", "" ); + return; + } + + } else { + let recipients_list: any = []; + } + + let editor_content: string; + if( tinyMCE.activeEditor && !tinyMCE.activeEditor.isHidden() ) { + // Visual mode. + editor_content = tinyMCE.get( 'postContent' ).getContent(); + } else { + // @ts-ignore + // Text mode. + editor_content = document.getElementById( 'postContent' ).value; + } + + if( editor_content === "" ){ + romhackplaza_manage_modal( romhackplaza_modal_notifications, "block", "Submit error", "Content is required.", "" ); + return; + } + + // @ts-ignore + const form_data: FormData = new FormData( this ); + form_data.append( "action", "submit_notification" ); + form_data.append( "content", editor_content ); + form_data.append( "_wpnonce", _romhackplaza_send_notification.submit_nonce ); + + $.ajax({ + type: "POST", + url: _romhackplaza_send_notification.fetch_url, + data: form_data, + processData: false, + contentType: false, + success: function( response: any ) { + + let real_response: any; + + if( typeof response === "string" ) { + try { + real_response = JSON.parse(response); + } catch( e ) { + console.error( e, response ); + return; + } + } else { + real_response = response; + } + + if( real_response != "-1" && real_response?.success === true ) { + romhackplaza_manage_modal( romhackplaza_modal_notifications, "block", "Submit successful", "Notification sent.", "" ); + return; + } else { + romhackplaza_manage_modal( romhackplaza_modal_notifications, "block", "Submit error", real_response?.message ?? real_response, "" ); + return; + } + + } + }) + }); + +}); \ No newline at end of file diff --git a/ts/admin/widgets/rhpz-url/class-rhpz-url.ts b/ts/admin/widgets/rhpz-url/class-rhpz-url.ts new file mode 100644 index 0000000..907db47 --- /dev/null +++ b/ts/admin/widgets/rhpz-url/class-rhpz-url.ts @@ -0,0 +1,50 @@ +declare const _romhackplaza_rhpz_url: any; + +export class RhpzUrl { + + container: HTMLElement; + form_field_url: HTMLInputElement|null; + form: HTMLElement|null; + output: HTMLElement|null; + + constructor( selector: any ){ + + this.form_field_url = null; + this.form = null; + this.output = null; + + this.container = document.querySelector( selector ); + if ( !this.container ) + return; + + this.form_field_url = this.container.querySelector( '#FormUrl' ); + this.form = this.container; + this.output = this.container.querySelector( '#Create-A-RHPZ-Url-Response' ); + + this.events(); + } + + events() { + + this.form!.onsubmit = ( event ) => this.submit_request( event ); + + } + + async submit_request( e: Event ){ + + e.preventDefault(); + let url = this.form_field_url!.value; + url = url.replace("https://", ""); + url = url.replace("http://", ""); + + const xhr = new XMLHttpRequest(); + xhr.open( 'GET', _romhackplaza_rhpz_url.create_url + url, true ); + xhr.onload = () => { + this.output!.innerHTML = '' + xhr.responseText + ''; + } + + xhr.send(); + + } + +} \ No newline at end of file diff --git a/ts/admin/widgets/rhpz-url/index.ts b/ts/admin/widgets/rhpz-url/index.ts new file mode 100644 index 0000000..83ef765 --- /dev/null +++ b/ts/admin/widgets/rhpz-url/index.ts @@ -0,0 +1,5 @@ +import { RhpzUrl } from "./class-rhpz-url"; + +document.addEventListener( 'DOMContentLoaded', () => { + new RhpzUrl( '#Create-A-RHPZ-Url-Form' ); +}) \ No newline at end of file diff --git a/ts/ajax_requests.ts b/ts/ajax_requests.ts new file mode 100644 index 0000000..9a9cbe5 --- /dev/null +++ b/ts/ajax_requests.ts @@ -0,0 +1,15 @@ +export function decode_entities_for_select2( encoded: any ){ + + let text_area = document.createElement("textarea"); + text_area.innerHTML = encoded; + return text_area.value; + +} + +export function format_state_for_select2( state: any ){ + + if( !state.id ) + return state.text; + return jQuery('' + decode_entities_for_select2( state.text ) + ''); + +} \ No newline at end of file diff --git a/ts/globals.d.ts b/ts/globals.d.ts new file mode 100644 index 0000000..a93f972 --- /dev/null +++ b/ts/globals.d.ts @@ -0,0 +1,3 @@ +/// +declare const $: any; +declare const jQuery: any; \ No newline at end of file diff --git a/ts/manage-notifications/index.ts b/ts/manage-notifications/index.ts new file mode 100644 index 0000000..e697623 --- /dev/null +++ b/ts/manage-notifications/index.ts @@ -0,0 +1,135 @@ +declare const _romhackplaza_manage_notifications: any; +declare const romhackplaza_modal_notifications: any; +declare function romhackplaza_manage_modal( a: object, b: string|undefined, c: string|undefined, d: string|undefined, e: string|undefined ): void; + +document.addEventListener("DOMContentLoaded", () => { + + function _change_event_to_reload(){ + + romhackplaza_modal_notifications.close_button.onclick = () => { + romhackplaza_manage_modal( romhackplaza_modal_notifications, "none", "", "", "" ); + window.location.reload(); + } + + window.addEventListener( 'click', (e) => { if( e.target === romhackplaza_modal_notifications.modal ) window.location.reload(); } ); + + } + + function _reply_to_message( e: Event ) { + + const button = e.currentTarget as HTMLElement; + const html_content: HTMLFormElement = document.createElement( 'form' ); + html_content.id = "reply_message_form" + const textarea: HTMLTextAreaElement = document.createElement( 'textarea' ); + textarea.id = "reply_message_content"; + + const submit_button: HTMLElement = document.createElement( 'input' ); + submit_button.setAttribute( 'type', 'submit' ); + submit_button.setAttribute( 'value', 'Send' ); + + html_content.appendChild( textarea ); + html_content.appendChild( submit_button ); + + romhackplaza_manage_modal( romhackplaza_modal_notifications, "block", "Reply to a message", "", html_content.outerHTML ); + + document.getElementById( 'reply_message_form' )!.onsubmit = function( e: SubmitEvent ) { + + e.preventDefault(); + + // @ts-ignore + let txt = document.getElementById( 'reply_message_content' ).value; + romhackplaza_manage_modal( romhackplaza_modal_notifications, "none", "", "", " " ); + + if ( txt == "" || txt == "none" || txt == "undefined" ){ + romhackplaza_manage_modal( romhackplaza_modal_notifications, "block", "Submit error", "A content is required", "" ); + _change_event_to_reload(); + return; + } + txt = "Reply to a notification " + button.getAttribute( 'data-id' ) + "\n\n" + txt; + + + let form_data: FormData = new FormData(); + form_data.append( 'notification', button.getAttribute( 'data-id' ) as string ) + form_data.append( 'content', txt ); + form_data.append( '_wpnonce', _romhackplaza_manage_notifications.reply_nonce ); + form_data.append( 'action', 'notifications_reply' ); + + const XML: XMLHttpRequest = new XMLHttpRequest(); + XML.open( 'POST', _romhackplaza_manage_notifications.manage_url, true ); + XML.onreadystatechange = function () { + if (XML.readyState === XMLHttpRequest.DONE && XML.status === 200) { + let real_response: any; + try { + real_response = JSON.parse(XML.responseText); + } catch( e ) { + console.error( e, XML.responseText ); + return; + } + if( real_response != "-1" && real_response?.success == true ) { + romhackplaza_manage_modal( romhackplaza_modal_notifications, "block", "Submit successful", "Notification sent.", "" ); + _change_event_to_reload(); + return; + } else { + romhackplaza_manage_modal( romhackplaza_modal_notifications, "block", "Submit error", real_response?.message ?? real_response, "" ); + _change_event_to_reload(); + return; + } + + } + } + + XML.send( form_data ); + + + } + + } + + function _mark_as_read( e: Event ) { + + const button = e.currentTarget as HTMLElement; + + let form_data: FormData = new FormData(); + form_data.append( 'notification', button.getAttribute( 'data-id' ) as string ) + form_data.append( '_wpnonce', _romhackplaza_manage_notifications.mark_as_read_nonce ); + form_data.append( 'action', 'notifications_unread_to_read' ); + + const XML: XMLHttpRequest = new XMLHttpRequest(); + XML.open( 'POST', _romhackplaza_manage_notifications.manage_url, true ); + XML.onreadystatechange = function () { + if (XML.readyState === XMLHttpRequest.DONE && XML.status === 200) { + let real_response: any; + try { + real_response = JSON.parse(XML.responseText); + } catch( e ) { + console.error( e, XML.responseText ); + return; + } + if( real_response != "-1" && real_response?.success == true ) { + document.getElementById( 'new-info-' + button.getAttribute( 'data-id' ) as string )?.remove(); + button.parentElement?.remove(); + return; + } else { + alert( "An error occurred : " + ( real_response?.message ?? real_response ) ); + return; + } + + } + } + XML.send( form_data ); + + } + + // Replies. + const replies_buttons: NodeList = document.querySelectorAll('.reply-message'); + for (const reply_button of replies_buttons) { + reply_button.addEventListener("click", _reply_to_message ); + } + + const mark_as_read_buttons: NodeList = document.querySelectorAll('.mark-as-read'); + for (const mark_as_read_button of mark_as_read_buttons) { + mark_as_read_button.addEventListener("click", _mark_as_read ); + } + + +}) \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..81be5a6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,47 @@ +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + // "rootDir": "./src", + // "outDir": "./dist", + + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "esnext", + "target": "esnext", + "types": [], + // For nodejs: + // "lib": ["esnext"], + // "types": ["node"], + // and npm install -D @types/node + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + + // Recommended Options + "strict": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + }, + "include": [ + "ts" + ] +} diff --git a/views/admin/pages/admin-scripts.twig b/views/admin/pages/admin-scripts.twig new file mode 100644 index 0000000..6353535 --- /dev/null +++ b/views/admin/pages/admin-scripts.twig @@ -0,0 +1,42 @@ +{% extends '@plugin/admin/pages/base.twig' %} + +{% block title %} +

{{ __('Admin Scripts', 'romhackplaza') }}

+{% endblock %} + +{% block page %} + +
+ + + + +
+
+
+ {{ __( 'Name:', 'romhackplaza') }} + {{ __( 'Value:', 'romhackplaza') }} +
+
+ +
+ + +
+ +

{{ __( 'Script response', 'romhackplaza') }}

+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/views/admin/pages/base.twig b/views/admin/pages/base.twig new file mode 100644 index 0000000..19a825e --- /dev/null +++ b/views/admin/pages/base.twig @@ -0,0 +1,9 @@ +
+ + {% block title %} + {% endblock %} + + {% block page %} + {% endblock %} + +
\ No newline at end of file diff --git a/views/admin/pages/main.twig b/views/admin/pages/main.twig new file mode 100644 index 0000000..a479163 --- /dev/null +++ b/views/admin/pages/main.twig @@ -0,0 +1,5 @@ +{% extends '@plugin/admin/pages/base.twig' %} + +{% block title %} +

{{ __('RomhackPlaza - Admin', 'romhackplaza') }}

+{% endblock %} \ No newline at end of file diff --git a/views/admin/pages/send-notification.twig b/views/admin/pages/send-notification.twig new file mode 100644 index 0000000..6a82681 --- /dev/null +++ b/views/admin/pages/send-notification.twig @@ -0,0 +1,43 @@ +{% extends '@plugin/admin/pages/base.twig' %} + +{% block title %} +

{{ __('Send Notification', 'romhackplaza') }}

+{% endblock %} + +{% block page %} + +
+ + + + +
+ {{ __('Anonymous notification?', 'romhackplaza') }} +
+ + + +
All users ? +
+ + {{ fn( 'wp_editor', "", 'postContent' ) }} +
+ +
+ +{% endblock %} \ No newline at end of file diff --git a/views/node/submission-accordion.twig b/views/node/submission-accordion.twig new file mode 100644 index 0000000..8d0c556 --- /dev/null +++ b/views/node/submission-accordion.twig @@ -0,0 +1,12 @@ +
+
+
+ + {{ pretty_title }} +
+
+
+
+ {{ content|raw }} +
+
\ No newline at end of file diff --git a/vite-configs/vite.admin-scripts.config.js b/vite-configs/vite.admin-scripts.config.js new file mode 100644 index 0000000..b63a219 --- /dev/null +++ b/vite-configs/vite.admin-scripts.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite'; +import path from 'path'; + +export default defineConfig({ + root: path.resolve(__dirname, '..'), + build: { + outDir: 'assets/js/admin', + emptyOutDir: false, + + rollupOptions: { + input: 'ts/admin/admin-scripts/index.ts', + output: { + entryFileNames: `admin-scripts.js`, + }, + }, + }, +}); \ No newline at end of file diff --git a/vite-configs/vite.manage-notifications.config.js b/vite-configs/vite.manage-notifications.config.js new file mode 100644 index 0000000..c3dde7f --- /dev/null +++ b/vite-configs/vite.manage-notifications.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite'; +import path from 'path'; + +export default defineConfig({ + root: path.resolve(__dirname, '..'), + build: { + outDir: 'assets/js', + emptyOutDir: false, + + rollupOptions: { + input: 'ts/manage-notifications/index.ts', + output: { + entryFileNames: `manage-notifications.js`, + }, + }, + }, +}); \ No newline at end of file diff --git a/vite-configs/vite.rhpz-url.config.js b/vite-configs/vite.rhpz-url.config.js new file mode 100644 index 0000000..f3be611 --- /dev/null +++ b/vite-configs/vite.rhpz-url.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite'; +import path from 'path'; + +export default defineConfig({ + root: path.resolve(__dirname, '..'), + build: { + outDir: 'assets/js/admin/widgets', + emptyOutDir: false, + + rollupOptions: { + input: 'ts/admin/widgets/rhpz-url/index.ts', + output: { + entryFileNames: `rhpz-url.js`, + }, + }, + }, +}); \ No newline at end of file diff --git a/vite-configs/vite.send-notification.config.js b/vite-configs/vite.send-notification.config.js new file mode 100644 index 0000000..3e1865e --- /dev/null +++ b/vite-configs/vite.send-notification.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite'; +import path from 'path'; + +export default defineConfig({ + root: path.resolve(__dirname, '..'), + build: { + outDir: 'assets/js/admin', + emptyOutDir: false, + + rollupOptions: { + input: 'ts/admin/send-notification/index.ts', + output: { + entryFileNames: `send-notification.js`, + }, + }, + }, +}); \ No newline at end of file