| 1 |
<?php |
| 2 |
|
| 3 |
namespace WPDeveloper\BetterDocs\Editors\BlockEditor; |
| 4 |
use WPDeveloper\BetterDocs\Utils\Base; |
| 5 |
use WPDeveloper\BetterDocs\Utils\BlockTemplate; |
| 6 |
|
| 7 |
/** |
| 8 |
* BlockTypesController class. |
| 9 |
* |
| 10 |
* @internal |
| 11 |
*/ |
| 12 |
class TemplatesController extends Base { |
| 13 |
protected $blockTemplate; |
| 14 |
|
| 15 |
/** |
| 16 |
* Constructor. |
| 17 |
*/ |
| 18 |
public function __construct( BlockTemplate $blockTemplate ) { |
| 19 |
$this->blockTemplate = $blockTemplate; |
| 20 |
$this->init(); |
| 21 |
} |
| 22 |
|
| 23 |
/** |
| 24 |
* Initialization method. |
| 25 |
*/ |
| 26 |
protected function init() { |
| 27 |
if (! betterdocs()->helper->current_theme_is_fse_theme() ) { |
| 28 |
return; |
| 29 |
} |
| 30 |
add_filter( 'pre_get_block_template', [ $this, 'get_block_template_fallback' ], 10, 3 ); |
| 31 |
add_filter( 'pre_get_block_file_template', [ $this, 'get_block_file_template' ], 10, 3 ); |
| 32 |
add_filter( 'get_block_templates', [ $this, 'add_block_templates' ], 10, 3 ); |
| 33 |
add_filter( 'taxonomy_template_hierarchy', [ $this, 'add_doc_archive_to_eligible_for_fallback_templates' ], 10, 1 ); |
| 34 |
} |
| 35 |
|
| 36 |
/** |
| 37 |
* This function is used on the `pre_get_block_template` hook to return the fallback template from the db in case |
| 38 |
* the template is eligible for it. |
| 39 |
* |
| 40 |
* @param \WP_Block_Template|null $template Block template object to short-circuit the default query, |
| 41 |
* or null to allow WP to run its normal queries. |
| 42 |
* @param string $id Template unique identifier (example: theme_slug//template_slug). |
| 43 |
* @param string $template_type wp_template or wp_template_part. |
| 44 |
* |
| 45 |
* @return object|null |
| 46 |
*/ |
| 47 |
public function get_block_template_fallback( $template, $id, $template_type ) { |
| 48 |
$template_name_parts = explode( '//', $id ); |
| 49 |
list( $theme, $slug ) = $template_name_parts; |
| 50 |
|
| 51 |
if ( ! $this->blockTemplate->template_is_eligible_for_docs_archive_fallback( $slug ) ) { |
| 52 |
return null; |
| 53 |
} |
| 54 |
|
| 55 |
$wp_query_args = [ |
| 56 |
'post_name__in' => [ 'archive-docs', $slug ], |
| 57 |
'post_type' => $template_type, |
| 58 |
'post_status' => [ 'auto-draft', 'draft', 'publish', 'trash' ], |
| 59 |
'no_found_rows' => true, |
| 60 |
'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query |
| 61 |
[ |
| 62 |
'taxonomy' => 'wp_theme', |
| 63 |
'field' => 'name', |
| 64 |
'terms' => $theme |
| 65 |
] |
| 66 |
] |
| 67 |
]; |
| 68 |
$template_query = new \WP_Query( $wp_query_args ); |
| 69 |
$posts = $template_query->posts; |
| 70 |
|
| 71 |
// If we have more than one result from the query, it means that the current template is present in the db (has |
| 72 |
// been customized by the user) and we should not return the `archive-docs` template. |
| 73 |
if ( count( $posts ) > 1 ) { |
| 74 |
return null; |
| 75 |
} |
| 76 |
|
| 77 |
if ( count( $posts ) > 0 ) { |
| 78 |
$template = _build_block_template_result_from_post( $posts[0] ); |
| 79 |
|
| 80 |
if ( ! is_wp_error( $template ) ) { |
| 81 |
$template->id = $theme . '//' . $slug; |
| 82 |
$template->slug = $slug; |
| 83 |
$template->title = $this->blockTemplate->get_block_template_title( $slug ); |
| 84 |
$template->description = $this->blockTemplate->get_block_template_description( $slug ); |
| 85 |
unset( $template->source ); |
| 86 |
|
| 87 |
return $template; |
| 88 |
} |
| 89 |
} |
| 90 |
return $template; |
| 91 |
} |
| 92 |
|
| 93 |
/** |
| 94 |
* Adds the `archive-docs` template to the `taxonomy-doc_category`, `taxonomy-doc_tag` |
| 95 |
* templates to be able to fall back to it. |
| 96 |
* |
| 97 |
* @param array $template_hierarchy A list of template candidates, in descending order of priority. |
| 98 |
*/ |
| 99 |
public function add_doc_archive_to_eligible_for_fallback_templates( $template_hierarchy ) { |
| 100 |
$template_slugs = array_map( |
| 101 |
'_strip_template_file_suffix', |
| 102 |
$template_hierarchy |
| 103 |
); |
| 104 |
|
| 105 |
$templates_eligible_for_fallback = array_filter( |
| 106 |
$template_slugs, |
| 107 |
[ $this->blockTemplate, 'template_is_eligible_for_docs_archive_fallback' ] |
| 108 |
); |
| 109 |
|
| 110 |
if ( count( $templates_eligible_for_fallback ) > 0 ) { |
| 111 |
$template_hierarchy[] = 'archive-docs'; |
| 112 |
} |
| 113 |
|
| 114 |
return $template_hierarchy; |
| 115 |
} |
| 116 |
|
| 117 |
/** |
| 118 |
* This function checks if there's a block template file in `betterdocs/includes/blocks/templates/` |
| 119 |
* to return to pre_get_posts short-circuiting the query in Gutenberg. |
| 120 |
* |
| 121 |
* @param \WP_Block_Template|null $template Return a block template object to short-circuit the default query, |
| 122 |
* or null to allow WP to run its normal queries. |
| 123 |
* @param string $id Template unique identifier (example: theme_slug//template_slug). |
| 124 |
* @param string $template_type wp_template or wp_template_part. |
| 125 |
* |
| 126 |
* @return mixed|\WP_Block_Template|\WP_Error |
| 127 |
*/ |
| 128 |
public function get_block_file_template( $template, $id, $template_type ) { |
| 129 |
|
| 130 |
$template_name_parts = explode( '//', $id ); |
| 131 |
|
| 132 |
if ( count( $template_name_parts ) < 2 ) { |
| 133 |
return $template; |
| 134 |
} |
| 135 |
|
| 136 |
list( $template_id, $template_slug ) = $template_name_parts; |
| 137 |
|
| 138 |
// If we are not dealing with a BetterDocs template let's return early and let it continue through the process. |
| 139 |
if ( BlockTemplate::PLUGIN_SLUG !== $template_id ) { |
| 140 |
return $template; |
| 141 |
} |
| 142 |
|
| 143 |
// If we don't have a template let Gutenberg do its thing. |
| 144 |
if ( ! $this->block_template_is_available( $template_slug, $template_type ) ) { |
| 145 |
return $template; |
| 146 |
} |
| 147 |
|
| 148 |
$directory = $this->blockTemplate->get_templates_directory( $template_type ); |
| 149 |
|
| 150 |
$template_file_path = $directory . '/' . $template_slug . '.html'; |
| 151 |
|
| 152 |
$template_object = $this->blockTemplate->create_new_block_template_object( $template_file_path, $template_type, $template_slug ); |
| 153 |
|
| 154 |
$template_built = $this->blockTemplate->build_template_result_from_file( $template_object, $template_type ); |
| 155 |
|
| 156 |
if ( null !== $template_built ) { |
| 157 |
return $template_built; |
| 158 |
} |
| 159 |
error_log(print_r($template, 1)); |
| 160 |
// Hand back over to Gutenberg if we can't find a template. |
| 161 |
return $template; |
| 162 |
} |
| 163 |
|
| 164 |
/** |
| 165 |
* Add the block template objects to be used. |
| 166 |
* |
| 167 |
* @param array $query_result Array of template objects. |
| 168 |
* @param array $query Optional. Arguments to retrieve templates. |
| 169 |
* @param string $template_type wp_template or wp_template_part. |
| 170 |
* @return array |
| 171 |
*/ |
| 172 |
public function add_block_templates( $query_result, $query, $template_type ) { |
| 173 |
if ( ! $this->blockTemplate->supports_block_templates() ) { |
| 174 |
return $query_result; |
| 175 |
} |
| 176 |
|
| 177 |
$post_type = isset( $query['post_type'] ) ? $query['post_type'] : ''; |
| 178 |
$slugs = isset( $query['slug__in'] ) ? $query['slug__in'] : []; |
| 179 |
|
| 180 |
$template_files = $this->get_block_templates( $slugs, $template_type ); |
| 181 |
|
| 182 |
// @todo: Add apply_filters to _gutenberg_get_template_files() in Gutenberg to prevent duplication of logic. |
| 183 |
foreach ( $template_files as $template_file ) { |
| 184 |
// If we have a template which is eligible for a fallback, we need to explicitly tell Gutenberg that |
| 185 |
// it has a theme file (because it is using the fallback template file). And then `continue` to avoid |
| 186 |
// adding duplicates. |
| 187 |
if ( $this->blockTemplate->set_has_theme_file_if_fallback_is_available( $query_result, $template_file ) ) { |
| 188 |
continue; |
| 189 |
} |
| 190 |
|
| 191 |
// If the current $post_type is set (e.g. on an Edit Post screen), and isn't included in the available post_types |
| 192 |
// on the template file, then lets skip it so that it doesn't get added. This is typically used to hide templates |
| 193 |
// in the template dropdown on the Edit Post page. |
| 194 |
if ( $post_type && |
| 195 |
isset( $template_file->post_types ) && |
| 196 |
! in_array( $post_type, $template_file->post_types, true ) |
| 197 |
) { |
| 198 |
continue; |
| 199 |
} |
| 200 |
|
| 201 |
// It would be custom if the template was modified in the editor, so if it's not custom we can load it from |
| 202 |
// the filesystem. |
| 203 |
if ( 'custom' !== $template_file->source ) { |
| 204 |
$template = $this->blockTemplate->build_template_result_from_file( $template_file, $template_type ); |
| 205 |
} else { |
| 206 |
$template_file->title = $this->blockTemplate->get_block_template_title( $template_file->slug ); |
| 207 |
$template_file->description = $this->blockTemplate->get_block_template_description( $template_file->slug ); |
| 208 |
$query_result[] = $template_file; |
| 209 |
continue; |
| 210 |
} |
| 211 |
|
| 212 |
$is_not_custom = false === array_search( |
| 213 |
wp_get_theme()->get_stylesheet() . '//' . $template_file->slug, |
| 214 |
array_column( $query_result, 'id' ), |
| 215 |
true |
| 216 |
); |
| 217 |
$fits_slug_query = |
| 218 |
! isset( $query['slug__in'] ) || in_array( $template_file->slug, $query['slug__in'], true ); |
| 219 |
$fits_area_query = |
| 220 |
! isset( $query['area'] ) || $template_file->area === $query['area']; |
| 221 |
$should_include = $is_not_custom && $fits_slug_query && $fits_area_query; |
| 222 |
if ( $should_include ) { |
| 223 |
$query_result[] = $template; |
| 224 |
} |
| 225 |
} |
| 226 |
|
| 227 |
// We need to remove theme (i.e. filesystem) templates that have the same slug as a customised one. |
| 228 |
// This only affects saved templates that were saved BEFORE a theme template with the same slug was added. |
| 229 |
$query_result = BlockTemplate::remove_theme_templates_with_custom_alternative( $query_result ); |
| 230 |
|
| 231 |
/** |
| 232 |
* WC templates from theme aren't included in `$this->get_block_templates()` but are handled by Gutenberg. |
| 233 |
* We need to do additional search through all templates file to update title and description for WC |
| 234 |
* templates that aren't listed in theme.json. |
| 235 |
*/ |
| 236 |
$query_result = array_map( |
| 237 |
function ( $template ) { |
| 238 |
if ( 'theme' === $template->origin ) { |
| 239 |
return $template; |
| 240 |
} |
| 241 |
if ( $template->title === $template->slug ) { |
| 242 |
$template->title = $this->blockTemplate->get_block_template_title( $template->slug ); |
| 243 |
} |
| 244 |
if ( ! $template->description ) { |
| 245 |
$template->description = $this->blockTemplate->get_block_template_description( $template->slug ); |
| 246 |
} |
| 247 |
return $template; |
| 248 |
}, |
| 249 |
$query_result |
| 250 |
); |
| 251 |
|
| 252 |
return $query_result; |
| 253 |
} |
| 254 |
|
| 255 |
/** |
| 256 |
* Gets the templates saved in the database. |
| 257 |
* |
| 258 |
* @param array $slugs An array of slugs to retrieve templates for. |
| 259 |
* @param string $template_type wp_template or wp_template_part. |
| 260 |
* |
| 261 |
* @return int[]|\WP_Post[] An array of found templates. |
| 262 |
*/ |
| 263 |
public function get_block_templates_from_db( $slugs = [], $template_type = 'wp_template' ) { |
| 264 |
$check_query_args = [ |
| 265 |
'post_type' => $template_type, |
| 266 |
'posts_per_page' => -1, |
| 267 |
'no_found_rows' => true, |
| 268 |
'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query |
| 269 |
[ |
| 270 |
'taxonomy' => 'wp_theme', |
| 271 |
'field' => 'name', |
| 272 |
'terms' => [ BlockTemplate::PLUGIN_SLUG, get_stylesheet() ] |
| 273 |
] |
| 274 |
] |
| 275 |
]; |
| 276 |
|
| 277 |
if ( is_array( $slugs ) && count( $slugs ) > 0 ) { |
| 278 |
$check_query_args['post_name__in'] = $slugs; |
| 279 |
} |
| 280 |
|
| 281 |
$check_query = new \WP_Query( $check_query_args ); |
| 282 |
$saved_betterdocs_templates = $check_query->posts; |
| 283 |
|
| 284 |
return array_map( |
| 285 |
function ( $saved_betterdocs_template ) { |
| 286 |
return $this->blockTemplate->build_template_result_from_post( $saved_betterdocs_template ); |
| 287 |
}, |
| 288 |
$saved_betterdocs_templates |
| 289 |
); |
| 290 |
} |
| 291 |
|
| 292 |
/** |
| 293 |
* Get and build the block template objects from the block template files. |
| 294 |
* |
| 295 |
* @param array $slugs An array of slugs to retrieve templates for. |
| 296 |
* @param string $template_type wp_template or wp_template_part. |
| 297 |
* |
| 298 |
* @return array WP_Block_Template[] An array of block template objects. |
| 299 |
*/ |
| 300 |
public function get_block_templates( $slugs = [], $template_type = 'wp_template' ) { |
| 301 |
$templates_from_db = $this->get_block_templates_from_db( $slugs, $template_type ); |
| 302 |
$templates_from_betterdocs = $this->blockTemplate->get_block_templates_from_betterdocs( $slugs, $templates_from_db, $template_type ); |
| 303 |
$templates = array_merge( $templates_from_db, $templates_from_betterdocs ); |
| 304 |
return $templates; |
| 305 |
} |
| 306 |
|
| 307 |
/** |
| 308 |
* Checks whether a block template with that name exists in BetterDocs Blocks |
| 309 |
* |
| 310 |
* @param string $template_name Template to check. |
| 311 |
* @param string $template_type wp_template or wp_template_part. |
| 312 |
* |
| 313 |
* @return boolean |
| 314 |
*/ |
| 315 |
public function block_template_is_available( $template_name, $template_type = 'wp_template' ) { |
| 316 |
if ( ! $template_name ) { |
| 317 |
return false; |
| 318 |
} |
| 319 |
$directory = $this->blockTemplate->get_templates_directory( $template_type ) . '/' . $template_name . '.html'; |
| 320 |
|
| 321 |
return is_readable( $directory ) || $this->get_block_templates( [ $template_name ], $template_type ); |
| 322 |
} |
| 323 |
} |
| 324 |
|