From a24d5e0c61ea4b1a481740ceb0eae6bf0bd4c011 Mon Sep 17 00:00:00 2001 From: codad5 Date: Mon, 18 Aug 2025 11:49:16 +0100 Subject: [PATCH 1/9] Add theme override support to ViewLoader and enhance template path resolution --- src/Utils/Page.php | 19 +++++++----- src/Utils/ViewLoader.php | 62 ++++++++++++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/Utils/Page.php b/src/Utils/Page.php index c8689c2..d15e0fd 100644 --- a/src/Utils/Page.php +++ b/src/Utils/Page.php @@ -1084,6 +1084,17 @@ public function handleFrontendTemplateInclude(string $template): string } } + + // if callback just echo the output of the callback and return + if (isset($config['callback']) && is_callable($config['callback'])) { + // Call the callback function and return its output + ob_start(); + call_user_func($config['callback'], $config); + $output = ob_get_clean(); + echo $output; + return ''; // Return empty to prevent further template processing + } + // Handle the request and return custom template return $this->getFrontendTemplate($plugin_page, $config); } @@ -1115,12 +1126,6 @@ protected function getFrontendTemplate(string $slug, array $config): string } } - // Use callback if provided -// if (isset($config['callback']) && is_callable($config['callback'])) { -// // Create a temporary template file for callback -// return $this->createCallbackTemplate($config['callback'], $data); -// } - // Use template if provided $template = $config['template'] ?? null; if ($template) { @@ -1133,7 +1138,7 @@ protected function getFrontendTemplate(string $slug, array $config): string } // Create default template - return $this->createDefaultFrontendTemplate($slug, $config, $data); + return "This page has no template configured. Please create a template file named '{$slug}.php' in your plugin or theme directory."; } /** diff --git a/src/Utils/ViewLoader.php b/src/Utils/ViewLoader.php index 25337c4..83e4219 100644 --- a/src/Utils/ViewLoader.php +++ b/src/Utils/ViewLoader.php @@ -12,8 +12,6 @@ namespace Codad5\WPToolkit\Utils; -use Codad5\WPToolkit\Utils\Cache; - /** * ViewLoader utility class for loading template files. * @@ -85,11 +83,13 @@ class ViewLoader * @param array $data Data to be extracted as variables * @param bool $echo Whether to echo the output * @param string|null $base_path Override base path for this load + * @param bool $overridable Whether to check for theme overrides + * @param string $plugin_prefix Plugin prefix for theme override path * @return string|false Rendered output or false on failure */ - public static function load(string $view, array $data = [], bool $echo = true, ?string $base_path = null): string|false - { - $template_path = self::resolve_template_path($view, $base_path); + public static function load(string $view, array $data = [], bool $echo = true, ?string $base_path = null, bool $overridable = false, string $plugin_prefix = 'wptoolkit'): string|false + { + $template_path = self::resolve_template_path($view, $base_path, $overridable, $plugin_prefix); if (!$template_path) { return self::handle_template_not_found($view, $echo); @@ -171,6 +171,20 @@ public static function get(string $view, array $data = [], ?string $base_path = return self::load($view, $data, false, $base_path) ?: ''; } + /** + * Load a view with WordPress theme override support. + * + * @param string $view View path relative to plugin templates + * @param array $data Data to be extracted as variables + * @param bool $echo Whether to echo the output + * @param string $plugin_prefix Plugin prefix for theme override path + * @return string|false Rendered output or false on failure + */ + public static function get_overridable(string $view, array $data = [], bool $echo = true, string $plugin_prefix = 'wptoolkit'): string|false + { + return self::load($view, $data, $echo, null, true, $plugin_prefix); + } + /** * Check if a view exists. * @@ -443,16 +457,34 @@ public static function get_extensions(): array return self::$extensions; } - /** - * Resolve template path from view name. - * - * @param string $view View name - * @param string|null $base_path Override base path - * @return string|false Resolved path or false if not found - */ - private static function resolve_template_path(string $view, ?string $base_path = null): string|false - { - $view = ltrim($view, '/'); + /** + * Resolve template path from view name. + * + * @param string $view View name + * @param string|null $base_path Override base path + * @param bool $overridable Whether to check for theme overrides + * @param string $plugin_prefix Plugin prefix for theme override path + * @return string|false Resolved path or false if not found + */ + private static function resolve_template_path(string $view, ?string $base_path = null, bool $overridable = false, string $plugin_prefix = 'wptoolkit'): string|false + { + $view = ltrim($view, '/'); + + // Check for WordPress theme override first if overridable + if ($overridable) { + $theme_template = locate_template("{$plugin_prefix}/{$view}"); + if ($theme_template) { + return $theme_template; + } + + // Also check with .php extension if not present + if (!pathinfo($view, PATHINFO_EXTENSION)) { + $theme_template = locate_template("{$plugin_prefix}/{$view}.php"); + if ($theme_template) { + return $theme_template; + } + } + } $search_paths = []; // Add override base path first From 2ea2df7dfa832abe588507c1f2ff2612f9727a55 Mon Sep 17 00:00:00 2001 From: codad5 Date: Tue, 19 Aug 2025 12:46:53 +0100 Subject: [PATCH 2/9] Add script and style registration methods to EnqueueManager for improved asset management --- src/Utils/EnqueueManager.php | 295 +++++++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) diff --git a/src/Utils/EnqueueManager.php b/src/Utils/EnqueueManager.php index f79076f..ff990ee 100644 --- a/src/Utils/EnqueueManager.php +++ b/src/Utils/EnqueueManager.php @@ -827,6 +827,301 @@ protected function enqueueIndividualStyle(string $handle, array $config): void } } + /** + * Register a script group without enqueuing it. + */ + public function registerScriptGroup(string $group_name): static + { + if (!isset($this->script_groups[$group_name])) { + throw new InvalidArgumentException("Script group '{$group_name}' does not exist"); + } + + $group_data = $this->script_groups[$group_name]; + + if (!$this->shouldLoadGroup($group_data['config'])) { + return $this; + } + + foreach ($group_data['scripts'] as $handle => $config) { + $this->registerIndividualScript($handle, $config); + } + + return $this; + } + + /** + * Register a style group without enqueuing it. + */ + public function registerStyleGroup(string $group_name): static + { + if (!isset($this->style_groups[$group_name])) { + throw new InvalidArgumentException("Style group '{$group_name}' does not exist"); + } + + $group_data = $this->style_groups[$group_name]; + + if (!$this->shouldLoadGroup($group_data['config'])) { + return $this; + } + + foreach ($group_data['styles'] as $handle => $config) { + $this->registerIndividualStyle($handle, $config); + } + + return $this; + } + + /** + * Register multiple groups. + */ + public function registerGroups(array $group_names, string $type = 'both'): static + { + foreach ($group_names as $group_name) { + if ($type === 'both' || $type === 'scripts') { + $this->registerScriptGroup($group_name); + } + + if ($type === 'both' || $type === 'styles') { + $this->registerStyleGroup($group_name); + } + } + + return $this; + } + + /** + * Register an individual script. + */ + public function registerScript(string $handle): static + { + if (!isset($this->individual_scripts[$handle])) { + throw new InvalidArgumentException("Script '{$handle}' does not exist"); + } + + $config = $this->individual_scripts[$handle]; + $this->registerIndividualScript($handle, $config); + + return $this; + } + + /** + * Register an individual style. + */ + public function registerStyle(string $handle): static + { + if (!isset($this->individual_styles[$handle])) { + throw new InvalidArgumentException("Style '{$handle}' does not exist"); + } + + $config = $this->individual_styles[$handle]; + $this->registerIndividualStyle($handle, $config); + + return $this; + } + + /** + * Register scripts and styles by handles. + */ + public function registerByHandles(array $handles): static + { + foreach ($handles as $handle) { + // Check individual scripts first + if (isset($this->individual_scripts[$handle])) { + $this->registerIndividualScript($handle, $this->individual_scripts[$handle]); + continue; + } + + // Check individual styles + if (isset($this->individual_styles[$handle])) { + $this->registerIndividualStyle($handle, $this->individual_styles[$handle]); + continue; + } + + // Check in groups + foreach ($this->script_groups as $group_name => $group_data) { + if (isset($group_data['scripts'][$handle])) { + $this->registerIndividualScript($handle, $group_data['scripts'][$handle]); + break; + } + } + + foreach ($this->style_groups as $group_name => $group_data) { + if (isset($group_data['styles'][$handle])) { + $this->registerIndividualStyle($handle, $group_data['styles'][$handle]); + break; + } + } + } + + return $this; + } + + /** + * Register an individual script without enqueuing. + */ + protected function registerIndividualScript(string $handle, array $config): void + { + // Check condition + if ($config['condition'] && is_callable($config['condition'])) { + if (!call_user_func($config['condition'])) { + return; + } + } + + wp_register_script( + $handle, + $config['src'], + $config['deps'], + $config['version'], + $config['in_footer'] + ); + + // Add strategy (WP 6.3+) + if (!empty($config['strategy']) && function_exists('wp_script_add_data')) { + wp_script_add_data($handle, 'strategy', $config['strategy']); + } + + // Note: Localization and inline scripts are NOT added during registration + // They should only be added when the script is actually enqueued + } + + /** + * Register an individual style without enqueuing. + */ + protected function registerIndividualStyle(string $handle, array $config): void + { + // Check condition + if ($config['condition'] && is_callable($config['condition'])) { + if (!call_user_func($config['condition'])) { + return; + } + } + + wp_register_style( + $handle, + $config['src'], + $config['deps'], + $config['version'], + $config['media'] + ); + + // Note: Inline styles are NOT added during registration + // They should only be added when the style is actually enqueued + } + + /** + * Check if a script is registered. + */ + public function isScriptRegistered(string $handle): bool + { + return wp_script_is($handle, 'registered'); + } + + /** + * Check if a style is registered. + */ + public function isStyleRegistered(string $handle): bool + { + return wp_style_is($handle, 'registered'); + } + + /** + * Enqueue a previously registered script by handle. + */ + public function enqueueRegisteredScript(string $handle): EnqueueManager + { + if (!$this->isScriptRegistered($handle)) { + throw new InvalidArgumentException("Script '{$handle}' is not registered"); + } + + wp_enqueue_script($handle); + + // Find and apply localization and inline scripts + $config = $this->findScriptConfig($handle); + if ($config) { + $this->handleScriptLocalization($handle, $config); + $this->addInlineScripts($handle, $config); + } + + return $this; + } + + /** + * Enqueue a previously registered style by handle. + */ + public function enqueueRegisteredStyle(string $handle): static + { + if (!$this->isStyleRegistered($handle)) { + throw new InvalidArgumentException("Style '{$handle}' is not registered"); + } + + wp_enqueue_style($handle); + + // Find and apply inline styles + $config = $this->findStyleConfig($handle); + if ($config) { + if (!empty($config['inline_before'])) { + wp_add_inline_style($handle, $config['inline_before']); + } + + if (!empty($config['inline_after'])) { + wp_add_inline_style($handle, $config['inline_after']); + } + + if (isset($this->inline_styles[$handle])) { + foreach ($this->inline_styles[$handle]['before'] as $style) { + wp_add_inline_style($handle, $style); + } + + foreach ($this->inline_styles[$handle]['after'] as $style) { + wp_add_inline_style($handle, $style); + } + } + } + + return $this; + } + + /** + * Find script configuration by handle. + */ + protected function findScriptConfig(string $handle): ?array + { + // Check individual scripts + if (isset($this->individual_scripts[$handle])) { + return $this->individual_scripts[$handle]; + } + + // Check in groups + foreach ($this->script_groups as $group_data) { + if (isset($group_data['scripts'][$handle])) { + return $group_data['scripts'][$handle]; + } + } + + return null; + } + + /** + * Find style configuration by handle. + */ + protected function findStyleConfig(string $handle): ?array + { + // Check individual styles + if (isset($this->individual_styles[$handle])) { + return $this->individual_styles[$handle]; + } + + // Check in groups + foreach ($this->style_groups as $group_data) { + if (isset($group_data['styles'][$handle])) { + return $group_data['styles'][$handle]; + } + } + + return null; + } + /** * Handle script localization with global data support. */ From e4c83c71edc0e78cb850d63ec2324adb7ca44c00 Mon Sep 17 00:00:00 2001 From: codad5 Date: Tue, 19 Aug 2025 15:21:45 +0100 Subject: [PATCH 3/9] Refactor view helper functions in ViewLoader to support base path and plugin prefix parameters for enhanced flexibility --- src/Utils/ViewLoader.php | 104 ++++++++++++++++++++++++++++----------- 1 file changed, 76 insertions(+), 28 deletions(-) diff --git a/src/Utils/ViewLoader.php b/src/Utils/ViewLoader.php index 83e4219..ccdd1c4 100644 --- a/src/Utils/ViewLoader.php +++ b/src/Utils/ViewLoader.php @@ -112,34 +112,82 @@ public static function load(string $view, array $data = [], bool $echo = true, ? $data = array_merge(self::$global_data, $data); // Add helper functions to data - $data['view'] = new class { - public static function load(string $view, array $data = []): string - { - return ViewLoader::load($view, $data, false) ?: ''; - } - - public static function include(string $view, array $data = []): void - { - ViewLoader::load($view, $data, true); - } - - public static function section(string $name): void - { - ViewLoader::start_section($name); - } - - public static function end_section(): void - { - ViewLoader::end_section(); - } - - public static function yield(string $name, string $default = ''): void - { - echo ViewLoader::get_section($name, $default); - } - }; - - // Render the template + $data['view'] = new class($base_path, $plugin_prefix) { + private ?string $default_base_path; + private string $default_plugin_prefix; + + public function __construct(?string $base_path, string $plugin_prefix) + { + $this->default_base_path = $base_path; + $this->default_plugin_prefix = $plugin_prefix; + } + + public function load(string $view, array $data = [], ?string $base_path = null, bool $overridable = false, ?string $plugin_prefix = null): string + { + return ViewLoader::load( + $view, + $data, + false, + $base_path ?? $this->default_base_path, + $overridable, + $plugin_prefix ?? $this->default_plugin_prefix + ) ?: ''; + } + + public function include(string $view, array $data = [], ?string $base_path = null, bool $overridable = false, ?string $plugin_prefix = null): void + { + ViewLoader::load( + $view, + $data, + true, + $base_path ?? $this->default_base_path, + $overridable, + $plugin_prefix ?? $this->default_plugin_prefix + ); + } + + public function load_overridable(string $view, array $data = []): string + { + return ViewLoader::load( + $view, + $data, + false, + $this->default_base_path, + true, + $this->default_plugin_prefix + ) ?: ''; + } + + public function include_overridable(string $view, array $data = []): void + { + ViewLoader::load( + $view, + $data, + true, + $this->default_base_path, + true, + $this->default_plugin_prefix + ); + } + + public function section(string $name): void + { + ViewLoader::start_section($name); + } + + public function end_section(): void + { + ViewLoader::end_section(); + } + + public function yield(string $name, string $default = ''): void + { + echo ViewLoader::get_section($name, $default); + } + }; + + + // Render the template $output = self::render_template($template_path, $data); if ($output === false) { From 01891d969fcc54268f8d63f95c9c460fdfad1fc1 Mon Sep 17 00:00:00 2001 From: codad5 Date: Tue, 19 Aug 2025 18:48:15 +0100 Subject: [PATCH 4/9] Refactor ViewLoader to utilize ViewHelper class for improved template functionality and maintainability --- src/Utils/ViewHelper.php | 92 ++++++++++++++++++++++++++++++++++++++++ src/Utils/ViewLoader.php | 87 ++++++------------------------------- 2 files changed, 106 insertions(+), 73 deletions(-) create mode 100644 src/Utils/ViewHelper.php diff --git a/src/Utils/ViewHelper.php b/src/Utils/ViewHelper.php new file mode 100644 index 0000000..0f59389 --- /dev/null +++ b/src/Utils/ViewHelper.php @@ -0,0 +1,92 @@ +default_base_path = $base_path; + $this->default_plugin_prefix = $plugin_prefix; + } + + public function load(string $view, array $data = [], ?string $base_path = null, bool $overridable = false, ?string $plugin_prefix = null): string + { + return ViewLoader::load( + $view, + $data, + false, + $base_path ?? $this->default_base_path, + $overridable, + $plugin_prefix ?? $this->default_plugin_prefix + ) ?: ''; + } + + public function include(string $view, array $data = [], ?string $base_path = null, bool $overridable = false, ?string $plugin_prefix = null): void + { + ViewLoader::load( + $view, + $data, + true, + $base_path ?? $this->default_base_path, + $overridable, + $plugin_prefix ?? $this->default_plugin_prefix + ); + } + + public function load_overridable(string $view, array $data = []): string + { + return ViewLoader::load( + $view, + $data, + false, + $this->default_base_path, + true, + $this->default_plugin_prefix + ) ?: ''; + } + + public function include_overridable(string $view, array $data = []): void + { + ViewLoader::load( + $view, + $data, + true, + $this->default_base_path, + true, + $this->default_plugin_prefix + ); + } + + /** + * @throws \Exception + */ + public function section(string $name): void + { + ViewLoader::start_section($name); + } + + /** + * @throws \Exception + */ + public function end_section(): void + { + ViewLoader::end_section(); + } + + public function yield(string $name, string $default = ''): void + { + echo ViewLoader::get_section($name, $default); + } +} \ No newline at end of file diff --git a/src/Utils/ViewLoader.php b/src/Utils/ViewLoader.php index ccdd1c4..26d97b6 100644 --- a/src/Utils/ViewLoader.php +++ b/src/Utils/ViewLoader.php @@ -112,79 +112,7 @@ public static function load(string $view, array $data = [], bool $echo = true, ? $data = array_merge(self::$global_data, $data); // Add helper functions to data - $data['view'] = new class($base_path, $plugin_prefix) { - private ?string $default_base_path; - private string $default_plugin_prefix; - - public function __construct(?string $base_path, string $plugin_prefix) - { - $this->default_base_path = $base_path; - $this->default_plugin_prefix = $plugin_prefix; - } - - public function load(string $view, array $data = [], ?string $base_path = null, bool $overridable = false, ?string $plugin_prefix = null): string - { - return ViewLoader::load( - $view, - $data, - false, - $base_path ?? $this->default_base_path, - $overridable, - $plugin_prefix ?? $this->default_plugin_prefix - ) ?: ''; - } - - public function include(string $view, array $data = [], ?string $base_path = null, bool $overridable = false, ?string $plugin_prefix = null): void - { - ViewLoader::load( - $view, - $data, - true, - $base_path ?? $this->default_base_path, - $overridable, - $plugin_prefix ?? $this->default_plugin_prefix - ); - } - - public function load_overridable(string $view, array $data = []): string - { - return ViewLoader::load( - $view, - $data, - false, - $this->default_base_path, - true, - $this->default_plugin_prefix - ) ?: ''; - } - - public function include_overridable(string $view, array $data = []): void - { - ViewLoader::load( - $view, - $data, - true, - $this->default_base_path, - true, - $this->default_plugin_prefix - ); - } - - public function section(string $name): void - { - ViewLoader::start_section($name); - } - - public function end_section(): void - { - ViewLoader::end_section(); - } - - public function yield(string $name, string $default = ''): void - { - echo ViewLoader::get_section($name, $default); - } - }; + $data['view'] = self::create_view_helper($base_path, $plugin_prefix); // Render the template @@ -206,6 +134,19 @@ public function yield(string $name, string $default = ''): void return $output; } + + /** + * Create a view helper instance. + * + * @param string|null $base_path Default base path + * @param string $plugin_prefix Default plugin prefix + * @return ViewHelper View helper instance + */ + private static function create_view_helper(?string $base_path, string $plugin_prefix): ViewHelper + { + return new ViewHelper($base_path, $plugin_prefix); + } + /** * Load a view and return output without echoing. * From a65ea7571ad7e0c1955be28c0028314ab27774c7 Mon Sep 17 00:00:00 2001 From: codad5 Date: Wed, 20 Aug 2025 01:14:47 +0100 Subject: [PATCH 5/9] Enhance ViewHelper and ViewLoader to support overridable views for improved flexibility in template management --- src/Utils/ViewHelper.php | 10 +++++++--- src/Utils/ViewLoader.php | 7 ++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Utils/ViewHelper.php b/src/Utils/ViewHelper.php index 0f59389..e0851f8 100644 --- a/src/Utils/ViewHelper.php +++ b/src/Utils/ViewHelper.php @@ -14,15 +14,18 @@ class ViewHelper { private ?string $default_base_path; private string $default_plugin_prefix; + private bool $overridable = false; - public function __construct(?string $base_path, string $plugin_prefix) + public function __construct(?string $base_path, string $plugin_prefix, bool $overridable = false) { $this->default_base_path = $base_path; $this->default_plugin_prefix = $plugin_prefix; + $this->overridable = $overridable; } - public function load(string $view, array $data = [], ?string $base_path = null, bool $overridable = false, ?string $plugin_prefix = null): string + public function load(string $view, array $data = [], ?string $base_path = null, bool $overridable = null, ?string $plugin_prefix = null): string { + $overridable = $overridable === null ? $this->overridable : $overridable; return ViewLoader::load( $view, $data, @@ -33,8 +36,9 @@ public function load(string $view, array $data = [], ?string $base_path = null, ) ?: ''; } - public function include(string $view, array $data = [], ?string $base_path = null, bool $overridable = false, ?string $plugin_prefix = null): void + public function include(string $view, array $data = [], ?string $base_path = null, bool $overridable = null, ?string $plugin_prefix = null): void { + $overridable = $overridable === null ? $this->overridable : $overridable; ViewLoader::load( $view, $data, diff --git a/src/Utils/ViewLoader.php b/src/Utils/ViewLoader.php index 26d97b6..7ab1446 100644 --- a/src/Utils/ViewLoader.php +++ b/src/Utils/ViewLoader.php @@ -112,7 +112,7 @@ public static function load(string $view, array $data = [], bool $echo = true, ? $data = array_merge(self::$global_data, $data); // Add helper functions to data - $data['view'] = self::create_view_helper($base_path, $plugin_prefix); + $data['view'] = self::create_view_helper($base_path, $plugin_prefix, $overridable); // Render the template @@ -140,11 +140,12 @@ public static function load(string $view, array $data = [], bool $echo = true, ? * * @param string|null $base_path Default base path * @param string $plugin_prefix Default plugin prefix + * @param bool $overridable Whether the view is overridable by theme * @return ViewHelper View helper instance */ - private static function create_view_helper(?string $base_path, string $plugin_prefix): ViewHelper + private static function create_view_helper(?string $base_path, string $plugin_prefix, bool $overridable = false): ViewHelper { - return new ViewHelper($base_path, $plugin_prefix); + return new ViewHelper($base_path, $plugin_prefix, $overridable); } /** From fd9da42e9b154b8581dd5b4145f8999606e6adf4 Mon Sep 17 00:00:00 2001 From: codad5 Date: Wed, 20 Aug 2025 19:20:46 +0100 Subject: [PATCH 6/9] Refactor APIHelper to enhance request handling and caching, including support for request body and content type resolution --- src/Utils/APIHelper.php | 207 +++++++++++++++++++++++++--------------- 1 file changed, 132 insertions(+), 75 deletions(-) diff --git a/src/Utils/APIHelper.php b/src/Utils/APIHelper.php index 5f6adbe..3aa1429 100644 --- a/src/Utils/APIHelper.php +++ b/src/Utils/APIHelper.php @@ -36,18 +36,19 @@ abstract class APIHelper */ protected static int $default_cache_duration = 90; - /** - * API endpoints configuration - should be overridden in child classes. - * - * @var array, - * headers?: array, - * cache?: bool|int|callable - * }> - */ - abstract protected static function get_endpoints(): array; + /** + * API endpoints configuration - should be overridden in child classes. + * + * @return array, + * body?: array, + * headers?: array, + * cache?: bool|int|callable + * }> + */ + abstract protected static function get_endpoints(): array;; /** * Get the plugin slug for cache and option key generation. @@ -190,73 +191,129 @@ public static function remember(string $key, callable $callback, ?int $expiratio * @param string $method HTTP method * @param string $url Request URL * @param array $params Request parameters + * @param array $body Request body data * @param array $args Additional request arguments * @return mixed Response data * @throws \Exception When request fails or response is invalid */ - public static function request(string $method, string $url, array $params = [], array $args = []): mixed - { - $method = strtoupper(sanitize_text_field($method)); - $url = esc_url_raw($url); - - $request_args = array_merge([ - 'method' => $method, - 'timeout' => 30, - ], $args, [ - 'headers' => array_merge( - static::get_headers(), - $args['headers'] ?? [] - ), - ]); - - // Add body for non-GET requests - if ($method !== 'GET' && !empty($params)) { - $request_args['body'] = $params; - } - - $response = wp_remote_request($url, $request_args); - - if (is_wp_error($response)) { - throw new \Exception( - sprintf( - 'Request failed: %s (Code: %s)', - esc_html($response->get_error_message()), - esc_html($response->get_error_code()) - ) - ); - } - - $status_code = wp_remote_retrieve_response_code($response); - if ($status_code >= 400) { - throw new \Exception( - sprintf('HTTP Error: %d', $status_code) - ); - } - - $body = wp_remote_retrieve_body($response); - $data = json_decode($body, true); - - if (json_last_error() !== JSON_ERROR_NONE) { - throw new \Exception( - sprintf( - 'JSON decode error: %s', - json_last_error_msg() - ) - ); - } - - // Handle caching - $cache_setting = $args['cache'] ?? true; - $should_cache = static::should_cache_response($cache_setting, $data); - - if ($should_cache && $method === 'GET') { - $cache_duration = is_numeric($cache_setting) ? (int)$cache_setting : static::$default_cache_duration; - static::cache($url, $data, $cache_duration); - } - - static::update_api_call_count(); - return $data; - } + public static function request(string $method, string $url, array $params = [], array $body = [], array $args = []): mixed + { + $method = strtoupper(sanitize_text_field($method)); + $url = esc_url_raw($url); + + $request_args = array_merge([ + 'method' => $method, + 'timeout' => 30, + ], $args, [ + 'headers' => array_merge( + static::get_headers(), + $args['headers'] ?? [] + ), + ]); + + // Add query parameters to URL for all methods + if (!empty($params)) { + $url = static::prepare_request_url($url, $params); + } + + // Add body for non-GET requests + if ($method !== 'GET' && !empty($body)) { + $content_type = static::resolve_content_type($request_args['headers']); + $request_args['body'] = static::prepare_request_body($body, $content_type); + } + + $response = wp_remote_request($url, $request_args); + + if (is_wp_error($response)) { + throw new \Exception( + sprintf( + 'Request failed: %s (Code: %s)', + esc_html($response->get_error_message()), + esc_html($response->get_error_code()) + ) + ); + } + + $status_code = wp_remote_retrieve_response_code($response); + if ($status_code >= 400) { + throw new \Exception( + sprintf('HTTP Error: %d', $status_code) + ); + } + + $body_response = wp_remote_retrieve_body($response); + $data = json_decode($body_response, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \Exception( + sprintf( + 'JSON decode error: %s', + json_last_error_msg() + ) + ); + } + + // Handle caching + $cache_setting = $args['cache'] ?? true; + $should_cache = static::should_cache_response($cache_setting, $data); + + if ($should_cache && $method === 'GET') { + $cache_duration = is_numeric($cache_setting) ? (int)$cache_setting : static::$default_cache_duration; + static::cache($url, $data, $cache_duration); + } + + static::update_api_call_count(); + return $data; + } + + /** + * Prepare request body based on content type and data. + * + * @param array $body Request body data + * @param string $content_type Content type header value + * @param bool $force_json_encode Force JSON encoding regardless of content type + * @return string|array Prepared request body + */ + protected static function prepare_request_body(array $body, string $content_type = 'application/json', bool $force_json_encode = true): string|array + { + if (empty($body)) { + return []; + } + + // Force JSON encoding by default or when content type is JSON + if ($force_json_encode || str_contains($content_type, 'application/json')) { + return json_encode($body); + } + + // Handle form data + if (str_contains($content_type, 'application/x-www-form-urlencoded')) { + return http_build_query($body); + } + + // Handle multipart form data or other types - return as array + return $body; + } + + /** + * Get default content type for requests. + * + * @return string Default content type + */ + protected static function get_default_content_type(): string + { + return 'application/json'; + } + + /** + * Determine content type from request headers or use default. + * + * @param array $headers Request headers + * @return string Content type + */ + protected static function resolve_content_type(array $headers): string + { + return $headers['Content-Type'] ?? static::get_default_content_type(); + } /** * Determine if response should be cached. From af5120cd0fb63b6647e43ece7912b4dddd403a85 Mon Sep 17 00:00:00 2001 From: codad5 Date: Wed, 20 Aug 2025 19:25:58 +0100 Subject: [PATCH 7/9] Enhance APIHelper's make_request method to support request body data, allowing for more flexible API interactions and improved handling of endpoint-specific body parameters. --- src/Utils/APIHelper.php | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Utils/APIHelper.php b/src/Utils/APIHelper.php index 3aa1429..763dd47 100644 --- a/src/Utils/APIHelper.php +++ b/src/Utils/APIHelper.php @@ -419,11 +419,12 @@ public static function set_cache_duration(int $duration): void * * @param string $endpoint_name Endpoint identifier * @param array $params Request parameters + * @param array $body Request body data * @param array $substitutions URL placeholder substitutions * @return mixed Response data * @throws \Exception When endpoint is invalid or request fails */ - public static function make_request(string $endpoint_name, array $params = [], array $substitutions = []): mixed + public static function make_request(string $endpoint_name, array $params = [], array $body = [], array $substitutions = []): mixed { $endpoint = static::get_endpoints()[$endpoint_name] ?? null; if (!$endpoint) { @@ -458,6 +459,22 @@ public static function make_request(string $endpoint_name, array $params = [], a } } + + $endpoint_body = $endpoint['body'] ?? []; + $body = array_merge($endpoint_body, $body); + + // Filter body to only include those defined in endpoint + if (!empty($endpoint_body)) { + $body = array_intersect_key($body, $endpoint_body); + } + + // Execute callable body values + foreach ($body as $key => $value) { + if (is_callable($value)) { + $body[$key] = $value(); + } + } + $method = $endpoint['method'] ?? 'GET'; $url = rtrim(static::get_base_url(), '/') . '/' . ltrim($route, '/'); @@ -474,7 +491,7 @@ public static function make_request(string $endpoint_name, array $params = [], a $params = []; // Parameters already in URL } - return static::request($method, $url, $params, $endpoint); + return static::request($method, $url, $params, $body, $endpoint); } /** From 5fb77808d1372847ad32a2df6e14fe43935f4569 Mon Sep 17 00:00:00 2001 From: codad5 Date: Wed, 20 Aug 2025 19:37:50 +0100 Subject: [PATCH 8/9] Refactor APIHelper to improve code readability and maintainability by standardizing method documentation and ensuring consistent formatting across the class. --- src/Utils/APIHelper.php | 282 ++++++++++++++++++++-------------------- 1 file changed, 141 insertions(+), 141 deletions(-) diff --git a/src/Utils/APIHelper.php b/src/Utils/APIHelper.php index 763dd47..74ad4e2 100644 --- a/src/Utils/APIHelper.php +++ b/src/Utils/APIHelper.php @@ -36,19 +36,19 @@ abstract class APIHelper */ protected static int $default_cache_duration = 90; - /** - * API endpoints configuration - should be overridden in child classes. - * - * @return array, - * body?: array, - * headers?: array, - * cache?: bool|int|callable - * }> - */ - abstract protected static function get_endpoints(): array;; + /** + * API endpoints configuration - should be overridden in child classes. + * + * @return array, + * body?: array, + * headers?: array, + * cache?: bool|int|callable + * }> + */ + abstract protected static function get_endpoints(): array; /** * Get the plugin slug for cache and option key generation. @@ -196,124 +196,124 @@ public static function remember(string $key, callable $callback, ?int $expiratio * @return mixed Response data * @throws \Exception When request fails or response is invalid */ - public static function request(string $method, string $url, array $params = [], array $body = [], array $args = []): mixed - { - $method = strtoupper(sanitize_text_field($method)); - $url = esc_url_raw($url); - - $request_args = array_merge([ - 'method' => $method, - 'timeout' => 30, - ], $args, [ - 'headers' => array_merge( - static::get_headers(), - $args['headers'] ?? [] - ), - ]); - - // Add query parameters to URL for all methods - if (!empty($params)) { - $url = static::prepare_request_url($url, $params); - } - - // Add body for non-GET requests - if ($method !== 'GET' && !empty($body)) { - $content_type = static::resolve_content_type($request_args['headers']); - $request_args['body'] = static::prepare_request_body($body, $content_type); - } - - $response = wp_remote_request($url, $request_args); - - if (is_wp_error($response)) { - throw new \Exception( - sprintf( - 'Request failed: %s (Code: %s)', - esc_html($response->get_error_message()), - esc_html($response->get_error_code()) - ) - ); - } - - $status_code = wp_remote_retrieve_response_code($response); - if ($status_code >= 400) { - throw new \Exception( - sprintf('HTTP Error: %d', $status_code) - ); - } - - $body_response = wp_remote_retrieve_body($response); - $data = json_decode($body_response, true); - - if (json_last_error() !== JSON_ERROR_NONE) { - throw new \Exception( - sprintf( - 'JSON decode error: %s', - json_last_error_msg() - ) - ); - } - - // Handle caching - $cache_setting = $args['cache'] ?? true; - $should_cache = static::should_cache_response($cache_setting, $data); - - if ($should_cache && $method === 'GET') { - $cache_duration = is_numeric($cache_setting) ? (int)$cache_setting : static::$default_cache_duration; - static::cache($url, $data, $cache_duration); - } - - static::update_api_call_count(); - return $data; - } - - /** - * Prepare request body based on content type and data. - * - * @param array $body Request body data - * @param string $content_type Content type header value - * @param bool $force_json_encode Force JSON encoding regardless of content type - * @return string|array Prepared request body - */ - protected static function prepare_request_body(array $body, string $content_type = 'application/json', bool $force_json_encode = true): string|array - { - if (empty($body)) { - return []; - } - - // Force JSON encoding by default or when content type is JSON - if ($force_json_encode || str_contains($content_type, 'application/json')) { - return json_encode($body); - } - - // Handle form data - if (str_contains($content_type, 'application/x-www-form-urlencoded')) { - return http_build_query($body); - } - - // Handle multipart form data or other types - return as array - return $body; - } - - /** - * Get default content type for requests. - * - * @return string Default content type - */ - protected static function get_default_content_type(): string - { - return 'application/json'; - } - - /** - * Determine content type from request headers or use default. - * - * @param array $headers Request headers - * @return string Content type - */ - protected static function resolve_content_type(array $headers): string - { - return $headers['Content-Type'] ?? static::get_default_content_type(); - } + public static function request(string $method, string $url, array $params = [], array $body = [], array $args = []): mixed + { + $method = strtoupper(sanitize_text_field($method)); + $url = esc_url_raw($url); + + $request_args = array_merge([ + 'method' => $method, + 'timeout' => 30, + ], $args, [ + 'headers' => array_merge( + static::get_headers(), + $args['headers'] ?? [] + ), + ]); + + // Add query parameters to URL for all methods + if (!empty($params)) { + $url = static::prepare_request_url($url, $params); + } + + // Add body for non-GET requests + if ($method !== 'GET' && !empty($body)) { + $content_type = static::resolve_content_type($request_args['headers']); + $request_args['body'] = static::prepare_request_body($body, $content_type); + } + + $response = wp_remote_request($url, $request_args); + + if (is_wp_error($response)) { + throw new \Exception( + sprintf( + 'Request failed: %s (Code: %s)', + esc_html($response->get_error_message()), + esc_html($response->get_error_code()) + ) + ); + } + + $status_code = wp_remote_retrieve_response_code($response); + if ($status_code >= 400) { + throw new \Exception( + sprintf('HTTP Error: %d', $status_code) + ); + } + + $body_response = wp_remote_retrieve_body($response); + $data = json_decode($body_response, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \Exception( + sprintf( + 'JSON decode error: %s', + json_last_error_msg() + ) + ); + } + + // Handle caching + $cache_setting = $args['cache'] ?? true; + $should_cache = static::should_cache_response($cache_setting, $data); + + if ($should_cache && $method === 'GET') { + $cache_duration = is_numeric($cache_setting) ? (int)$cache_setting : static::$default_cache_duration; + static::cache($url, $data, $cache_duration); + } + + static::update_api_call_count(); + return $data; + } + + /** + * Prepare request body based on content type and data. + * + * @param array $body Request body data + * @param string $content_type Content type header value + * @param bool $force_json_encode Force JSON encoding regardless of content type + * @return string|array Prepared request body + */ + protected static function prepare_request_body(array $body, string $content_type = 'application/json', bool $force_json_encode = true): string|array + { + if (empty($body)) { + return []; + } + + // Force JSON encoding by default or when content type is JSON + if ($force_json_encode || str_contains($content_type, 'application/json')) { + return json_encode($body); + } + + // Handle form data + if (str_contains($content_type, 'application/x-www-form-urlencoded')) { + return http_build_query($body); + } + + // Handle multipart form data or other types - return as array + return $body; + } + + /** + * Get default content type for requests. + * + * @return string Default content type + */ + protected static function get_default_content_type(): string + { + return 'application/json'; + } + + /** + * Determine content type from request headers or use default. + * + * @param array $headers Request headers + * @return string Content type + */ + protected static function resolve_content_type(array $headers): string + { + return $headers['Content-Type'] ?? static::get_default_content_type(); + } /** * Determine if response should be cached. @@ -460,20 +460,20 @@ public static function make_request(string $endpoint_name, array $params = [], a } - $endpoint_body = $endpoint['body'] ?? []; - $body = array_merge($endpoint_body, $body); + $endpoint_body = $endpoint['body'] ?? []; + $body = array_merge($endpoint_body, $body); - // Filter body to only include those defined in endpoint + // Filter body to only include those defined in endpoint if (!empty($endpoint_body)) { - $body = array_intersect_key($body, $endpoint_body); - } + $body = array_intersect_key($body, $endpoint_body); + } - // Execute callable body values + // Execute callable body values foreach ($body as $key => $value) { - if (is_callable($value)) { - $body[$key] = $value(); - } - } + if (is_callable($value)) { + $body[$key] = $value(); + } + } $method = $endpoint['method'] ?? 'GET'; $url = rtrim(static::get_base_url(), '/') . '/' . ltrim($route, '/'); From 11db13be4edc7ec479eb5066ea5796d2ccbc9e0a Mon Sep 17 00:00:00 2001 From: codad5 Date: Sat, 23 Aug 2025 10:47:29 +0100 Subject: [PATCH 9/9] fix to support PHP 8.0 --- src/Utils/ViewLoader.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Utils/ViewLoader.php b/src/Utils/ViewLoader.php index 7ab1446..0fc4710 100644 --- a/src/Utils/ViewLoader.php +++ b/src/Utils/ViewLoader.php @@ -618,7 +618,7 @@ private static function generate_cache_key(string $template_path, array $data): * @param bool $echo Whether to echo error * @return false */ - private static function handle_template_not_found(string $view, bool $echo): false + private static function handle_template_not_found(string $view, bool $echo): bool { $error_message = "Template not found: {$view}"; @@ -639,7 +639,7 @@ private static function handle_template_not_found(string $view, bool $echo): fal * @param bool $echo Whether to echo error * @return false */ - private static function handle_render_error(string $view, bool $echo): false + private static function handle_render_error(string $view, bool $echo): bool { $error_message = "Error rendering template: {$view}";