├── .ci └── update-libraries.php ├── .editorconfig ├── .gitignore ├── .php-cs-fixer.dist.php ├── .wp-org-assets ├── HTMX-logo-1600x1600.jpg ├── banner-1544x500.png ├── banner-772x250.png ├── icon-1024x1024.png ├── icon-128x128.png ├── icon-256x256.png └── screenshot-1.jpg ├── CHANGELOG.md ├── FAQ.md ├── LICENSE ├── README.md ├── README.txt ├── SECURITY.md ├── api-for-htmx.php ├── assets ├── index.html └── js │ ├── index.html │ └── libs │ ├── LICENSE-alpinejs.md │ ├── LICENSE-datastar.md │ ├── LICENSE-htmx.md │ ├── LICENSE-hyperscript.md │ ├── _hyperscript.js │ ├── _hyperscript.min.js │ ├── alpine-ajax.min.js │ ├── alpinejs.js │ ├── alpinejs.min.js │ ├── datastar.js │ ├── datastar.min.js │ ├── htmx-extensions │ ├── ajax-header.js │ ├── alpine-morph.js │ ├── class-tools.js │ ├── client-side-templates.js │ ├── debug.js │ ├── disable-element.js │ ├── event-header.js │ ├── head-support.js │ ├── include-vals.js │ ├── json-enc.js │ ├── loading-states.js │ ├── method-override.js │ ├── morphdom-swap.js │ ├── multi-swap.js │ ├── path-deps.js │ ├── path-params.js │ ├── preload.js │ ├── remove-me.js │ ├── response-targets.js │ ├── restored.js │ ├── sse.js │ └── ws.js │ ├── htmx.js │ ├── htmx.min.js │ └── index.html ├── composer.json ├── composer.lock ├── hypermedia ├── alpine-ajax-demo.hm.php ├── datastar-demo.hm.php ├── demos-index.hm.php ├── htmx-demo.hm.php ├── index.html └── noswap │ ├── alpine-ajax-demo.hm.php │ ├── datastar-demo.hm.php │ ├── htmx-demo.hm.php │ └── index.html ├── includes ├── helpers.php └── index.html ├── index.html ├── package-lock.json ├── package.json ├── src ├── Admin │ ├── Activation.php │ ├── Options.php │ ├── WPSettingsOptions.php │ └── index.html ├── Assets.php ├── Compatibility.php ├── Config.php ├── Main.php ├── Render.php ├── Router.php ├── Theme.php └── index.html ├── uninstall.php └── vendor ├── autoload.php └── composer ├── ClassLoader.php ├── InstalledVersions.php ├── LICENSE ├── autoload_classmap.php ├── autoload_files.php ├── autoload_namespaces.php ├── autoload_psr4.php ├── autoload_real.php ├── autoload_static.php ├── installed.json ├── installed.php └── platform_check.php /.ci/update-libraries.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 'htmx.min.js', 126 | 'hyperscript' => '_hyperscript.min.js', 127 | 'alpinejs' => 'alpinejs.min.js', 128 | 'alpine_ajax' => 'alpine-ajax.min.js', 129 | 'datastar' => 'datastar.min.js', 130 | ]; 131 | 132 | $filename = $filename_map[$name] ?? "$name.min.js"; 133 | $output_path = ASSETS_DIR . '/' . $filename; 134 | 135 | download_file($url, $output_path); 136 | } 137 | 138 | /** 139 | * Download HTMX extension 140 | */ 141 | function download_extension($name, $url) { 142 | echo "\n🔌 Downloading HTMX extension: $name\n"; 143 | 144 | ensure_dir(EXTENSIONS_DIR); 145 | 146 | $filename = "$name.js"; 147 | $output_path = EXTENSIONS_DIR . '/' . $filename; 148 | 149 | download_file($url, $output_path); 150 | } 151 | 152 | /** 153 | * Parse command line arguments 154 | */ 155 | function parse_args($argv) { 156 | $target_library = null; 157 | 158 | for ($i = 1; $i < count($argv); $i++) { 159 | $arg = $argv[$i]; 160 | 161 | if ($arg === '--all') { 162 | $target_library = 'all'; 163 | } elseif (strpos($arg, '--library=') === 0) { 164 | $target_library = substr($arg, 10); 165 | } elseif ($arg === '--library' && isset($argv[$i + 1])) { 166 | $target_library = $argv[$i + 1]; 167 | $i++; // Skip next argument 168 | } 169 | } 170 | 171 | return $target_library; 172 | } 173 | 174 | /** 175 | * Main download function 176 | */ 177 | function download_libraries($target_library = null) { 178 | try { 179 | echo "🔍 Getting CDN URLs...\n"; 180 | 181 | $cdn_urls = get_cdn_urls(); 182 | $core_count = count($cdn_urls) - (isset($cdn_urls['htmx_extensions']) ? 1 : 0); 183 | $extensions_count = isset($cdn_urls['htmx_extensions']) ? count($cdn_urls['htmx_extensions']) : 0; 184 | 185 | echo "✅ Found $core_count core libraries and $extensions_count HTMX extensions\n"; 186 | 187 | if ($target_library === 'htmx-extensions') { 188 | // Download all HTMX extensions 189 | echo "\n🚀 Downloading all HTMX extensions...\n"; 190 | if (isset($cdn_urls['htmx_extensions'])) { 191 | foreach ($cdn_urls['htmx_extensions'] as $name => $config) { 192 | download_extension($name, $config['url']); 193 | } 194 | } 195 | } elseif ($target_library && $target_library !== 'all') { 196 | // Download specific library 197 | if (isset($cdn_urls[$target_library])) { 198 | download_core_library($target_library, $cdn_urls[$target_library]['url']); 199 | } else { 200 | throw new Exception("Library '$target_library' not found in CDN URLs"); 201 | } 202 | } else { 203 | // Download all libraries 204 | echo "\n🚀 Downloading all libraries...\n"; 205 | 206 | // Download core libraries 207 | foreach ($cdn_urls as $name => $config) { 208 | if ($name !== 'htmx_extensions') { 209 | download_core_library($name, $config['url']); 210 | } 211 | } 212 | 213 | // Download HTMX extensions 214 | if (isset($cdn_urls['htmx_extensions'])) { 215 | foreach ($cdn_urls['htmx_extensions'] as $name => $config) { 216 | download_extension($name, $config['url']); 217 | } 218 | } 219 | } 220 | 221 | echo "\n🎉 All downloads completed successfully!\n"; 222 | 223 | } catch (Exception $e) { 224 | echo "\n❌ Error during download: " . $e->getMessage() . "\n"; 225 | exit(1); 226 | } 227 | } 228 | 229 | // Main execution 230 | if (php_sapi_name() === 'cli') { 231 | echo "🔽 HTMX API WordPress Plugin - Library Downloader (PHP)\n"; 232 | echo "====================================================\n\n"; 233 | 234 | $target_library = parse_args($argv); 235 | 236 | if ($target_library) { 237 | echo "🎯 Target: " . ($target_library === 'all' ? 'All libraries' : $target_library) . "\n"; 238 | } else { 239 | echo "🎯 Target: All libraries (default)\n"; 240 | } 241 | 242 | download_libraries($target_library); 243 | } 244 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = space 15 | 16 | [*.yml] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | 23 | [{*.txt,wp-config-sample.php}] 24 | end_of_line = crlf 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ./wp-org-assets/htmx-api-wp-logo.psd 2 | /node_modules/ 3 | deploy.sh 4 | deploy-notag.sh 5 | TODO.md 6 | .wp-org-assets/htmx-api-wp-logo.psd 7 | rules-wordpress.md 8 | .php-cs-fixer.cache 9 | vendor/ 10 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | true, 12 | '@PER-CS' => true, 13 | '@PHP82Migration' => true, 14 | 'array_syntax' => ['syntax' => 'short'], 15 | 'binary_operator_spaces' => [ 16 | 'default' => 'single_space', 17 | 'operators' => ['=>' => null] 18 | ], 19 | 'blank_line_after_namespace' => true, 20 | 'blank_line_after_opening_tag' => true, 21 | 'blank_line_before_statement' => [ 22 | 'statements' => ['return'] 23 | ], 24 | 'single_space_around_construct' => true, 25 | 'control_structure_braces' => true, 26 | 'braces_position' => true, 27 | 'control_structure_continuation_position' => true, 28 | 'declare_parentheses' => true, 29 | 'statement_indentation' => true, 30 | 'no_multiple_statements_per_line' => true, 31 | 'cast_spaces' => true, 32 | 'class_attributes_separation' => [ 33 | 'elements' => [ 34 | 'method' => 'one', 35 | 'trait_import' => 'none' 36 | ] 37 | ], 38 | 'class_definition' => true, 39 | 'concat_space' => [ 40 | 'spacing' => 'one' 41 | ], 42 | 'declare_equal_normalize' => true, 43 | 'elseif' => true, 44 | 'encoding' => true, 45 | 'full_opening_tag' => true, 46 | 'fully_qualified_strict_types' => true, 47 | 'function_declaration' => true, 48 | 'type_declaration_spaces' => true, 49 | 'heredoc_to_nowdoc' => true, 50 | 'include' => true, 51 | 'increment_style' => ['style' => 'post'], 52 | 'indentation_type' => true, 53 | 'linebreak_after_opening_tag' => true, 54 | 'line_ending' => true, 55 | 'lowercase_cast' => true, 56 | 'constant_case' => true, 57 | 'lowercase_keywords' => true, 58 | 'lowercase_static_reference' => true, 59 | 'magic_method_casing' => true, 60 | 'magic_constant_casing' => true, 61 | 'method_argument_space' => true, 62 | 'native_function_casing' => true, 63 | 'no_alias_functions' => true, 64 | 'no_extra_blank_lines' => [ 65 | 'tokens' => [ 66 | 'extra', 67 | 'throw', 68 | 'use' 69 | ] 70 | ], 71 | 'no_blank_lines_after_class_opening' => true, 72 | 'no_blank_lines_after_phpdoc' => true, 73 | 'no_closing_tag' => true, 74 | 'no_empty_phpdoc' => true, 75 | 'no_empty_statement' => true, 76 | 'no_leading_import_slash' => true, 77 | 'no_leading_namespace_whitespace' => true, 78 | 'no_mixed_echo_print' => [ 79 | 'use' => 'echo' 80 | ], 81 | 'no_multiline_whitespace_around_double_arrow' => true, 82 | 'multiline_whitespace_before_semicolons' => [ 83 | 'strategy' => 'no_multi_line' 84 | ], 85 | 'no_short_bool_cast' => true, 86 | 'no_singleline_whitespace_before_semicolons' => true, 87 | 'no_spaces_after_function_name' => true, 88 | 'no_spaces_around_offset' => true, 89 | 'spaces_inside_parentheses' => true, 90 | 'no_trailing_comma_in_singleline' => true, 91 | 'no_trailing_whitespace' => true, 92 | 'no_trailing_whitespace_in_comment' => true, 93 | 'no_unneeded_control_parentheses' => true, 94 | 'no_unreachable_default_argument_value' => true, 95 | 'no_useless_return' => true, 96 | 'no_whitespace_before_comma_in_array' => true, 97 | 'no_whitespace_in_blank_line' => true, 98 | 'normalize_index_brace' => true, 99 | 'not_operator_with_successor_space' => false, 100 | 'object_operator_without_whitespace' => true, 101 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 102 | 'phpdoc_indent' => true, 103 | 'general_phpdoc_tag_rename' => true, 104 | 'phpdoc_inline_tag_normalizer' => true, 105 | 'phpdoc_tag_type' => true, 106 | 'phpdoc_no_access' => true, 107 | 'phpdoc_no_package' => true, 108 | 'phpdoc_no_useless_inheritdoc' => true, 109 | 'phpdoc_scalar' => true, 110 | 'phpdoc_single_line_var_spacing' => true, 111 | 'phpdoc_summary' => true, 112 | 'phpdoc_to_comment' => true, 113 | 'phpdoc_trim' => true, 114 | 'phpdoc_types' => true, 115 | 'phpdoc_var_without_name' => true, 116 | 'psr_autoloading' => true, 117 | 'self_accessor' => true, 118 | 'short_scalar_cast' => true, 119 | 'simplified_null_return' => false, 120 | 'single_blank_line_at_eof' => true, 121 | //'single_blank_line_before_namespace' => true, 122 | 'single_class_element_per_statement' => true, 123 | 'single_import_per_statement' => true, 124 | 'single_line_after_imports' => true, 125 | 'single_line_comment_style' => [ 126 | 'comment_types' => ['hash'] 127 | ], 128 | 'single_quote' => true, 129 | 'space_after_semicolon' => true, 130 | 'standardize_not_equals' => true, 131 | 'switch_case_semicolon_to_colon' => true, 132 | 'switch_case_space' => true, 133 | 'ternary_operator_spaces' => true, 134 | 'trailing_comma_in_multiline' => true, 135 | 'trim_array_spaces' => true, 136 | 'unary_operator_spaces' => true, 137 | 'visibility_required' => [ 138 | 'elements' => ['method', 'property'] 139 | ], 140 | 'whitespace_after_comma_in_array' => true, 141 | 'no_unused_imports' => true, 142 | ]; 143 | 144 | $finder = PhpCsFixer\Finder::create() 145 | ->exclude([ 146 | '.wp-org-assets', 147 | 'vendor', 148 | 'node_modules', 149 | 'assets/js/libs', 150 | 'languages' 151 | ]) 152 | ->name('*.php') 153 | ->notName([ 154 | 'composer.lock', 155 | '*.js', 156 | '*.css' 157 | ]) 158 | ->ignoreDotFiles(true) 159 | ->in([ 160 | __DIR__ . '/src', 161 | __DIR__ . '/includes', 162 | __DIR__ . '/hypermedia' 163 | ]); 164 | 165 | return (new PhpCsFixer\Config()) 166 | ->setFinder($finder) 167 | ->setRules($rules) 168 | ->setRiskyAllowed(true) 169 | ->setUsingCache(true) 170 | ->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect()); 171 | -------------------------------------------------------------------------------- /.wp-org-assets/HTMX-logo-1600x1600.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EstebanForge/HTMX-API-WP/28205b850d56eb80a6c61dfc8fbd8a69a8375221/.wp-org-assets/HTMX-logo-1600x1600.jpg -------------------------------------------------------------------------------- /.wp-org-assets/banner-1544x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EstebanForge/HTMX-API-WP/28205b850d56eb80a6c61dfc8fbd8a69a8375221/.wp-org-assets/banner-1544x500.png -------------------------------------------------------------------------------- /.wp-org-assets/banner-772x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EstebanForge/HTMX-API-WP/28205b850d56eb80a6c61dfc8fbd8a69a8375221/.wp-org-assets/banner-772x250.png -------------------------------------------------------------------------------- /.wp-org-assets/icon-1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EstebanForge/HTMX-API-WP/28205b850d56eb80a6c61dfc8fbd8a69a8375221/.wp-org-assets/icon-1024x1024.png -------------------------------------------------------------------------------- /.wp-org-assets/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EstebanForge/HTMX-API-WP/28205b850d56eb80a6c61dfc8fbd8a69a8375221/.wp-org-assets/icon-128x128.png -------------------------------------------------------------------------------- /.wp-org-assets/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EstebanForge/HTMX-API-WP/28205b850d56eb80a6c61dfc8fbd8a69a8375221/.wp-org-assets/icon-256x256.png -------------------------------------------------------------------------------- /.wp-org-assets/screenshot-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EstebanForge/HTMX-API-WP/28205b850d56eb80a6c61dfc8fbd8a69a8375221/.wp-org-assets/screenshot-1.jpg -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | # 2.0.0 / 2025-06-06 4 | - Renamed plugin to "Hypermedia API for WordPress" to reflect broader support for multiple hypermedia libraries. 5 | - **NEW:** Added support for Datastar.js hypermedia library. 6 | - **NEW:** Added support for Alpine Ajax hypermedia library. 7 | - **NEW:** Template engine now supports both `.hm.php` (primary) and `.htmx.php` (legacy) extensions. 8 | - **NEW:** Template engine now supports both `hypermedia` (primary) and `htmx-templates` (legacy) theme directories. 9 | - **NEW:** Added `hmapi_get_endpoint_url()` helper function to get the API endpoint URL. 10 | - **NEW:** Added `hmapi_enpoint_url()` helper function to echo the API endpoint URL in templates. 11 | - **IMPROVED:** Enhanced admin interface with a new informational card displaying the API endpoint URL. 12 | - **IMPROVED:** The `$hmvals` variable is now available in templates, containing the request parameters. 13 | - **BACKWARD COMPATIBILITY:** All `hxwp_*` functions are maintained as deprecated aliases for `hmapi_*` functions. 14 | - **BACKWARD COMPATIBILITY:** The legacy `$hxvals` variable is still available in templates for backward compatibility. 15 | - **BACKWARD COMPATIBILITY:** Dual nonce system supports both `hmapi_nonce` (new) and `hxwp_nonce` (legacy). 16 | - **BACKWARD COMPATIBILITY:** Legacy filter hooks (`hxwp/`) are preserved alongside new `hmapi/` prefixed filters. 17 | - **BACKWARD COMPATIBILITY:** The plugin now intelligently sends the correct nonce with the request header, ensuring compatibility with legacy themes. 18 | - **DOCUMENTATION:** Updated `README.md` and inline documentation to reflect the latest changes. 19 | 20 | # 1.3.0 / 2025-05-11 21 | - Updated HTMX, HTMX extensions, Hyperscript and Alpine.js to their latest versions. 22 | - Added the ability to use this plugin as a library, using composer. This allows you to use HTMX in your own plugins or themes, without the need to install this plugin. The plugin/library will determine if a greater instance of itself is already loaded. If so, it will use that instance. Otherwise, it will load a new one. So, no issues with multiple instances of the same library on different plugins or themes. 23 | - Added a new way to load different HTMX templates path, using the filter `hxwp/register_template_path`. This allows you to register a new template path for your plugin or theme, without overriding the default template path, or stepping on the toes of other plugins or themes that may be using the same template path. This is useful if you want to use HTMX in your own plugin or theme, without having to worry about conflicts with other plugins or themes. 24 | - Introduced a colon (`:`) as the explicit separator for namespaced template paths (e.g., `my-plugin:path/to/template`). This provides a clear distinction between plugin/theme-specific templates and templates in the active theme's default `htmx-templates` directory. Requests for explicitly namespaced templates that are not found will result in a 404 and will not fall back to the theme's default directory. 25 | - Now using PSR-4 autoloading for the plugin's code. 26 | 27 | # 1.0.0 / 2024-08-25 28 | - Promoted plugin to stable :) 29 | - Updated to HTMX and its extensions. 30 | - Added a helper to validate requests. Automatically handles nonce and action validation. 31 | 32 | # 0.9.1 / 2024-07-05 33 | - Released on WordPress.org official plugins repository. 34 | 35 | # 0.9.0 / 2024-06-30 36 | - Updated to HTMX 2.0.0 37 | - More WP.org plugin guidelines compliance. 38 | 39 | # 0.3.2 / 2024-05-26 40 | - More WP.org plugin guidelines compliance. 41 | 42 | # 0.3.1 / 2024-05-15 43 | - Fixed a bug in the wp_localize_script() call. Thanks @mwender for the report. 44 | 45 | # 0.3.0 / 2024-05-07 46 | - WP.org plugin guidelines compliance. 47 | - Changed hxwp_send_header_response() behavior to include a nonce by default. First argument is the nonce. Second argument, an array with the data. Check the htmx-demo.htmx.php template for an updated example. 48 | 49 | # 0.2.0 / 2024-04-26 50 | - Added [Alpine.js](https://alpinejs.dev/) support. Now you can use HTMX with Alpine.js, Hyperscript, or both. 51 | 52 | # 0.1.15 / 2024-04-13 53 | - Fixes sanitization for form elements that allows multiple values. Thanks @mwender for the report. [Discussion #8](https://github.com/EstebanForge/HTMX-API-WP/discussions/8). 54 | 55 | # 0.1.14 / 2024-03-06 56 | - Added option to add the `hx-boost` (true) attribute to any enabled theme, automatically. This enables HTMX's boost feature, globally. Learn more [here](https://htmx.org/attributes/hx-boost/). 57 | 58 | # 0.1.12 / 2024-02-22 59 | - Added Composer support. Thanks @mwender! 60 | - Fixed a bug on how the plugin obtains the active theme path. Thanks again @mwender for the report and fix :) 61 | - Added a filter to allow the user to change the default path for the HTMX templates. Thanks @mwender for the suggestion. 62 | 63 | # 0.1.11 / 2024-02-21 64 | - Added WooCommerce compatibility. Thanks @carlosromanxyz for the suggestion. 65 | 66 | # 0.1.10 / 2024-02-20 67 | - Added a showcase/demo theme to demonstrate how to use HTMX with WordPress. The theme is available at [EstebanForge/HTMX-WordPress-Theme](https://github.com/EstebanForge/HTMX-WordPress-Theme). 68 | - hxwp_api_url() helper now accepts a path to be appended to the API URL. Just like WP's home_url(). 69 | - Keeps line breaks on sanitization of hxvals. Thanks @texorama! 70 | - Added option to enable HTMX to load at the WordPress backend (wp-admin). Thanks @texorama for the suggestion. 71 | 72 | # 0.1.8 / 2024-02-14 73 | - HTMX and Hyperscript are now retrieved using NPM. 74 | - Fixes loading extensions from local/CDN and their paths. Thanks @agencyhub! 75 | 76 | # 0.1.7 / 2023-12-27 77 | - Bugfixes. 78 | 79 | # 0.1.6 / 2023-12-18 80 | - Merged `noswap/` folder into `htmx-templates/` folder. Now, all templates are inside `htmx-templates/` folder. 81 | 82 | # 0.1.5 / 2023-12-15 83 | - Renamed `hxparams` to `hxvals` to match HTMX terminology. 84 | - Added hxwp_die() function to be used on templates (`noswap/` included). This functions will die() the script, but sending a 200 status code so HTMX can process the response and along with a header HX-Error on it, with the message included, so it can be used on the client side. 85 | 86 | # 0.1.4 / 2023-12-13 87 | - Renamed `void/` endpoint to `noswap/` to match HTMX terminology, better showing the purpose of this endpoint. 88 | - Better path sanitization for template files. 89 | - Added `hxwp_send_header_response` function to send a Response Header back to the client, to allow for non-visual responses (`noswap/`) to execute some logic on the client side. Refer to the [Response Headers](https://htmx.org/docs/#response-headers) and [HX-Trigger](https://htmx.org/headers/hx-trigger/) sections to know more about this. 90 | 91 | # 0.1.3 / 2023-12-04 92 | - Added filters and actions to inject HTMX meta tag configuration. Refer to the [documentation](https://htmx.org/docs/#config) for more information. 93 | - Added new endpoint to wp-htmx to allow non visual responses to be executed, vía /void/ endpoint. 94 | 95 | # 0.1.1 / 2023-12-01 96 | - First public release. 97 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | ## Why? Why HTMX? Why?! 4 | 5 | [Because...](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExM21yeDBzZ2ltcWNlZm05bjc2djF2bHo2cWVpOXcxNmQyZDJiZDhiZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/qkJJRL9Sz1R04/giphy.gif) 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hypermedia API for WordPress 2 | 3 | An unofficial WordPress plugin that enables the use of [HTMX](https://htmx.org), [Alpine AJAX](https://alpine-ajax.js.org/), [Datastar](https://data-star.dev/) and other hypermedia libraries on your WordPress site, theme, and/or plugins. Intended for software developers. 4 | 5 | Adds a new endpoint `/wp-html/v1/` from which you can load any hypermedia template. 6 | 7 |
8 | 9 | [![Hypermedia API for WordPress Demo](https://img.youtube.com/vi/6mrRA5QIcRw/0.jpg)](https://www.youtube.com/watch?v=6mrRA5QIcRw "Hypermedia API for WordPress Demo") 10 | 11 | 12 | 13 | [Check the video](https://www.youtube.com/watch?v=6mrRA5QIcRw) 14 | 15 | 16 | 17 |
18 | 19 | ## Hypermedia what? 20 | 21 | [Hypermedia](https://hypermedia.systems/) is a "new" concept that allows you to build modern web applications, even SPAs, without the need to write a single line of JavaScript. A forgotten concept that was popular in the 90s and early 2000s, but has been forgotten by newer generations of software developers. 22 | 23 | HTMX, Alpine Ajax and Datastar are JavaScript libraries that allows you to access AJAX, WebSockets, and Server-Sent Events directly in HTML using attributes, without writing any JavaScript. 24 | 25 | Unless you're trying to build a Google Docs clone or a competitor, Hypermedia allows you to build modern web applications, even SPAs, without the need to write a single line of JavaScript. 26 | 27 | For a better explanation and demos, check the following video: 28 | 29 |
30 | 31 | [![You don't need a frontend framework by Andrew Schmelyun](https://img.youtube.com/vi/Fuz-jLIo2g8/0.jpg)](https://www.youtube.com/watch?v=Fuz-jLIo2g8) 32 | 33 |
34 | 35 | ## Why mix it with WordPress? 36 | 37 | Because I share the same sentiment as Carson Gross, the creator of HTMX, that the software stack used to build the web today has become too complex without good reason (most of the time). And, just like him, I also want to see the world burn. 38 | 39 | (Seriously) Because Hypermedia is awesome, and WordPress is awesome (sometimes). So, why not? 40 | 41 | I'm using this in production for a few projects, and it's working great, stable, and ready to use. So, I decided to share it with the world. 42 | 43 | I took this idea out of the tangled mess it was inside a project and made it into a standalone plugin that should work for everyone. 44 | 45 | It might have some bugs, but the idea is to open it up and improve it over time. 46 | 47 | So, if you find any bugs, please report them. 48 | 49 | ## Installation 50 | 51 | Install it directly from the WordPress.org plugin repository. On the plugins install page, search for: Hypermedia API 52 | 53 | Or download the zip from the [official plugin repository](https://wordpress.org/plugins/api-for-htmx/) and install it from your WordPress plugins install page. 54 | 55 | Activate the plugin. Configure it to your liking on Settings > Hypermedia API. 56 | 57 | ### Installation via Composer 58 | If you want to use this plugin as a library, you can install it via Composer. This allows you to use hypermedia libraries in your own plugins or themes, without the need to install this plugin. 59 | 60 | ```bash 61 | composer require estebanforge/hypermedia-api-wordpress 62 | ``` 63 | 64 | This plugin/library will determine which instance of itself is the newer one when WordPress is loading. Then, it will use the newer instance between all competing plugins or themes. This is to avoid conflicts with other plugins or themes that may be using the same library for their Hypermedia implementation. 65 | 66 | ## How to use 67 | 68 | After installation, you can use hypermedia templates in any theme. 69 | 70 | This plugin will include the active hypermedia library by default, locally from the plugin folder. Libraries like HTMX, Alpine.js, Hyperscript, and Datastar are supported. 71 | 72 | The plugin has an opt-in option, not enforced, to include these third-party libraries from a CDN (using the unpkg.com service). You must explicitly enable this option for privacy and security reasons. 73 | 74 | Create a `hypermedia` folder in your theme's root directory. This plugin includes a demo folder that you can copy to your theme. Don't put your templates inside the demo folder located in the plugin's directory, because it will be deleted when you update the plugin. 75 | 76 | Inside your `hypermedia` folder, create as many templates as you want. All files must end with `.hm.php`. 77 | 78 | For example: 79 | 80 | ``` 81 | hypermedia/live-search.hm.php 82 | hypermedia/related-posts.hm.php 83 | hypermedia/private/author.hm.php 84 | hypermedia/private/author-posts.hm.php 85 | ``` 86 | 87 | Check the demo template at `hypermedia/demo.hm.php` to see how to use it. 88 | 89 | Then, in your theme, use your Hypermedia library to GET/POST to the `/wp-html/v1/` endpoint corresponding to the template you want to load, without the file extension: 90 | 91 | ``` 92 | /wp-html/v1/live-search 93 | /wp-html/v1/related-posts 94 | /wp-html/v1/private/author 95 | /wp-html/v1/private/author-posts 96 | ``` 97 | 98 | ### Helper Functions 99 | 100 | You can use the `hmapi_get_endpoint_url()` helper function to generate the URL for your hypermedia templates. This function will automatically add the `/wp-html/v1/` prefix. The hypermedia file extension (`.hm.php`) is not needed, the API will resolve it automatically. 101 | 102 | For example: 103 | 104 | ```php 105 | echo hmapi_get_endpoint_url( 'live-search' ); 106 | ``` 107 | 108 | Or, 109 | 110 | ```php 111 | hmapi_endpoint_url( 'live-search' ); 112 | ``` 113 | 114 | Will call the template located at: 115 | 116 | ``` 117 | /hypermedia/live-search.hm.php 118 | ``` 119 | And will load it from the URL: 120 | 121 | ``` 122 | http://your-site.com/wp-html/v1/live-search 123 | ``` 124 | 125 | This will output: 126 | 127 | ``` 128 | http://your-site.com/wp-html/v1/live-search 129 | ``` 130 | 131 | #### Backward Compatibility 132 | 133 | For backward compatibility, the old `hxwp_api_url()` function is still available as an alias for `hmapi_get_endpoint_url()`. However, we recommend updating your code to use the new function names as the old ones are deprecated and may be removed in future versions. 134 | 135 | Other helper functions available: 136 | - `hmapi_send_header_response()` / `hxwp_send_header_response()` (deprecated alias) 137 | - `hmapi_die()` / `hxwp_die()` (deprecated alias) 138 | - `hmapi_validate_request()` / `hxwp_validate_request()` (deprecated alias) 139 | 140 | ### How to pass data to the template 141 | 142 | You can pass data to the template using URL parameters (GET/POST). For example: 143 | 144 | ``` 145 | /wp-html/v1/live-search?search=hello 146 | /wp-html/v1/related-posts?category_id=5 147 | ``` 148 | 149 | All of those parameters (with their values) will be available inside the template as an array named: `$hmvals`. 150 | 151 | ### No Swap response templates 152 | 153 | Hypermedia libraries allow you to use templates that don't return any HTML but perform some processing in the background on your server. These templates can still send a response back (using HTTP headers) if desired. Check [Swapping](https://htmx.org/docs/#swapping) for more info. 154 | 155 | For this purpose, and for convenience, you can use the `noswap/` folder/endpoint. For example: 156 | 157 | ``` 158 | /wp-html/v1/noswap/save-user?user_id=5&name=John&last_name=Doe 159 | /wp-html/v1/noswap/delete-user?user_id=5 160 | ``` 161 | 162 | In this examples, the `save-user` and `delete-user` templates will not return any HTML, but will do some processing in the background. They will be loaded from the `hypermedia/noswap` folder. 163 | 164 | ``` 165 | hypermedia/noswap/save-user.hm.php 166 | hypermedia/noswap/delete-user.hm.php 167 | ``` 168 | 169 | You can pass data to these templates in the exact same way as you do with regular templates. 170 | 171 | Nothing stops you from using regular templates to do the same thing or using another folder altogether. You can mix and match or organize your templates in any way you want. This is mentioned here just as a convenience feature for those who want to use it. 172 | 173 | ### Choosing a Hypermedia Library 174 | 175 | This plugin comes with [HTMX](https://htmx.org), [Alpine Ajax](https://alpine-ajax.js.org/) and [Datastar](https://data-star.dev/) already integrated and enabled. 176 | 177 | You can choose which library to use in the plugin's options page: Settings > Hypermedia API. 178 | 179 | In the case of HTMX, you can also enable any of its extensions in the plugin's options page: Settings > Hypermedia API. 180 | 181 | #### Local vs CDN Loading 182 | 183 | The plugin includes local copies of all libraries for privacy and offline development. You can choose to load from: 184 | 185 | 1. **Local files** (default): Libraries are served from your WordPress installation 186 | 2. **CDN**: Optional CDN loading from jsdelivr.net. Will always load the latest version of the library. 187 | 188 | #### Build System Integration 189 | 190 | For developers, the plugin includes npm scripts to download the latest versions of all libraries locally: 191 | 192 | ```bash 193 | # Download all libraries 194 | npm run download:all 195 | 196 | # Download specific library 197 | npm run download:htmx 198 | npm run download:alpine 199 | npm run download:hyperscript 200 | npm run download:datastar 201 | npm run download:all 202 | ``` 203 | 204 | This ensures your local development environment stays in sync with the latest library versions. 205 | 206 | ## Using Hypermedia Libraries in your plugin 207 | 208 | You can definitely use hypermedia libraries and this Hypermedia API for WordPress in your plugin. You are not limited to using it only in your theme. 209 | 210 | The plugin provides the filter: `hmapi/register_template_path` 211 | 212 | This filter allows you to register a new template path for your plugin or theme. It expects an associative array where keys are your chosen namespaces and values are the absolute paths to your template directories. 213 | 214 | For example, if your plugin slug is `my-plugin`, you can register a new template path like this: 215 | 216 | ```php 217 | add_filter( 'hmapi/register_template_path', function( $paths ) { 218 | // Ensure YOUR_PLUGIN_PATH is correctly defined, e.g., plugin_dir_path( __FILE__ ) 219 | // 'my-plugin' is the namespace. 220 | $paths['my-plugin'] = YOUR_PLUGIN_PATH . 'hypermedia/'; 221 | 222 | return $paths; 223 | }); 224 | ``` 225 | 226 | Assuming `YOUR_PLUGIN_PATH` is already defined and points to your plugin's root directory, the above code registers the `my-plugin` namespace to point to `YOUR_PLUGIN_PATH/hypermedia/`. 227 | 228 | Then, you can use the new template path in your plugin like this, using a colon `:` to separate the namespace from the template file path (which can include subdirectories): 229 | 230 | ```php 231 | // Loads the template from: YOUR_PLUGIN_PATH/hypermedia/template-name.hm.php 232 | echo hmapi_get_endpoint_url( 'my-plugin:template-name' ); 233 | 234 | // Loads the template from: YOUR_PLUGIN_PATH/hypermedia/parts/header.hm.php 235 | echo hmapi_get_endpoint_url( 'my-plugin:parts/header' ); 236 | ``` 237 | 238 | This will output the URL for the template from the path associated with the `my-plugin` namespace. If the namespace is not registered, or the template file does not exist within that registered path (or is not allowed due to sanitization rules), the request will result in a 404 error. Templates requested with an explicit namespace do not fall back to the theme's default `hypermedia` directory. 239 | 240 | For templates located directly in your active theme's `hypermedia` directory (or its subdirectories), you would call them without a namespace: 241 | 242 | ```php 243 | // Loads: wp-content/themes/your-theme/hypermedia/live-search.hm.php 244 | echo hmapi_get_endpoint_url( 'live-search' ); 245 | 246 | // Loads: wp-content/themes/your-theme/hypermedia/subfolder/my-listing.hm.php 247 | echo hmapi_get_endpoint_url( 'subfolder/my-listing' ); 248 | ``` 249 | 250 | ## Security 251 | 252 | Every call to the `wp-html` endpoint will automatically check for a valid nonce. If the nonce is not valid, the call will be rejected. 253 | 254 | The nonce itself is auto-generated and added to all HTMX requests automatically, using HTMX's own `htmx:configRequest` event. 255 | 256 | If you are new to Hypermedia, please read the [security section](https://htmx.org/docs/#security) of the official documentation. Remember that Hypermedia requires you to validate and sanitize any data you receive from the user. This is something developers used to do all the time, but it seems to have been forgotten by newer generations of software developers. 257 | 258 | If you are not familiar with how WordPress recommends handling data sanitization and escaping, please read the [official documentation](https://developer.wordpress.org/themes/theme-security/data-sanitization-escaping/) on [Sanitizing Data](https://developer.wordpress.org/apis/security/sanitizing/) and [Escaping Data](https://developer.wordpress.org/apis/security/escaping/). 259 | 260 | ### REST Endpoint 261 | 262 | The plugin will perform basic sanitization of calls to the new REST endpoint, `wp-html`, to avoid security issues like directory traversal attacks. It will also limit access so you can't use it to access any file outside the `hypermedia` folder within your own theme. 263 | 264 | The parameters and their values passed to the endpoint via GET or POST will be sanitized with `sanitize_key()` and `sanitize_text_field()`, respectively. 265 | 266 | Filters `hmapi/sanitize_param_key` and `hmapi/sanitize_param_value` are available to modify the sanitization process if needed. For backward compatibility, the old filters `hxwp/sanitize_param_key` and `hxwp/sanitize_param_value` are still supported but deprecated. 267 | 268 | Do your due diligence and ensure you are not returning unsanitized data back to the user or using it in a way that could pose a security issue for your site. Hypermedia requires that you validate and sanitize any data you receive from the user. Don't forget that. 269 | 270 | ## Examples 271 | 272 | Check out the showcase/demo theme at [EstebanForge/Hypermedia-Theme-WordPress](https://github.com/EstebanForge/Hypermedia-Theme-WordPress). 273 | 274 | ## Suggestions, Support 275 | 276 | Please, open [a discussion](https://github.com/EstebanForge/hypermedia-api-wordpress/discussions). 277 | 278 | ## Bugs and Error reporting 279 | 280 | Please, open [an issue](https://github.com/EstebanForge/hypermedia-api-wordpress/issues). 281 | 282 | ## FAQ 283 | [FAQ available here](https://github.com/EstebanForge/hypermedia-api-wordpress/blob/main/FAQ.md). 284 | 285 | ## Changelog 286 | 287 | [Changelog available here](https://github.com/EstebanForge/hypermedia-api-wordpress/blob/main/CHANGELOG.md). 288 | 289 | ## Contributing 290 | 291 | You are welcome to contribute to this plugin. 292 | 293 | If you have a feature request or a bug report, please open an issue on the [GitHub repository](https://github.com/EstebanForge/hypermedia-api-wordpress/issues). 294 | 295 | If you want to contribute with code, please open a pull request. 296 | 297 | ## License 298 | 299 | This plugin is licensed under the GPLv2 or later. 300 | 301 | You can find the full license text in the `license.txt` file. 302 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | === API for HTMX === 2 | Contributors: tcattd 3 | Tags: htmx, ajax, hypermedia, hyperscript, alpinejs 4 | Stable tag: 2.0.0 5 | Requires at least: 6.4 6 | Tested up to: 6.6 7 | Requires PHP: 8.1 8 | License: GPLv2 or later 9 | License URI: http://www.gnu.org/licenses/gpl-2.0.txt 10 | 11 | An unofficial WordPress plugin that enables the use of Hypermedia on your WordPress site, theme, and/or plugins. Intended for software developers. 12 | 13 | == Description == 14 | An unofficial WordPress plugin that enables the use of Hypermedia on WordPress. Adds a new endpoint `/wp-htmx/v1/` from which you can load any Hypermedia template. 15 | 16 | Hypermedia is a concept that extends the idea of hypertext by allowing for more complex interactions and data representations. It enables the use of AJAX, WebSockets, and Server-Sent Events directly in HTML using attributes, without writing any JavaScript. It reuses an "old" concept, [Hypermedia](https://hypermedia.systems/), to handle the modern web in a more HTML-like and natural way. 17 | 18 | Check the [full feature set at here](https://github.com/EstebanForge/Hypermedia-API-WordPress). 19 | 20 | This plugin include several Hypermedia libraries by default, locally from the plugin folder. Currently, it includes: 21 | 22 | - [HTMX](https://htmx.org/) with [Hyperscript](https://hyperscript.org/). 23 | - [Alpine Ajax](https://alpine-ajax.js.org/) with [Alpine.js](https://alpinejs.dev/). 24 | - [Datastar](https://data-star.dev/). 25 | 26 | The plugin has an opt-in option, not enforced, to include these third-party libraries from a CDN (using the unpkg.com service). You must explicitly enable this option for privacy and security reasons. 27 | 28 | == Installation == 29 | 1. Install Hypermedia-API-WordPress from WordPress repository. Plugins > Add New > Search for: Hypermedia API or API-for-HTMX. Activate it. 30 | 2. Configure Hypermedia-API-WordPress at Settings > Hypermedia API. 31 | 3. Enjoy. 32 | 33 | == Frequently Asked Questions == 34 | = Where is the FAQ? = 35 | You can [read the full FAQ at GitHub](https://github.com/EstebanForge/Hypermedia-API-WordPress/blob/main/FAQ.md). 36 | 37 | = Suggestions, Support? = 38 | Please, open [a discussion](https://github.com/EstebanForge/Hypermedia-API-WordPress/discussions). 39 | 40 | = Found a Bug or Error? = 41 | Please, open [an issue](https://github.com/EstebanForge/Hypermedia-API-WordPress/issues). 42 | 43 | == Screenshots == 44 | 1. Main options page. 45 | 46 | == Upgrade Notice == 47 | Nothing to see here. 48 | 49 | == Changelog == 50 | [Check the changelog at GitHub](https://github.com/EstebanForge/Hypermedia-API-WordPress/blob/master/CHANGELOG.md). 51 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 2.0.0 | :white_check_mark: | 8 | | <2.0.0 | :x: | 9 | 10 | ## Reporting a Vulnerability 11 | 12 | Please, contact me at any of the following email addresses: 13 | 14 | esteban at attitude.cl 15 | 16 | esteban at actitud.xyz 17 | 18 | Thanks! 19 | -------------------------------------------------------------------------------- /api-for-htmx.php: -------------------------------------------------------------------------------- 1 | 'Version'], false); 28 | $current_hmapi_instance_version = $hmapi_plugin_data['Version'] ?? '0.0.0'; // Default to 0.0.0 if not found 29 | $current_hmapi_instance_path = realpath(__FILE__); 30 | 31 | // Register this instance as a candidate 32 | // Globals, i know. But we need a fast way to do this. 33 | if (!isset($GLOBALS['hmapi_api_candidates']) || !is_array($GLOBALS['hmapi_api_candidates'])) { 34 | $GLOBALS['hmapi_api_candidates'] = []; 35 | } 36 | 37 | // Use path as key to prevent duplicates from the same file if included multiple times 38 | $GLOBALS['hmapi_api_candidates'][$current_hmapi_instance_path] = [ 39 | 'version' => $current_hmapi_instance_version, 40 | 'path' => $current_hmapi_instance_path, 41 | 'init_function' => 'hmapi_run_initialization_logic', 42 | ]; 43 | 44 | // Hook to decide and run the winner. This action should only be added once. 45 | if (!has_action('plugins_loaded', 'hmapi_select_and_load_latest')) { 46 | add_action('plugins_loaded', 'hmapi_select_and_load_latest', 0); // Priority 0 to run very early 47 | } 48 | 49 | /* 50 | * Contains the actual plugin initialization logic. 51 | * This function is called only for the winning (latest version) instance. 52 | * 53 | * @param string $plugin_file_path Path to the plugin file that should run. 54 | * @param string $plugin_version The version of the plugin file. 55 | */ 56 | if (!function_exists('hmapi_run_initialization_logic')) { 57 | function hmapi_run_initialization_logic(string $plugin_file_path, string $plugin_version): void 58 | { 59 | // These constants signify that the chosen instance is now loading. 60 | define('HMAPI_INSTANCE_LOADED', true); 61 | define('HMAPI_LOADED_VERSION', $plugin_version); 62 | define('HMAPI_INSTANCE_LOADED_PATH', $plugin_file_path); 63 | 64 | // Define plugin constants using the provided path and version 65 | define('HMAPI_VERSION', $plugin_version); 66 | define('HMAPI_ABSPATH', plugin_dir_path($plugin_file_path)); 67 | define('HMAPI_BASENAME', plugin_basename($plugin_file_path)); 68 | define('HMAPI_PLUGIN_URL', plugin_dir_url($plugin_file_path)); 69 | define('HMAPI_PLUGIN_FILE', $plugin_file_path); 70 | define('HMAPI_ENDPOINT', 'wp-html'); // New primary endpoint 71 | define('HMAPI_LEGACY_ENDPOINT', 'wp-htmx'); 72 | define('HMAPI_TEMPLATE_DIR', 'hypermedia'); // Default template directory in theme 73 | define('HMAPI_LEGACY_TEMPLATE_DIR', 'htmx-templates'); // Legacy template directory in theme 74 | define('HMAPI_TEMPLATE_EXT', '.hm.php'); // Default template file extension 75 | define('HMAPI_LEGACY_TEMPLATE_EXT', '.htmx.php'); // Legacy template file extension 76 | define('HMAPI_ENDPOINT_VERSION', 'v1'); 77 | 78 | // --- Backward Compatibility Aliases for Constants --- 79 | if (!defined('HXWP_VERSION')) { 80 | define('HXWP_VERSION', HMAPI_VERSION); 81 | define('HXWP_ABSPATH', HMAPI_ABSPATH); 82 | define('HXWP_BASENAME', HMAPI_BASENAME); 83 | define('HXWP_PLUGIN_URL', HMAPI_PLUGIN_URL); 84 | define('HXWP_ENDPOINT', HMAPI_LEGACY_ENDPOINT); 85 | define('HXWP_ENDPOINT_VERSION', HMAPI_ENDPOINT_VERSION); 86 | define('HXWP_TEMPLATE_DIR', HMAPI_TEMPLATE_DIR); 87 | } 88 | // --- End Backward Compatibility Aliases --- 89 | 90 | // Composer autoloader 91 | if (file_exists(HMAPI_ABSPATH . 'vendor/autoload.php')) { 92 | require_once HMAPI_ABSPATH . 'vendor/autoload.php'; 93 | // Helpers 94 | require_once HMAPI_ABSPATH . 'includes/helpers.php'; 95 | } else { 96 | // Log error or display admin notice 97 | add_action('admin_notices', function () { 98 | echo '

' . __('Hypermedia API: Composer autoloader not found. Please run "composer install" inside the plugin folder.', 'api-for-htmx') . '

'; 99 | }); 100 | 101 | return; 102 | } 103 | 104 | // "Don't run when..." check, moved here to allow class loading for library use cases. 105 | // Ensures that boolean true is checked, not just definition. 106 | if ((defined('DOING_CRON') && DOING_CRON === true) || 107 | (defined('DOING_AJAX') && DOING_AJAX === true) || 108 | (defined('REST_REQUEST') && REST_REQUEST === true) || 109 | (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST === true) || 110 | (defined('WP_CLI') && WP_CLI === true)) { 111 | // The plugin's runtime (hooks, etc.) is skipped, but classes are available via autoloader. 112 | return; 113 | } 114 | 115 | // Activation and deactivation hooks, tied to the specific plugin file. 116 | register_activation_hook($plugin_file_path, ['HMApi\Admin\Activation', 'activate']); 117 | register_deactivation_hook($plugin_file_path, ['HMApi\Admin\Activation', 'deactivate']); 118 | 119 | // Initialize the plugin's main class. 120 | if (class_exists('HMApi\Main')) { 121 | $router = new HMApi\Router(); 122 | $render = new HMApi\Render(); 123 | $config = new HMApi\Config(); 124 | $compatibility = new HMApi\Compatibility(); 125 | $theme_support = new HMApi\Theme(); 126 | $hmapi_main = new HMApi\Main( 127 | $router, 128 | $render, 129 | $config, 130 | $compatibility, 131 | $theme_support 132 | ); 133 | $hmapi_main->run(); 134 | } else { 135 | // Log an error or handle the case where the main class is not found. 136 | // This might happen if the autoloader failed or classes are not correctly namespaced/located. 137 | if (defined('WP_DEBUG') && WP_DEBUG === true) { 138 | error_log('Hypermedia API for WordPress: HMApi\Main class not found. Autoloader or class structure issue.'); 139 | } 140 | } 141 | } 142 | } 143 | 144 | /* 145 | * Selects the latest version from registered candidates and runs its initialization. 146 | * This function is hooked to 'plugins_loaded' at priority 0. 147 | */ 148 | if (!function_exists('hmapi_select_and_load_latest')) { 149 | function hmapi_select_and_load_latest(): void 150 | { 151 | if (empty($GLOBALS['hmapi_api_candidates']) || !is_array($GLOBALS['hmapi_api_candidates'])) { 152 | return; 153 | } 154 | 155 | $candidates = $GLOBALS['hmapi_api_candidates']; 156 | 157 | // Sort candidates by version in descending order (latest version first). 158 | uasort($candidates, fn ($a, $b) => version_compare($b['version'], $a['version'])); 159 | 160 | $winner = reset($candidates); // Get the first candidate (which is the latest version). 161 | 162 | if ($winner && isset($winner['path'], $winner['version'], $winner['init_function']) && function_exists($winner['init_function'])) { 163 | // Call the initialization function of the winning instance. 164 | call_user_func($winner['init_function'], $winner['path'], $winner['version']); 165 | } elseif ($winner && defined('WP_DEBUG') && WP_DEBUG === true) { 166 | error_log('Hypermedia API for WordPress: Winning candidate\'s init_function ' . esc_html($winner['init_function'] ?? 'N/A') . ' not found or candidate structure invalid.'); 167 | } 168 | 169 | // Clean up the global array to free memory and prevent re-processing. 170 | unset($GLOBALS['hmapi_api_candidates']); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | You shall not pass! 14 | 15 | 16 | 17 | 18 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /assets/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | You shall not pass! 14 | 15 | 16 | 17 | 18 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /assets/js/libs/LICENSE-alpinejs.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright © 2019-2021 Caleb Porzio and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/js/libs/LICENSE-datastar.md: -------------------------------------------------------------------------------- 1 | Copyright (c) Delaney Gillilan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /assets/js/libs/LICENSE-htmx.md: -------------------------------------------------------------------------------- 1 | Zero-Clause BSD 2 | ============= 3 | 4 | Permission to use, copy, modify, and/or distribute this software for 5 | any purpose with or without fee is hereby granted. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL 8 | WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES 9 | OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE 10 | FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY 11 | DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 12 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 13 | OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /assets/js/libs/LICENSE-hyperscript.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 'Dominic Tarr' 2 | 3 | Permission is hereby granted, free of charge, 4 | to any person obtaining a copy of this software and 5 | associated documentation files (the "Software"), to 6 | deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom 10 | the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 20 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /assets/js/libs/_hyperscript.min.js: -------------------------------------------------------------------------------- 1 | (function(t,n){const e=n(t);if(typeof exports==="object"&&typeof exports["nodeName"]!=="string"){module.exports=e}else{if("_hyperscript"in t)t._hyperscript.use(e)}})(typeof self!=="undefined"?self:this,(t=>t=>{function n(n,e,o){this.ctx=n;this.runtime=e;this.cmd=o;this._hyperscript=t;this.cmdMap=[];this.bus=new EventTarget}t.addCommand("breakpoint",(function(t,e,o){if(!o.matchToken("breakpoint"))return;var i;return{op:function(t){globalThis.hdb=i=new n(t,e,this);try{return i.break(t)}catch(t){console.error(t,t.stack)}}}}));n.prototype.break=function(t){console.log("=== HDB///_hyperscript/debugger ===");this.ui();return new Promise(((n,e)=>{this.bus.addEventListener("continue",(()=>{if(this.ctx!==t){for(var e in t){delete t[e]}Object.assign(t,this.ctx)}delete window["hdb"];n(this.runtime.findNext(this.cmd,this.ctx))}),{once:true})}))};n.prototype.continueExec=function(){this.bus.dispatchEvent(new Event("continue"))};n.prototype.stepOver=function(){if(!this.cmd)return this.continueExec();var t=this.cmd&&this.cmd.type==="breakpointCommand"?this.runtime.findNext(this.cmd,this.ctx):this.runtime.unifiedEval(this.cmd,this.ctx);if(t.type==="implicitReturn")return this.stepOut();if(t&&t.then instanceof Function){return t.then((t=>{this.cmd=t;this.bus.dispatchEvent(new Event("step"));this.logCommand()}))}else if(t.halt_flag){this.bus.dispatchEvent(new Event("continue"))}else{this.cmd=t;this.bus.dispatchEvent(new Event("step"));this.logCommand()}};n.prototype.stepOut=function(){if(!this.ctx.meta.caller)return this.continueExec();var t=this.ctx.meta.callingCommand;var n=this.ctx.me;this.ctx=this.ctx.meta.caller;console.log("[hdb] stepping out into "+this.ctx.meta.feature.displayName);if(this.ctx.me instanceof Element&&this.ctx.me!==n){console.log("[hdb] me: ",this.ctx.me)}this.cmd=this.runtime.findNext(t,this.ctx);this.cmd=this.runtime.findNext(this.cmd,this.ctx);this.logCommand();this.bus.dispatchEvent(new Event("step"))};n.prototype.skipTo=function(t){this.cmd=t.cmd;this.bus.dispatchEvent(new Event("skip"))};n.prototype.rewrite=function(n,e){console.log("##",n);const o=n.cmd.parent;let i;for(i of o.children){if(i.next===n.cmd)break}const r=n.next;const s=t.internals.lexer.tokenize(e);const a=t.internals.parser.requireElement("command",s);console.log(a);a.startToken=n.startToken;a.endToken=n.endToken;a.programSource=n.programSource;a.sourceFor=function(){return e};i.next=a;a.next=r;a.parent=o;this.bus.dispatchEvent(new Event("step"))};n.prototype.logCommand=function(){var t=this.cmd.sourceFor instanceof Function;var n=t?this.cmd.sourceFor():"-- "+this.cmd.type;console.log("[hdb] current command: "+n)};n.prototype.traverse=function(t){const n=[];(function t(e){n.push(e);if("children"in e)for(const n of e.children)t(n)})(t);return n};var e=`\n
\n\n\t 139 | 140 | 158 |
159 | -------------------------------------------------------------------------------- /hypermedia/datastar-demo.hm.php: -------------------------------------------------------------------------------- 1 | 18 | 19 |
20 |

Hello Datastar!

21 | 22 |

Demo template loaded from plugins/api-for-htmx//datastar-demo.hm.php

23 | 24 |

Received params ($hmvals):

25 | 26 |
 27 | 		
 28 | 	
29 | 30 |
32 | 33 |

Datastar Examples:

34 | 35 | 36 |
37 |
Example 1: GET Request
38 | 47 |
48 |
49 | 50 | 51 |
52 |
Example 2: POST Request with Data
53 | 57 | 66 |
67 | 68 | 69 |
70 |
Example 3: Form Submission
71 |
75 |

76 | 77 | 82 |

83 |

84 | 85 | 90 |

91 | 95 |
96 |
97 | 98 | 99 |
100 |
Example 4: Real-time Data Binding
101 |

Type in the input below and see real-time updates:

102 | 106 |

You typed:

107 |

Length: characters

108 |
109 | 110 | 111 |
112 |
Example 5: Fetch with Merge
113 | 120 |
121 |

Server time:

122 |
123 |
124 |
125 | 126 | 147 |
148 | -------------------------------------------------------------------------------- /hypermedia/htmx-demo.hm.php: -------------------------------------------------------------------------------- 1 | 18 | 19 |
20 |

Hello HTMX!

21 | 22 |

Demo template loaded from plugins/api-for-htmx//htmx-demo.hm.php

23 | 24 |

Received params ($hmvals):

25 | 26 |
27 | 		
28 | 	
29 | 30 |
31 | -------------------------------------------------------------------------------- /hypermedia/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | You shall not pass! 14 | 15 | 16 | 17 | 18 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /hypermedia/noswap/alpine-ajax-demo.hm.php: -------------------------------------------------------------------------------- 1 | $status, 39 | 'nonce' => wp_create_nonce('hmapi_nonce'), 40 | 'message' => $message, 41 | 'demo_type' => $demo_type, 42 | 'params' => $hmvals, 43 | 'timestamp' => current_time('mysql'), 44 | ] 45 | ); 46 | -------------------------------------------------------------------------------- /hypermedia/noswap/datastar-demo.hm.php: -------------------------------------------------------------------------------- 1 | '', 'email' => '']; 33 | break; 34 | case 'fetch_merge': 35 | $message = 'Data fetched and merged successfully!'; 36 | $extra_data['serverTime'] = current_time('Y-m-d H:i:s'); 37 | $extra_data['randomNumber'] = wp_rand(1, 1000); 38 | break; 39 | default: 40 | $message = 'Datastar request processed via noswap template.'; 41 | } 42 | 43 | // For Datastar, we need to send the response in a format that can be merged into the store 44 | $response_data = [ 45 | 'status' => $status, 46 | 'nonce' => wp_create_nonce('hmapi_nonce'), 47 | 'message' => $message, 48 | 'demo_type' => $demo_type, 49 | 'params' => $hmvals, 50 | 'timestamp' => current_time('mysql'), 51 | ]; 52 | 53 | // Merge any extra data 54 | $response_data = array_merge($response_data, $extra_data); 55 | 56 | // Send appropriate headers for Datastar store merging 57 | if (!headers_sent()) { 58 | // For Datastar, we can send data that gets merged into the store 59 | header('Content-Type: text/vnd.datastar'); 60 | 61 | // Send a merge fragment that updates the store 62 | echo "data: merge " . wp_json_encode($response_data) . "\n\n"; 63 | } else { 64 | // Fallback to standard response 65 | hmapi_send_header_response( 66 | wp_create_nonce('hmapi_nonce'), 67 | $response_data 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /hypermedia/noswap/htmx-demo.hm.php: -------------------------------------------------------------------------------- 1 | 'success', 17 | 'nonce' => wp_create_nonce('hmapi_nonce'), 18 | 'message' => 'Server-side processing done.', 19 | 'params' => $hmvals, 20 | ] 21 | ); 22 | -------------------------------------------------------------------------------- /hypermedia/noswap/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | You shall not pass! 14 | 15 | 16 | 17 | 18 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /includes/helpers.php: -------------------------------------------------------------------------------- 1 | $hmvals, etc. 53 | * @param string $action WP action, optional, default value: none 54 | * 55 | * @return void 56 | */ 57 | function hmapi_send_header_response($data = [], $action = null) 58 | { 59 | // Use shared validation logic 60 | if (!hmapi_validate_request()) { 61 | hmapi_die('Nonce verification failed.'); 62 | } 63 | 64 | if ($action === null) { 65 | // Legacy: check if action is set inside $_POST['hmvals']['action'] 66 | $action = isset($_POST['hmvals']['action']) ? sanitize_text_field($_POST['hmvals']['action']) : ''; 67 | } 68 | 69 | // Action still empty, null or not set? 70 | if (empty($action)) { 71 | $action = 'none'; 72 | } 73 | 74 | // If success or silent-success, set code to 200 75 | $code = $data['status'] == 'error' ? 400 : 200; 76 | 77 | // Response array 78 | $response = [ 79 | 'hmapiResponse' => [ 80 | 'action' => $action, 81 | 'status' => $data['status'], 82 | 'data' => $data, 83 | ], 84 | ]; 85 | 86 | // Headers already sent? 87 | if (headers_sent()) { 88 | wp_die('HMAPI Error: Headers already sent.'); 89 | } 90 | 91 | // Filter our response 92 | $response = apply_filters('hmapi/header_response', $response, $action, $data['status'], $data); 93 | 94 | // Send our response 95 | status_header($code); 96 | nocache_headers(); 97 | header('HX-Trigger: ' . wp_json_encode($response)); 98 | 99 | die(); // Don't use wp_die() here 100 | } 101 | 102 | /** 103 | * HTMX die helper (New HMAPI version) 104 | * To be used inside templates 105 | * die, but with a 200 status code, so HTMX can show and display the error message 106 | * Also sends a custom header with the error message, to be used by HTMX if needed. 107 | * 108 | * @since 2.0.0 109 | * 110 | * @param string $message 111 | * @param bool $display_error 112 | * 113 | * @return void 114 | */ 115 | function hmapi_die($message = '', $display_error = false) 116 | { 117 | // Send our response 118 | if (!headers_sent()) { 119 | status_header(200); 120 | nocache_headers(); 121 | header('HX-Error: ' . wp_json_encode([ 122 | 'status' => 'error', 123 | 'data' => [ 124 | 'message' => $message, 125 | ], 126 | ])); 127 | } 128 | 129 | // Don't display error message 130 | if ($display_error === false) { 131 | $message = ''; 132 | } 133 | 134 | die($message); 135 | } 136 | 137 | /** 138 | * Validate HTMX request (New HMAPI version) 139 | * Checks if the nonce is valid and optionally validates the action. 140 | * 141 | * @since 2.0.0 142 | * 143 | * @param array|null $hmvals The hypermedia values array (optional, will use $_REQUEST if not provided) 144 | * @param string|null $action The expected action (optional) 145 | * 146 | * @return bool 147 | */ 148 | function hmapi_validate_request($hmvals = null, $action = null) 149 | { 150 | // If hmvals not provided, get from $_REQUEST for backwards compatibility 151 | if ($hmvals === null) { 152 | $hmvals = $_REQUEST; 153 | } 154 | 155 | // Secure it - check both request parameter and header for nonce 156 | $nonce = ''; 157 | if (isset($_REQUEST['_wpnonce'])) { 158 | $nonce = sanitize_key($_REQUEST['_wpnonce']); 159 | } elseif (isset($_SERVER['HTTP_X_WP_NONCE'])) { 160 | $nonce = sanitize_key($_SERVER['HTTP_X_WP_NONCE']); 161 | } 162 | 163 | // Check if nonce is valid (try both new and old nonce names for compatibility). 164 | $is_valid_new = wp_verify_nonce(sanitize_text_field(wp_unslash($nonce)), 'hmapi_nonce'); 165 | $is_valid_legacy = wp_verify_nonce(sanitize_text_field(wp_unslash($nonce)), 'hxwp_nonce'); 166 | 167 | if (!$is_valid_new && !$is_valid_legacy) { 168 | return false; 169 | } 170 | 171 | // Check if action is set and matches the expected action (if provided) 172 | if ($action !== null) { 173 | if (!isset($hmvals['action']) || $hmvals['action'] !== $action) { 174 | return false; 175 | } 176 | } 177 | 178 | // Return true if everything is ok 179 | return true; 180 | } 181 | 182 | // =================================================================== 183 | // BACKWARD COMPATIBILITY ALIASES 184 | // =================================================================== 185 | 186 | /** 187 | * Helper to get the API URL. 188 | * 189 | * @since 2023-12-04 190 | * @deprecated 2.0.0 Use hmapi_get_endpoint_url() instead 191 | * 192 | * @param string $template_path (optional) 193 | * 194 | * @return string The full URL to the API endpoint for the given template. 195 | */ 196 | function hxwp_api_url($template_path = '') 197 | { 198 | // Set a global flag to indicate that a legacy function has been used. 199 | $GLOBALS['hmapi_is_legacy_theme'] = true; 200 | 201 | _deprecated_function(__FUNCTION__, '2.0.0', 'hmapi_get_endpoint_url'); 202 | 203 | return hmapi_get_endpoint_url($template_path); 204 | } 205 | 206 | /** 207 | * HTMX send header response and die() (Legacy HXWP version - deprecated) 208 | * To be used inside noswap templates 209 | * Sends HX-Trigger header with our response inside hxwpResponse. 210 | * 211 | * @since 2023-12-13 212 | * @deprecated 2.0.0 Use hmapi_send_header_response() instead 213 | * 214 | * @param array $data status (success|error|silent-success), message, params => $hxvals, etc. 215 | * @param string $action WP action, optional, default value: none 216 | * 217 | * @return void 218 | */ 219 | function hxwp_send_header_response($data = [], $action = null) 220 | { 221 | _deprecated_function(__FUNCTION__, '2.0.0', 'hmapi_send_header_response'); 222 | 223 | // Use shared validation logic 224 | if (!hmapi_validate_request()) { 225 | hxwp_die('Nonce verification failed.'); 226 | } 227 | 228 | if ($action === null) { 229 | // Legacy: check if action is set inside $_POST['hxvals']['action'] 230 | $action = isset($_POST['hxvals']['action']) ? sanitize_text_field($_POST['hxvals']['action']) : ''; 231 | } 232 | 233 | // Action still empty, null or not set? 234 | if (empty($action)) { 235 | $action = 'none'; 236 | } 237 | 238 | // If success or silent-success, set code to 200 239 | $code = $data['status'] == 'error' ? 400 : 200; 240 | 241 | // Response array (keep legacy format for backward compatibility) 242 | $response = [ 243 | 'hxwpResponse' => [ 244 | 'action' => $action, 245 | 'status' => $data['status'], 246 | 'data' => $data, 247 | ], 248 | ]; 249 | 250 | // Headers already sent? 251 | if (headers_sent()) { 252 | wp_die('HXWP Error: Headers already sent.'); 253 | } 254 | 255 | // Filter our response (legacy filter) 256 | $response = apply_filters('hxwp/header_response', $response, $action, $data['status'], $data); 257 | 258 | // Send our response 259 | status_header($code); 260 | nocache_headers(); 261 | header('HX-Trigger: ' . wp_json_encode($response)); 262 | 263 | die(); // Don't use wp_die() here 264 | } 265 | 266 | /** 267 | * HTMX die helper (Legacy HXWP version - deprecated) 268 | * To be used inside templates 269 | * die, but with a 200 status code, so HTMX can show and display the error message 270 | * Also sends a custom header with the error message, to be used by HTMX if needed. 271 | * 272 | * @since 2023-12-15 273 | * @deprecated 2.0.0 Use hmapi_die() instead 274 | * 275 | * @param string $message 276 | * @param bool $display_error 277 | * 278 | * @return void 279 | */ 280 | function hxwp_die($message = '', $display_error = false) 281 | { 282 | _deprecated_function(__FUNCTION__, '2.0.0', 'hmapi_die'); 283 | 284 | hmapi_die($message, $display_error); 285 | } 286 | 287 | /** 288 | * Validate HTMX request (Legacy HXWP version - deprecated) 289 | * Checks if the nonce is valid and optionally validates the action. 290 | * 291 | * @since 2023-12-15 292 | * @deprecated 2.0.0 Use hmapi_validate_request() instead 293 | * 294 | * @param array|null $hxvals The HTMX values array (optional, will use $_REQUEST if not provided) 295 | * @param string|null $action The expected action (optional) 296 | * 297 | * @return bool 298 | */ 299 | function hxwp_validate_request($hxvals = null, $action = null) 300 | { 301 | _deprecated_function(__FUNCTION__, '2.0.0', 'hmapi_validate_request'); 302 | 303 | return hmapi_validate_request($hxvals, $action); 304 | } 305 | -------------------------------------------------------------------------------- /includes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | You shall not pass! 14 | 15 | 16 | 17 | 18 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | You shall not pass! 14 | 15 | 16 | 17 | 18 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hypermedia-api-wp", 3 | "version": "1.3.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "hypermedia-api-wp", 9 | "version": "1.3.0", 10 | "hasInstallScript": true, 11 | "license": "GPL-2.0-or-later", 12 | "devDependencies": { 13 | "@starfederation/datastar": "^1.0.0-beta.11", 14 | "alpinejs": "3.*", 15 | "htmx.org": "2.*", 16 | "hyperscript.org": "*" 17 | } 18 | }, 19 | "node_modules/@jridgewell/gen-mapping": { 20 | "version": "0.3.8", 21 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", 22 | "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", 23 | "dev": true, 24 | "license": "MIT", 25 | "dependencies": { 26 | "@jridgewell/set-array": "^1.2.1", 27 | "@jridgewell/sourcemap-codec": "^1.4.10", 28 | "@jridgewell/trace-mapping": "^0.3.24" 29 | }, 30 | "engines": { 31 | "node": ">=6.0.0" 32 | } 33 | }, 34 | "node_modules/@jridgewell/resolve-uri": { 35 | "version": "3.1.2", 36 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 37 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 38 | "dev": true, 39 | "license": "MIT", 40 | "engines": { 41 | "node": ">=6.0.0" 42 | } 43 | }, 44 | "node_modules/@jridgewell/set-array": { 45 | "version": "1.2.1", 46 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", 47 | "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", 48 | "dev": true, 49 | "license": "MIT", 50 | "engines": { 51 | "node": ">=6.0.0" 52 | } 53 | }, 54 | "node_modules/@jridgewell/source-map": { 55 | "version": "0.3.6", 56 | "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", 57 | "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", 58 | "dev": true, 59 | "license": "MIT", 60 | "dependencies": { 61 | "@jridgewell/gen-mapping": "^0.3.5", 62 | "@jridgewell/trace-mapping": "^0.3.25" 63 | } 64 | }, 65 | "node_modules/@jridgewell/sourcemap-codec": { 66 | "version": "1.5.0", 67 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 68 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 69 | "dev": true, 70 | "license": "MIT" 71 | }, 72 | "node_modules/@jridgewell/trace-mapping": { 73 | "version": "0.3.25", 74 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", 75 | "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", 76 | "dev": true, 77 | "license": "MIT", 78 | "dependencies": { 79 | "@jridgewell/resolve-uri": "^3.1.0", 80 | "@jridgewell/sourcemap-codec": "^1.4.14" 81 | } 82 | }, 83 | "node_modules/@starfederation/datastar": { 84 | "version": "1.0.0-beta.11", 85 | "resolved": "https://registry.npmjs.org/@starfederation/datastar/-/datastar-1.0.0-beta.11.tgz", 86 | "integrity": "sha512-62TtP/Rm8HVnWxZm1rqhZo+0F57V7A6bKE0FMFMP+1ZeRoDd3lBqYUEdcbSPtIYf9fjoPEUd4TU3bgWS0CGy9w==", 87 | "dev": true, 88 | "license": "MIT" 89 | }, 90 | "node_modules/@vue/reactivity": { 91 | "version": "3.1.5", 92 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz", 93 | "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==", 94 | "dev": true, 95 | "license": "MIT", 96 | "dependencies": { 97 | "@vue/shared": "3.1.5" 98 | } 99 | }, 100 | "node_modules/@vue/shared": { 101 | "version": "3.1.5", 102 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz", 103 | "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==", 104 | "dev": true, 105 | "license": "MIT" 106 | }, 107 | "node_modules/acorn": { 108 | "version": "8.14.1", 109 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", 110 | "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", 111 | "dev": true, 112 | "license": "MIT", 113 | "bin": { 114 | "acorn": "bin/acorn" 115 | }, 116 | "engines": { 117 | "node": ">=0.4.0" 118 | } 119 | }, 120 | "node_modules/alpinejs": { 121 | "version": "3.14.9", 122 | "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.9.tgz", 123 | "integrity": "sha512-gqSOhTEyryU9FhviNqiHBHzgjkvtukq9tevew29fTj+ofZtfsYriw4zPirHHOAy9bw8QoL3WGhyk7QqCh5AYlw==", 124 | "dev": true, 125 | "license": "MIT", 126 | "dependencies": { 127 | "@vue/reactivity": "~3.1.1" 128 | } 129 | }, 130 | "node_modules/buffer-from": { 131 | "version": "1.1.2", 132 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 133 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 134 | "dev": true, 135 | "license": "MIT" 136 | }, 137 | "node_modules/commander": { 138 | "version": "2.20.3", 139 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 140 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 141 | "dev": true, 142 | "license": "MIT" 143 | }, 144 | "node_modules/htmx.org": { 145 | "version": "2.0.4", 146 | "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.4.tgz", 147 | "integrity": "sha512-HLxMCdfXDOJirs3vBZl/ZLoY+c7PfM4Ahr2Ad4YXh6d22T5ltbTXFFkpx9Tgb2vvmWFMbIc3LqN2ToNkZJvyYQ==", 148 | "dev": true, 149 | "license": "0BSD" 150 | }, 151 | "node_modules/hyperscript.org": { 152 | "version": "0.9.14", 153 | "resolved": "https://registry.npmjs.org/hyperscript.org/-/hyperscript.org-0.9.14.tgz", 154 | "integrity": "sha512-ugmojsQQUMmXcnwaXYiYf8L3GbeANy/m59EmE/0Z6C5eQ52fOuSrvFkuEIejG9BdpbYB4iTtoYGqV99eYqDVMA==", 155 | "dev": true, 156 | "license": "BSD 2-Clause", 157 | "dependencies": { 158 | "markdown-it-deflist": "^2.1.0", 159 | "terser": "^5.14.1" 160 | }, 161 | "bin": { 162 | "_hyperscript": "src/node-hyperscript.js" 163 | } 164 | }, 165 | "node_modules/markdown-it-deflist": { 166 | "version": "2.1.0", 167 | "resolved": "https://registry.npmjs.org/markdown-it-deflist/-/markdown-it-deflist-2.1.0.tgz", 168 | "integrity": "sha512-3OuqoRUlSxJiuQYu0cWTLHNhhq2xtoSFqsZK8plANg91+RJQU1ziQ6lA2LzmFAEes18uPBsHZpcX6We5l76Nzg==", 169 | "dev": true, 170 | "license": "MIT" 171 | }, 172 | "node_modules/source-map": { 173 | "version": "0.6.1", 174 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 175 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 176 | "dev": true, 177 | "license": "BSD-3-Clause", 178 | "engines": { 179 | "node": ">=0.10.0" 180 | } 181 | }, 182 | "node_modules/source-map-support": { 183 | "version": "0.5.21", 184 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", 185 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", 186 | "dev": true, 187 | "license": "MIT", 188 | "dependencies": { 189 | "buffer-from": "^1.0.0", 190 | "source-map": "^0.6.0" 191 | } 192 | }, 193 | "node_modules/terser": { 194 | "version": "5.39.0", 195 | "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", 196 | "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", 197 | "dev": true, 198 | "license": "BSD-2-Clause", 199 | "dependencies": { 200 | "@jridgewell/source-map": "^0.3.3", 201 | "acorn": "^8.8.2", 202 | "commander": "^2.20.0", 203 | "source-map-support": "~0.5.20" 204 | }, 205 | "bin": { 206 | "terser": "bin/terser" 207 | }, 208 | "engines": { 209 | "node": ">=10" 210 | } 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hypermedia-api-wordpress", 3 | "author": "Esteban Cuevas", 4 | "license": "GPL-2.0-or-later", 5 | "version": "2.0.0", 6 | "description": "WordPress plugin providing API endpoints and integration for hypermedia libraries like HTMX, AlpineJS, and Datastar.", 7 | "keywords": [], 8 | "main": "index.js", 9 | "scripts": { 10 | "version-bump": "node -e \"const pkg = require('./package.json'); const currentVersion = pkg.version; console.log('Current version ' + currentVersion); const readline = require('readline').createInterface({ input: process.stdin, output: process.stdout }); readline.question('Enter new version (semver): ', (newVersion) => { pkg.version = newVersion; require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2)); const fs = require('fs'); const files = ['composer.json', 'README.txt', 'api-for-htmx.php', 'SECURITY.md']; const regex = new RegExp(currentVersion + '(?!\\d)', 'gm'); for (const file of files) { let data = fs.readFileSync(file, 'utf8'); data = data.replace(regex, newVersion); fs.writeFileSync(file, data, 'utf8'); } readline.close(); console.log('Bumped from ' + currentVersion + ' to ' + newVersion); console.log('Version updated successfully!'); });\"", 11 | "download-libraries": "php .ci/update-libraries.php", 12 | "update-libraries": "php .ci/update-libraries.php", 13 | "update-htmx": "npm run update-libraries -- --library=htmx", 14 | "update-htmx-extensions": "npm run update-libraries -- --library=htmx-extensions", 15 | "update-hyperscript": "npm run update-libraries -- --library=hyperscript", 16 | "update-alpinejs": "npm run update-libraries -- --library=alpinejs", 17 | "update-alpine-ajax": "npm run update-libraries -- --library=alpine-ajax", 18 | "update-datastar": "npm run update-libraries -- --library=datastar", 19 | "update-all": "npm run update-libraries -- --all", 20 | "postinstall": "echo '📋 To download all libraries, run: npm run update-all'" 21 | }, 22 | "devDependencies": { 23 | "alpinejs": "3.*", 24 | "htmx.org": "2.*", 25 | "hyperscript.org": "*", 26 | "@starfederation/datastar": "^1.0.0-beta.11" 27 | }, 28 | "volta": { 29 | "node": "22.15.0", 30 | "npm": "10.8.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Admin/Activation.php: -------------------------------------------------------------------------------- 1 | get_arg('api_url') && $this->get_arg('title')) { 60 | return $this->render_info_card(); 61 | } 62 | 63 | // Debug table rendering 64 | if ($this->get_arg('debug_data')) { 65 | return $this->render_debug_table(); 66 | } 67 | 68 | // Raw HTML content rendering 69 | return $this->get_arg('content', ''); 70 | } 71 | 72 | /** 73 | * Renders an info card with styled display. 74 | * 75 | * @return string The rendered info card HTML. 76 | */ 77 | private function render_info_card(): string 78 | { 79 | $api_url = $this->get_arg('api_url', ''); 80 | $title = $this->get_arg('title', ''); 81 | $description = $this->get_arg('description', ''); 82 | 83 | return sprintf( 84 | '
85 |
86 | 🔗 87 | %s 88 |
89 | %s 90 |

%s

91 |
', 92 | esc_html($title), 93 | esc_html($api_url), 94 | esc_html($description) 95 | ); 96 | } 97 | 98 | /** 99 | * Renders a debug table with technical information. 100 | * 101 | * @return string The rendered debug table HTML. 102 | */ 103 | private function render_debug_table(): string 104 | { 105 | $debug_data = $this->get_arg('debug_data', []); 106 | $table_title = $this->get_arg('table_title', esc_html__('Debug Information', 'api-for-htmx')); 107 | 108 | if (empty($debug_data)) { 109 | return '

' . esc_html__('No debug data available.', 'api-for-htmx') . '

'; 110 | } 111 | 112 | $html = '

' . esc_html($table_title) . '

'; 113 | $html .= ''; 114 | 115 | // Add table headers if provided 116 | if ($this->get_arg('table_headers')) { 117 | $headers = $this->get_arg('table_headers'); 118 | $html .= ''; 119 | foreach ($headers as $header) { 120 | $style = isset($header['style']) ? ' style="' . esc_attr($header['style']) . '"' : ''; 121 | $html .= '' . esc_html($header['text']) . ''; 122 | } 123 | $html .= ''; 124 | } 125 | 126 | $html .= ''; 127 | 128 | foreach ($debug_data as $key => $value) { 129 | if (is_array($value)) { 130 | // Handle nested arrays (like CDN URLs with version info) 131 | if (isset($value['version']) && isset($value['url'])) { 132 | $html .= ''; 133 | $html .= ''; 134 | $html .= ''; 135 | $html .= ''; 136 | $html .= ''; 137 | } 138 | } else { 139 | // Simple key-value pairs 140 | $html .= ''; 141 | $html .= ''; 142 | $html .= ''; 143 | $html .= ''; 144 | } 145 | } 146 | 147 | $html .= '
' . esc_html(ucfirst(str_replace('_', ' ', $key))) . '' . esc_html($value['version']) . '' . esc_html($value['url']) . '
' . esc_html($key) . '' . esc_html($value) . '
'; 148 | 149 | return $html; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Admin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | You shall not pass! 14 | 15 | 16 | 17 | 18 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/Compatibility.php: -------------------------------------------------------------------------------- 1 | . 24 | * Currently supports htmx-config meta tag. 25 | * 26 | * @since 2023-12-04 27 | * @return void 28 | */ 29 | public function insert_config_meta_tag(): void 30 | { 31 | $options = get_option('hmapi_options'); 32 | $active_library = $options['active_hypermedia_library'] ?? 'htmx'; // Default to htmx if not set 33 | 34 | // Only output htmx-config if HTMX is the active library 35 | if ('htmx' !== $active_library) { 36 | return; 37 | } 38 | 39 | $meta_config_content = $options['hmapi_meta_config_content'] ?? ''; 40 | 41 | if (empty($meta_config_content)) { 42 | return; 43 | } 44 | 45 | $meta_config_content = apply_filters('hmapi/meta/config_content', $meta_config_content); 46 | 47 | // Sanitize the content for the meta tag 48 | $escaped_meta_config_content = esc_attr($meta_config_content); 49 | $meta_tag = ""; 50 | 51 | // Allow filtering of the entire meta tag 52 | $meta_tag = apply_filters('hmapi/meta/insert_config_tag', $meta_tag, $escaped_meta_config_content); 53 | 54 | /* 55 | * Action hook before echoing the htmx-config meta tag. 56 | * 57 | * @since 2.0.0 58 | * @param string $meta_tag The complete HTML meta tag. 59 | */ 60 | do_action('hmapi/meta/before_echo_config_tag', $meta_tag); 61 | 62 | echo $meta_tag; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Main.php: -------------------------------------------------------------------------------- 1 | router = $router; 97 | $this->render = $render; 98 | $this->config = $config; 99 | $this->compatibility = $compatibility; 100 | $this->theme_support = $theme_support; 101 | 102 | $this->assets_manager = new Assets($this); 103 | 104 | if (is_admin()) { 105 | $this->options = new Options($this); 106 | new Activation(); 107 | } 108 | do_action('hmapi/init_construct_end'); 109 | } 110 | 111 | /** 112 | * Returns all CDN URLs for core libraries and HTMX extensions. 113 | * 114 | * This method serves as the centralized source for all external library URLs and versions. 115 | * It provides CDN URLs for core hypermedia libraries (HTMX, Alpine.js, Datastar, etc.) 116 | * and all available HTMX extensions with their specific versions. 117 | * 118 | * The returned array structure allows for: 119 | * - Consistent versioning across the plugin 120 | * - Dynamic library loading based on available CDN resources 121 | * - Easy maintenance and updates of library versions 122 | * - Validation of available extensions in the admin interface 123 | * 124 | * @since 2023-11-22 125 | * @since 1.3.0 Refactored to include version information and centralized URL management 126 | * 127 | * @return array { 128 | * Array of CDN URLs and versions for libraries and extensions. 129 | * 130 | * @type array $htmx { 131 | * HTMX core library information. 132 | * 133 | * @type string $url CDN URL for HTMX core library. 134 | * @type string $version Version number of HTMX library. 135 | * } 136 | * @type array $hyperscript { 137 | * Hyperscript library information. 138 | * 139 | * @type string $url CDN URL for Hyperscript library. 140 | * @type string $version Version number of Hyperscript library. 141 | * } 142 | * @type array $alpinejs { 143 | * Alpine.js core library information. 144 | * 145 | * @type string $url CDN URL for Alpine.js library. 146 | * @type string $version Version number of Alpine.js library. 147 | * } 148 | * @type array $alpine_ajax { 149 | * Alpine.js AJAX extension information. 150 | * 151 | * @type string $url CDN URL for Alpine AJAX extension. 152 | * @type string $version Version number of Alpine AJAX extension. 153 | * } 154 | * @type array $datastar { 155 | * Datastar library information. 156 | * 157 | * @type string $url CDN URL for Datastar library. 158 | * @type string $version Version number of Datastar library. 159 | * } 160 | * @type array $htmx_extensions { 161 | * Collection of HTMX extensions with their URLs and versions. 162 | * Each extension follows the same structure with 'url' and 'version' keys. 163 | * Available extensions include: sse, head-support, response-targets, 164 | * loading-states, ws, preload, alpine-morph, json-enc, remove-me, 165 | * debug, multi-swap, class-tools, disable-element, client-side-templates, 166 | * ajax-header, path-params, event-header, restored, include-vals, 167 | * path-deps, morphdom-swap, method-override. 168 | * 169 | * @type array $extension_name { 170 | * Individual extension information. 171 | * 172 | * @type string $url CDN URL for the extension. 173 | * @type string $version Version number of the extension. 174 | * } 175 | * } 176 | * } 177 | * 178 | * @example 179 | * // Get all CDN URLs 180 | * $cdn_urls = $this->get_cdn_urls(); 181 | * 182 | * // Get HTMX core URL 183 | * $htmx_url = $cdn_urls['htmx']['url']; 184 | * 185 | * // Get all HTMX extensions 186 | * $extensions = $cdn_urls['htmx_extensions']; 187 | * 188 | * // Check if specific extension is available 189 | * if (isset($cdn_urls['htmx_extensions']['sse'])) { 190 | * $sse_url = $cdn_urls['htmx_extensions']['sse']['url']; 191 | * } 192 | * 193 | * @see Assets::enqueue_scripts_logic() For usage in script enqueuing 194 | * @see Admin\Options::get_htmx_extensions() For admin interface integration 195 | */ 196 | public function get_cdn_urls(): array 197 | { 198 | return [ 199 | 'htmx' => [ 200 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx.org@2/dist/htmx.min.js', 201 | 'version' => '2.0.4', 202 | ], 203 | 'hyperscript' => [ 204 | 'url' => 'https://cdn.jsdelivr.net/npm/hyperscript.org/dist/hdb.min.js', 205 | 'version' => '0.9.14', 206 | ], 207 | 'alpinejs' => [ 208 | 'url' => 'https://cdn.jsdelivr.net/npm/alpinejs/dist/cdn.min.js', 209 | 'version' => '3.14.9', 210 | ], 211 | 'alpine_ajax' => [ 212 | 'url' => 'https://cdn.jsdelivr.net/npm/@imacrayon/alpine-ajax/dist/cdn.min.js', 213 | 'version' => '0.12.2', 214 | ], 215 | 'datastar' => [ 216 | 'url' => 'https://cdn.jsdelivr.net/npm/@starfederation/datastar/dist/datastar.min.js', 217 | 'version' => '1.0.0-beta.11', 218 | ], 219 | 'htmx_extensions' => [ 220 | 'sse' => [ 221 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-sse/sse.min.js', 222 | 'version' => '2.2.3', 223 | ], 224 | 'head-support' => [ 225 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-head-support/head-support.min.js', 226 | 'version' => '2.0.4', 227 | ], 228 | 'response-targets' => [ 229 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-response-targets/response-targets.min.js', 230 | 'version' => '2.0.3', 231 | ], 232 | 'loading-states' => [ 233 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-loading-states/loading-states.min.js', 234 | 'version' => '2.0.1', 235 | ], 236 | 'ws' => [ 237 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-ws/ws.min.js', 238 | 'version' => '2.0.3', 239 | ], 240 | 'preload' => [ 241 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-preload/preload.min.js', 242 | 'version' => '2.1.1', 243 | ], 244 | 'alpine-morph' => [ 245 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-alpine-morph/alpine-morph.min.js', 246 | 'version' => '2.0.1', 247 | ], 248 | 'json-enc' => [ 249 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-json-enc/json-enc.min.js', 250 | 'version' => '2.0.2', 251 | ], 252 | 'remove-me' => [ 253 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-remove-me/remove-me.min.js', 254 | 'version' => '2.0.1', 255 | ], 256 | 'debug' => [ 257 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-debug/debug.min.js', 258 | 'version' => '2.0.1', 259 | ], 260 | 'multi-swap' => [ 261 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-multi-swap/multi-swap.min.js', 262 | 'version' => '2.0.1', 263 | ], 264 | 'class-tools' => [ 265 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-class-tools/class-tools.min.js', 266 | 'version' => '2.0.2', 267 | ], 268 | 'disable-element' => [ 269 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-disable-element/disable-element.min.js', 270 | 'version' => '2.0.1', 271 | ], 272 | 'client-side-templates' => [ 273 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-client-side-templates/client-side-templates.min.js', 274 | 'version' => '2.0.1', 275 | ], 276 | 'ajax-header' => [ 277 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-ajax-header/dist/ajax-header.esm.min.js', 278 | 'version' => '2.0.2', 279 | ], 280 | 'path-params' => [ 281 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-path-params/dist/path-params.esm.min.js', 282 | 'version' => '2.0.1', 283 | ], 284 | 'event-header' => [ 285 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-event-header/dist/event-header.esm.min.js', 286 | 'version' => '2.0.1', 287 | ], 288 | 'restored' => [ 289 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-restored/dist/restored.esm.min.js', 290 | 'version' => '2.0.1', 291 | ], 292 | 'include-vals' => [ 293 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-include-vals/dist/include-vals.esm.min.js', 294 | 'version' => '2.0.1', 295 | ], 296 | 'path-deps' => [ 297 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-path-deps/path-deps.min.js', 298 | 'version' => '2.0.1', 299 | ], 300 | 'morphdom-swap' => [ 301 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-morphdom-swap/dist/morphdom-swap.esm.min.js', 302 | 'version' => '2.0.1', 303 | ], 304 | 'method-override' => [ 305 | 'url' => 'https://cdn.jsdelivr.net/npm/htmx-ext-method-override/dist/method-override.esm.min.js', 306 | 'version' => '2.0.2', 307 | ], 308 | ], 309 | ]; 310 | } 311 | 312 | /** 313 | * Main HMApi Instance. 314 | * Initializes and registers all WordPress hooks and actions for the plugin. 315 | * 316 | * This method serves as the main entry point for plugin initialization. 317 | * It registers all necessary WordPress hooks and starts the plugin components. 318 | * 319 | * @since 2023-11-22 320 | * 321 | * @return void 322 | */ 323 | public function run() 324 | { 325 | do_action('hmapi/init_run_start'); 326 | 327 | add_action('init', [$this->router, 'register_main_route']); 328 | add_action('template_redirect', [$this->render, 'load_template']); 329 | add_action('wp_head', [$this->config, 'insert_config_meta_tag']); 330 | $this->compatibility->run(); 331 | $this->theme_support->run(); 332 | 333 | do_action('hmapi/init_run_end'); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /src/Router.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | You shall not pass! 14 | 15 | 16 | 17 | 18 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /uninstall.php: -------------------------------------------------------------------------------- 1 | get_results("SELECT option_name FROM $wpdb->options WHERE option_name LIKE '_hxwp_%' OR option_name LIKE 'hxwp_%' OR option_name LIKE '_hmapi_%' OR option_name LIKE 'hmapi_%'"); 25 | 26 | if (is_array($hmapi_options) && !empty($hmapi_options)) { 27 | foreach ($hmapi_options as $option) { 28 | delete_option($option->option_name); 29 | } 30 | } 31 | 32 | // Flush rewrite rules 33 | flush_rewrite_rules(); 34 | -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/adbario/php-dot-notation/src/Dot.php', 10 | 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 11 | 'HMApi\\Admin\\Activation' => $baseDir . '/src/Admin/Activation.php', 12 | 'HMApi\\Admin\\Options' => $baseDir . '/src/Admin/Options.php', 13 | 'HMApi\\Admin\\WPSettingsOptions' => $baseDir . '/src/Admin/WPSettingsOptions.php', 14 | 'HMApi\\Assets' => $baseDir . '/src/Assets.php', 15 | 'HMApi\\Compatibility' => $baseDir . '/src/Compatibility.php', 16 | 'HMApi\\Config' => $baseDir . '/src/Config.php', 17 | 'HMApi\\Main' => $baseDir . '/src/Main.php', 18 | 'HMApi\\Render' => $baseDir . '/src/Render.php', 19 | 'HMApi\\Router' => $baseDir . '/src/Router.php', 20 | 'HMApi\\Theme' => $baseDir . '/src/Theme.php', 21 | 'Jeffreyvr\\WPSettings\\EnqueueManager' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/EnqueueManager.php', 22 | 'Jeffreyvr\\WPSettings\\Enqueuer' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Enqueuer.php', 23 | 'Jeffreyvr\\WPSettings\\Error' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Error.php', 24 | 'Jeffreyvr\\WPSettings\\Flash' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Flash.php', 25 | 'Jeffreyvr\\WPSettings\\Option' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Option.php', 26 | 'Jeffreyvr\\WPSettings\\Options\\Checkbox' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Options/Checkbox.php', 27 | 'Jeffreyvr\\WPSettings\\Options\\Choices' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Options/Choices.php', 28 | 'Jeffreyvr\\WPSettings\\Options\\CodeEditor' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Options/CodeEditor.php', 29 | 'Jeffreyvr\\WPSettings\\Options\\Color' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Options/Color.php', 30 | 'Jeffreyvr\\WPSettings\\Options\\Image' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Options/Image.php', 31 | 'Jeffreyvr\\WPSettings\\Options\\Media' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Options/Media.php', 32 | 'Jeffreyvr\\WPSettings\\Options\\OptionAbstract' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Options/OptionAbstract.php', 33 | 'Jeffreyvr\\WPSettings\\Options\\Select' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Options/Select.php', 34 | 'Jeffreyvr\\WPSettings\\Options\\SelectMultiple' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Options/SelectMultiple.php', 35 | 'Jeffreyvr\\WPSettings\\Options\\Text' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Options/Text.php', 36 | 'Jeffreyvr\\WPSettings\\Options\\Textarea' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Options/Textarea.php', 37 | 'Jeffreyvr\\WPSettings\\Options\\Video' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Options/Video.php', 38 | 'Jeffreyvr\\WPSettings\\Options\\WPEditor' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Options/WPEditor.php', 39 | 'Jeffreyvr\\WPSettings\\Section' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Section.php', 40 | 'Jeffreyvr\\WPSettings\\Tab' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Tab.php', 41 | 'Jeffreyvr\\WPSettings\\Traits\\HasOptionLevel' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/Traits/HasOptionLevel.php', 42 | 'Jeffreyvr\\WPSettings\\WPSettings' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/WPSettings.php', 43 | ); 44 | -------------------------------------------------------------------------------- /vendor/composer/autoload_files.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/adbario/php-dot-notation/src/helpers.php', 10 | '53b3b608b18ef5b655166dcd8c512966' => $vendorDir . '/jeffreyvanrossum/wp-settings/src/helpers.php', 11 | '60bb17a79c7758c8c553181dcced7422' => $baseDir . '/api-for-htmx.php', 12 | ); 13 | -------------------------------------------------------------------------------- /vendor/composer/autoload_namespaces.php: -------------------------------------------------------------------------------- 1 | array($vendorDir . '/jeffreyvanrossum/wp-settings/src'), 10 | 'HMApi\\' => array($baseDir . '/src'), 11 | 'Adbar\\' => array($vendorDir . '/adbario/php-dot-notation/src'), 12 | ); 13 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | register(true); 35 | 36 | $filesToLoad = \Composer\Autoload\ComposerStaticInita313c11744e7bb00222a21571d0c39da::$files; 37 | $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { 38 | if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { 39 | $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; 40 | 41 | require $file; 42 | } 43 | }, null, null); 44 | foreach ($filesToLoad as $fileIdentifier => $file) { 45 | $requireFile($fileIdentifier, $file); 46 | } 47 | 48 | return $loader; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | __DIR__ . '/..' . '/adbario/php-dot-notation/src/helpers.php', 11 | '53b3b608b18ef5b655166dcd8c512966' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/helpers.php', 12 | '60bb17a79c7758c8c553181dcced7422' => __DIR__ . '/../..' . '/api-for-htmx.php', 13 | ); 14 | 15 | public static $prefixLengthsPsr4 = array ( 16 | 'J' => 17 | array ( 18 | 'Jeffreyvr\\WPSettings\\' => 21, 19 | ), 20 | 'H' => 21 | array ( 22 | 'HMApi\\' => 6, 23 | ), 24 | 'A' => 25 | array ( 26 | 'Adbar\\' => 6, 27 | ), 28 | ); 29 | 30 | public static $prefixDirsPsr4 = array ( 31 | 'Jeffreyvr\\WPSettings\\' => 32 | array ( 33 | 0 => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src', 34 | ), 35 | 'HMApi\\' => 36 | array ( 37 | 0 => __DIR__ . '/../..' . '/src', 38 | ), 39 | 'Adbar\\' => 40 | array ( 41 | 0 => __DIR__ . '/..' . '/adbario/php-dot-notation/src', 42 | ), 43 | ); 44 | 45 | public static $classMap = array ( 46 | 'Adbar\\Dot' => __DIR__ . '/..' . '/adbario/php-dot-notation/src/Dot.php', 47 | 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 48 | 'HMApi\\Admin\\Activation' => __DIR__ . '/../..' . '/src/Admin/Activation.php', 49 | 'HMApi\\Admin\\Options' => __DIR__ . '/../..' . '/src/Admin/Options.php', 50 | 'HMApi\\Admin\\WPSettingsOptions' => __DIR__ . '/../..' . '/src/Admin/WPSettingsOptions.php', 51 | 'HMApi\\Assets' => __DIR__ . '/../..' . '/src/Assets.php', 52 | 'HMApi\\Compatibility' => __DIR__ . '/../..' . '/src/Compatibility.php', 53 | 'HMApi\\Config' => __DIR__ . '/../..' . '/src/Config.php', 54 | 'HMApi\\Main' => __DIR__ . '/../..' . '/src/Main.php', 55 | 'HMApi\\Render' => __DIR__ . '/../..' . '/src/Render.php', 56 | 'HMApi\\Router' => __DIR__ . '/../..' . '/src/Router.php', 57 | 'HMApi\\Theme' => __DIR__ . '/../..' . '/src/Theme.php', 58 | 'Jeffreyvr\\WPSettings\\EnqueueManager' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/EnqueueManager.php', 59 | 'Jeffreyvr\\WPSettings\\Enqueuer' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Enqueuer.php', 60 | 'Jeffreyvr\\WPSettings\\Error' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Error.php', 61 | 'Jeffreyvr\\WPSettings\\Flash' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Flash.php', 62 | 'Jeffreyvr\\WPSettings\\Option' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Option.php', 63 | 'Jeffreyvr\\WPSettings\\Options\\Checkbox' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Options/Checkbox.php', 64 | 'Jeffreyvr\\WPSettings\\Options\\Choices' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Options/Choices.php', 65 | 'Jeffreyvr\\WPSettings\\Options\\CodeEditor' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Options/CodeEditor.php', 66 | 'Jeffreyvr\\WPSettings\\Options\\Color' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Options/Color.php', 67 | 'Jeffreyvr\\WPSettings\\Options\\Image' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Options/Image.php', 68 | 'Jeffreyvr\\WPSettings\\Options\\Media' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Options/Media.php', 69 | 'Jeffreyvr\\WPSettings\\Options\\OptionAbstract' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Options/OptionAbstract.php', 70 | 'Jeffreyvr\\WPSettings\\Options\\Select' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Options/Select.php', 71 | 'Jeffreyvr\\WPSettings\\Options\\SelectMultiple' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Options/SelectMultiple.php', 72 | 'Jeffreyvr\\WPSettings\\Options\\Text' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Options/Text.php', 73 | 'Jeffreyvr\\WPSettings\\Options\\Textarea' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Options/Textarea.php', 74 | 'Jeffreyvr\\WPSettings\\Options\\Video' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Options/Video.php', 75 | 'Jeffreyvr\\WPSettings\\Options\\WPEditor' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Options/WPEditor.php', 76 | 'Jeffreyvr\\WPSettings\\Section' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Section.php', 77 | 'Jeffreyvr\\WPSettings\\Tab' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Tab.php', 78 | 'Jeffreyvr\\WPSettings\\Traits\\HasOptionLevel' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/Traits/HasOptionLevel.php', 79 | 'Jeffreyvr\\WPSettings\\WPSettings' => __DIR__ . '/..' . '/jeffreyvanrossum/wp-settings/src/WPSettings.php', 80 | ); 81 | 82 | public static function getInitializer(ClassLoader $loader) 83 | { 84 | return \Closure::bind(function () use ($loader) { 85 | $loader->prefixLengthsPsr4 = ComposerStaticInita313c11744e7bb00222a21571d0c39da::$prefixLengthsPsr4; 86 | $loader->prefixDirsPsr4 = ComposerStaticInita313c11744e7bb00222a21571d0c39da::$prefixDirsPsr4; 87 | $loader->classMap = ComposerStaticInita313c11744e7bb00222a21571d0c39da::$classMap; 88 | 89 | }, null, ClassLoader::class); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | { 4 | "name": "adbario/php-dot-notation", 5 | "version": "3.3.0", 6 | "version_normalized": "3.3.0.0", 7 | "source": { 8 | "type": "git", 9 | "url": "https://github.com/adbario/php-dot-notation.git", 10 | "reference": "a94ce4493d19ea430baa8d7d210a2c9bd7129fc2" 11 | }, 12 | "dist": { 13 | "type": "zip", 14 | "url": "https://api.github.com/repos/adbario/php-dot-notation/zipball/a94ce4493d19ea430baa8d7d210a2c9bd7129fc2", 15 | "reference": "a94ce4493d19ea430baa8d7d210a2c9bd7129fc2", 16 | "shasum": "" 17 | }, 18 | "require": { 19 | "ext-json": "*", 20 | "php": "^7.4 || ^8.0" 21 | }, 22 | "require-dev": { 23 | "phpstan/phpstan": "^1.8", 24 | "phpunit/phpunit": "^9.5", 25 | "squizlabs/php_codesniffer": "^3.7" 26 | }, 27 | "time": "2023-02-24T20:27:50+00:00", 28 | "type": "library", 29 | "installation-source": "dist", 30 | "autoload": { 31 | "files": [ 32 | "src/helpers.php" 33 | ], 34 | "psr-4": { 35 | "Adbar\\": "src" 36 | } 37 | }, 38 | "notification-url": "https://packagist.org/downloads/", 39 | "license": [ 40 | "MIT" 41 | ], 42 | "authors": [ 43 | { 44 | "name": "Riku Särkinen", 45 | "email": "riku@adbar.io" 46 | } 47 | ], 48 | "description": "PHP dot notation access to arrays", 49 | "homepage": "https://github.com/adbario/php-dot-notation", 50 | "keywords": [ 51 | "ArrayAccess", 52 | "dotnotation" 53 | ], 54 | "support": { 55 | "issues": "https://github.com/adbario/php-dot-notation/issues", 56 | "source": "https://github.com/adbario/php-dot-notation/tree/3.3.0" 57 | }, 58 | "install-path": "../adbario/php-dot-notation" 59 | }, 60 | { 61 | "name": "jeffreyvanrossum/wp-settings", 62 | "version": "1.2.2", 63 | "version_normalized": "1.2.2.0", 64 | "source": { 65 | "type": "git", 66 | "url": "https://github.com/jeffreyvr/wp-settings.git", 67 | "reference": "89f4713690a800c4e23f7578f12035ce4f6d0007" 68 | }, 69 | "dist": { 70 | "type": "zip", 71 | "url": "https://api.github.com/repos/jeffreyvr/wp-settings/zipball/89f4713690a800c4e23f7578f12035ce4f6d0007", 72 | "reference": "89f4713690a800c4e23f7578f12035ce4f6d0007", 73 | "shasum": "" 74 | }, 75 | "require": { 76 | "adbario/php-dot-notation": "^3.3", 77 | "php": "^7.4|^8.0" 78 | }, 79 | "require-dev": { 80 | "laravel/pint": "^1.4", 81 | "phpcompatibility/php-compatibility": "*", 82 | "spatie/ray": "^1.36" 83 | }, 84 | "time": "2023-10-20T20:18:58+00:00", 85 | "type": "library", 86 | "installation-source": "dist", 87 | "autoload": { 88 | "files": [ 89 | "src/helpers.php" 90 | ], 91 | "psr-4": { 92 | "Jeffreyvr\\WPSettings\\": "src" 93 | } 94 | }, 95 | "notification-url": "https://packagist.org/downloads/", 96 | "license": [ 97 | "MIT" 98 | ], 99 | "authors": [ 100 | { 101 | "name": "Jeffrey van Rossum", 102 | "email": "jeffrey@vanrossum.dev" 103 | } 104 | ], 105 | "description": "Handy wrapper to make creating WordPress settings pages a breeze.", 106 | "support": { 107 | "issues": "https://github.com/jeffreyvr/wp-settings/issues", 108 | "source": "https://github.com/jeffreyvr/wp-settings/tree/1.2.2" 109 | }, 110 | "funding": [ 111 | { 112 | "url": "https://vanrossum.dev/donate", 113 | "type": "custom" 114 | }, 115 | { 116 | "url": "https://github.com/jeffreyvr", 117 | "type": "github" 118 | } 119 | ], 120 | "install-path": "../jeffreyvanrossum/wp-settings" 121 | } 122 | ], 123 | "dev": true, 124 | "dev-package-names": [] 125 | } 126 | -------------------------------------------------------------------------------- /vendor/composer/installed.php: -------------------------------------------------------------------------------- 1 | array( 3 | 'name' => 'estebanforge/hypermedia-api-wordpress', 4 | 'pretty_version' => '2.0.0', 5 | 'version' => '2.0.0.0', 6 | 'reference' => null, 7 | 'type' => 'wordpress-plugin', 8 | 'install_path' => __DIR__ . '/../../', 9 | 'aliases' => array(), 10 | 'dev' => true, 11 | ), 12 | 'versions' => array( 13 | 'adbario/php-dot-notation' => array( 14 | 'pretty_version' => '3.3.0', 15 | 'version' => '3.3.0.0', 16 | 'reference' => 'a94ce4493d19ea430baa8d7d210a2c9bd7129fc2', 17 | 'type' => 'library', 18 | 'install_path' => __DIR__ . '/../adbario/php-dot-notation', 19 | 'aliases' => array(), 20 | 'dev_requirement' => false, 21 | ), 22 | 'estebanforge/hypermedia-api-wordpress' => array( 23 | 'pretty_version' => '2.0.0', 24 | 'version' => '2.0.0.0', 25 | 'reference' => null, 26 | 'type' => 'wordpress-plugin', 27 | 'install_path' => __DIR__ . '/../../', 28 | 'aliases' => array(), 29 | 'dev_requirement' => false, 30 | ), 31 | 'jeffreyvanrossum/wp-settings' => array( 32 | 'pretty_version' => '1.2.2', 33 | 'version' => '1.2.2.0', 34 | 'reference' => '89f4713690a800c4e23f7578f12035ce4f6d0007', 35 | 'type' => 'library', 36 | 'install_path' => __DIR__ . '/../jeffreyvanrossum/wp-settings', 37 | 'aliases' => array(), 38 | 'dev_requirement' => false, 39 | ), 40 | ), 41 | ); 42 | -------------------------------------------------------------------------------- /vendor/composer/platform_check.php: -------------------------------------------------------------------------------- 1 | = 80100)) { 8 | $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; 9 | } 10 | 11 | if ($issues) { 12 | if (!headers_sent()) { 13 | header('HTTP/1.1 500 Internal Server Error'); 14 | } 15 | if (!ini_get('display_errors')) { 16 | if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { 17 | fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); 18 | } elseif (!headers_sent()) { 19 | echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; 20 | } 21 | } 22 | trigger_error( 23 | 'Composer detected issues in your platform: ' . implode(' ', $issues), 24 | E_USER_ERROR 25 | ); 26 | } 27 | --------------------------------------------------------------------------------