Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new calendar endpoints | part 2/4 | endpoint base #928

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
76bdf3f
Autoload endpoint files
carstingaxion Sep 27, 2024
24ee3f8
NEW Endpoint Class for Custom Rewrite Rules and Query Handling
carstingaxion Sep 27, 2024
27f9efb
New Abstract class for defining custom endpoint types
carstingaxion Sep 27, 2024
caa9eff
Fix for CS
carstingaxion Sep 27, 2024
a32cdb7
NEW Class responsible for handling redirect-based endpoints
carstingaxion Sep 27, 2024
f5e6bd3
NEW Class responsible for handling template-based endpoints
carstingaxion Sep 27, 2024
f57774b
The BEGINNING of a unit test
carstingaxion Sep 27, 2024
7688800
Fix for CS
carstingaxion Sep 27, 2024
045f493
Simplify logic wether to load a feed template or anything other for t…
carstingaxion Sep 28, 2024
a2c277b
Fix typo
carstingaxion Sep 28, 2024
313fafd
WIP unit tests
carstingaxion Sep 29, 2024
5be1c95
Move methods into more related class
carstingaxion Sep 30, 2024
b7eafc0
failing test, that I can't figure out
carstingaxion Sep 30, 2024
41f6b3e
NEW tests
carstingaxion Sep 30, 2024
80a1c51
Maybe its pointless to hook this onto the next round?
carstingaxion Sep 30, 2024
35a8512
NEW test for constructor
carstingaxion Sep 30, 2024
0b2d460
NEW test for method get_rewrite_atts()
carstingaxion Sep 30, 2024
d0c9618
NEW test for allow_query_vars() method
carstingaxion Sep 30, 2024
4d29734
Make method avail. to public
carstingaxion Oct 1, 2024
18c0b4d
NEW test for has_feed_template() method
carstingaxion Oct 1, 2024
75b408c
NEW test for is_valid_query() method
carstingaxion Oct 1, 2024
6576b3b
Order test methods like class under test
carstingaxion Oct 1, 2024
f2a8528
Fix for CS
carstingaxion Oct 1, 2024
4994b40
Rename class under test for consistency
carstingaxion Oct 1, 2024
4113308
100% tests for Endpoint_Redirect class
carstingaxion Oct 1, 2024
797af62
Fix for CS
carstingaxion Oct 1, 2024
596070c
DRY out init steps into own get_regex_pattern() method
carstingaxion Oct 1, 2024
b44c554
NEW test for maybe_flush_rewrite_rules() method
carstingaxion Oct 1, 2024
b4d9617
NEW test for get_regex_pattern() method
carstingaxion Oct 1, 2024
8814165
Move activate() method into Endpoint_Types
carstingaxion Oct 1, 2024
d73cb79
Fix for CS
carstingaxion Oct 1, 2024
4e96f17
Fix: Parameter #1 $template of method GatherPress\Core\Endpoints\Endp…
carstingaxion Oct 4, 2024
73a791b
Remove missleading prio from error message.
carstingaxion Oct 4, 2024
dc4384f
Fix: Call to function is_string() with string will always evaluate to…
carstingaxion Oct 4, 2024
7480c2a
Fix: Call to protected method has_feed() of class GatherPress\Core\En…
carstingaxion Oct 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions includes/core/classes/class-autoloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ static function ( string $class_string = '' ): void {

switch ( $class_type ) {
case 'commands':
case 'endpoints':
case 'settings':
case 'traits':
array_pop( $structure );
Expand Down
94 changes: 94 additions & 0 deletions includes/core/classes/endpoints/class-endpoint-redirect.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php
/**
* Class responsible for handling redirect-based endpoints in GatherPress.
*
* This file defines the `Endpoint_Redirect` class, which extends the base `Endpoint_Type`
* class and manages URL redirection for specific endpoints. It safely redirects users
* to external URLs based on the logic provided by the callback function, while ensuring
* the redirection is secure and follows WordPress's allowed hosts policy.
*
* @package GatherPress\Core\Endpoints
* @since 1.0.0
*/

namespace GatherPress\Core\Endpoints;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore

use GatherPress\Core\Endpoints\Endpoint;

/**
* Handles safe URL redirection for custom endpoints in GatherPress.
*
* The `Endpoint_Redirect` class extends the `Endpoint_Type` class and is responsible
* for managing URL redirection for specific endpoints. It handles:
* - Getting the redirect URL through a callback.
* - Safely redirecting users using `wp_safe_redirect()`.
* - Filtering allowed redirect hosts to ensure security.
*
* @since 1.0.0
*/
class Endpoint_Redirect extends Endpoint_Type {

/**
* The target URL for the redirection.
*
* This property stores the URL that the user will be redirected to when the endpoint
* is triggered. The URL is generated by calling the callback function provided during
* the endpoint's construction.
*
* @since 1.0.0
*
* @var string
*/
protected $url;

/**
* Activate Endpoint_Type by hooking into relevant parts.
*
* Safely redirects the user to the specified URL.
*
* This method gets the target URL by calling the callback function and then
* safely redirects the user to that URL using `wp_safe_redirect()`.
*
* @since 1.0.0
*
* @param Endpoint|null $endpoint Class for custom rewrite endpoints and their query handling in GatherPress.
* @return void
*/
public function activate( ?Endpoint $endpoint = null ): void {
$this->url = ( $this->callback )();
if ( $this->url ) {
// Add the target host to the list of allowed redirect hosts.
add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
// Perform a safe redirection to the target URL. Defaults to a 302 status code.
wp_safe_redirect( $this->url );
exit; // Always exit after redirecting.
}
}

/**
* Filters the list of allowed hosts to include the redirect target.
*
* This method ensures that the host of the target URL is added to the list of allowed
* redirect hosts, allowing the redirection to proceed safely. It is hooked into the
* `allowed_redirect_hosts` filter, which controls the domains that `wp_safe_redirect()`
* is allowed to redirect to.
*
* @see https://developer.wordpress.org/reference/hooks/allowed_redirect_hosts/
*
* @since 1.0.0
*
* @param string[] $hosts An array of allowed host names.
* @return string[] The updated array of allowed host names, including the redirect target.
*/
public function allowed_redirect_hosts( array $hosts ): array {
return array_merge(
$hosts,
array(
wp_parse_url( $this->url, PHP_URL_HOST ),
)
);
}
}
210 changes: 210 additions & 0 deletions includes/core/classes/endpoints/class-endpoint-template.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
<?php
/**
* Class responsible for handling template-based endpoints in GatherPress.
*
* This file defines the `Endpoint_Template` class, which extends the base `Endpoint_Type` class
* and manages custom templates for endpoints. It allows themes to override the default plugin
* templates by checking the theme's template directory before falling back to the plugin's template.
*
* @package GatherPress\Core\Endpoints
* @since 1.0.0
*/

namespace GatherPress\Core\Endpoints;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore

use GatherPress\Core\Endpoints\Endpoint;
use GatherPress\Core\Utility;

/**
* Handles template rendering for custom endpoints in GatherPress.
*
* The `Endpoint_Template` class extends the `Endpoint_Type` class and is responsible
* for loading custom templates for specific endpoints. It allows for theme-based
* overrides, giving themes the ability to provide their own template files for the
* endpoints instead of using the default plugin templates.
*
* - It checks if a template exists in the current theme or child theme.
* - If not found, it falls back to the template provided by the plugin.
*
* @since 1.0.0
*/
class Endpoint_Template extends Endpoint_Type {

/**
* Directory path for plugin templates.
*
* @var string
*/
protected $plugin_template_dir;

/**
* Class constructor.
*
* Initializes the `Endpoint_Template` object by setting the slug, callback, and
* plugin template directory. The parent constructor initializes the slug and callback,
* while this constructor adds the plugin template default.
*
* @since 1.0.0
*
* @param string $slug The slug used to identify the endpoint in the URL.
* @param callable $callback The callback function to retrieve file name and path of the endpoint template.
* @param string $plugin_template_dir The directory path for the plugin templates.
*/
public function __construct( string $slug, callable $callback, string $plugin_template_dir = '' ) {
parent::__construct( $slug, $callback );
$this->plugin_template_dir = ( ! empty( $plugin_template_dir ) ) ? $plugin_template_dir : sprintf(
'%s/includes/templates/endpoints',
GATHERPRESS_CORE_PATH
);
}


/**
* Activate Endpoint_Type by hooking into relevant parts.
*
* @since 1.0.0
*
* @param Endpoint|null $endpoint Class for custom rewrite endpoints and their query handling in GatherPress.
* @return void
*/
public function activate( ?Endpoint $endpoint = null ): void {

// A call to any /feed/ endpoint is handled different by WordPress
// and as such the 'Endpoint_Template's template_include hook would fail.
$feed_slug = ( null !== $endpoint ) ? $endpoint->has_feed() : false;
if ( $feed_slug ) {
// Hook into WordPress' feed handling to load the custom feed template.
add_action( sprintf( 'do_feed_%s', $feed_slug ), array( $this, 'load_feed_template' ) );
} else {
// Filters the path of the current template before including it.
add_filter( 'template_include', array( $this, 'template_include' ) );
}
}

/**
* Load the theme-overridable feed template from the plugin.
*
* This method ensures that a feed template is loaded when a request is made to
* a custom feed endpoint. If the theme provides an override for the feed template,
* it will be used; otherwise, the default template from the plugin is loaded. The
* method ensures that WordPress does not return a 404 for custom feed URLs.
*
* A call to any post types /feed/anything endpoint is handled by WordPress
* prior 'Endpoint_Template's template_include hook would run.
* Therefore WordPress will throw an xml'ed 404 error,
* if nothing is hooked onto the 'do_feed_anything' action.
*
* That's the reason for this method, it delivers what WordPress wants
* and re-uses the parameters provided by the class.
*
* We expect that a endpoint, that contains the /feed/ string, only has one 'Redirect_Template' attached.
* This might be wrong or short sightened, please open an issue in that case: https://github.com/GatherPress/gatherpress/issues
*
* Until then, we *just* use the first of the provided endpoint-types,
* to hook into WordPress, which should be the valid template endpoint.
*
* @since 1.0.0
*
* @return void
*/
public function load_feed_template() {
load_template( $this->template_include() );
}

/**
* Filters the path of the current template before including it.
*
* This method checks if the theme or child theme provides a custom template for the
* current endpoint. If a theme template exists, it will use that; otherwise, it will
* fall back to the default template provided by the plugin. The template information
* is provided by the callback set during the construction of the endpoint.
*
* @since 1.0.0
*
* @param string $template The path of the default template to include,
* defaults to '' so that the template loader keeps looking for templates.
* @return string The path of the template to include, either from the theme or plugin.
*/
public function template_include( string $template = '' ): string {
$presets = $this->get_template_presets();

$file_name = $presets['file_name'];
$dir_path = $presets['dir_path'] ?? $this->plugin_template_dir;

// Check if the theme provides a custom template.
$theme_template = $this->get_template_from_theme( $file_name );
if ( $theme_template ) {
return $theme_template;
}

// Check if the plugin has a template file.
$plugin_template = $this->get_template_from_plugin( $file_name, $dir_path, );
if ( $plugin_template ) {
return $plugin_template;
}

// Fallback to the default template.
return $template;
}


/**
* Retrieve template presets by invoking the callback.
*
* @return array Template preset data including file_name and optional dir_path.
*/
protected function get_template_presets(): array {
return ( $this->callback )();
}

/**
* Locate a template in the theme or child theme.
*
* @todo Maybe better put in the Utility class?
*
* @param string $file_name The name of the template file.
* @return string The path to the theme template or an empty string if not found.
*/
protected function get_template_from_theme( string $file_name ): string {

// locate_template() doesn't cares,
// but locate_block_template() needs this to be an array.
$templates = array( $file_name );

// First, search for PHP templates, which block themes can also use.
$template = locate_template( $templates );

// Pass the result into the block template locator and let it figure
// out whether block templates are supported and this template exists.
$template = locate_block_template(
$template,
pathinfo( $file_name, PATHINFO_FILENAME ), // Name of the file without extension.
$templates
);

return $template;
}

/**
* Build the full path to the plugin's template file.
*
* @todo Maybe better put in the Utility class?
*
* @param string $file_name The name of the template file.
* @param string $dir_path The directory path where the template is stored.
* @return string The full path to the template file or an empty string if file not exists.
*/
protected function get_template_from_plugin( string $file_name, string $dir_path ): string {
// Remove prefix to keep file-names simple,
// for templates of core GatherPress.
if ( $this->plugin_template_dir === $dir_path ) {
$file_name = Utility::unprefix_key( $file_name );
}

$template = trailingslashit( $dir_path ) . $file_name;
return file_exists( $template ) ? $template : '';
}
}
Loading
Loading