Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 209 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
| WP_Template_Inserter | |
0.00% |
0 / 209 |
|
0.00% |
0 / 8 |
650 | |
0.00% |
0 / 1 |
| __construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
| fetch_template_parts | |
0.00% |
0 / 40 |
|
0.00% |
0 / 1 |
72 | |||
| fetch_retry | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
| get_default_header | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| get_default_footer | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| is_template_data_inserted | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
| insert_default_template_data | |
0.00% |
0 / 62 |
|
0.00% |
0 / 1 |
72 | |||
| register_template_post_types | |
0.00% |
0 / 89 |
|
0.00% |
0 / 1 |
2 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * Full site editing file. |
| 4 | * |
| 5 | * @package A8C\FSE |
| 6 | */ |
| 7 | |
| 8 | namespace Automattic\Jetpack\Jetpack_Mu_Wpcom\Wpcom_Legacy_FSE; |
| 9 | |
| 10 | /** |
| 11 | * Class WP_Template_Inserter |
| 12 | */ |
| 13 | class WP_Template_Inserter { |
| 14 | /** |
| 15 | * Template header content. |
| 16 | * |
| 17 | * @var string $header_content |
| 18 | */ |
| 19 | private $header_content; |
| 20 | |
| 21 | /** |
| 22 | * Template footer content. |
| 23 | * |
| 24 | * @var string $footer_content |
| 25 | */ |
| 26 | private $footer_content; |
| 27 | |
| 28 | /** |
| 29 | * Current theme slug. |
| 30 | * |
| 31 | * @var string $theme_slug |
| 32 | */ |
| 33 | private $theme_slug; |
| 34 | |
| 35 | /** |
| 36 | * Image URLs contained in the returned from the template API |
| 37 | * |
| 38 | * @var array $image_urls |
| 39 | */ |
| 40 | private $image_urls; |
| 41 | |
| 42 | /** |
| 43 | * This site option will be used to indicate that template data has already been |
| 44 | * inserted for this theme, in order to prevent this functionality from running |
| 45 | * more than once. |
| 46 | * |
| 47 | * @var string $fse_template_data_option |
| 48 | */ |
| 49 | private $fse_template_data_option; |
| 50 | |
| 51 | /** |
| 52 | * The strategy to use for default data insertion. |
| 53 | * |
| 54 | * 'use-api' will use the wpcom API to get specifc content depending on the theme. |
| 55 | * |
| 56 | * 'use-local' will use the locally defined defaults. |
| 57 | * |
| 58 | * @var string $loading_strategy |
| 59 | */ |
| 60 | private $loading_strategy; |
| 61 | |
| 62 | /** |
| 63 | * WP_Template_Inserter constructor. |
| 64 | * |
| 65 | * @param string $theme_slug Current theme slug. |
| 66 | * @param string $loading_strategy The strategy to use to load the template part content. |
| 67 | */ |
| 68 | public function __construct( $theme_slug, $loading_strategy = 'use-local' ) { |
| 69 | $this->theme_slug = $theme_slug; |
| 70 | $this->header_content = ''; |
| 71 | $this->footer_content = ''; |
| 72 | $this->loading_strategy = $loading_strategy; |
| 73 | |
| 74 | /* |
| 75 | * Previously the option suffix was '-fse-template-data'. Bumping this to '-fse-template-data-v1' |
| 76 | * to differentiate it from the old data that was not provided by the API. Note that we don't want |
| 77 | * to tie this to plugin version constant, because that would trigger the insertion on each plugin |
| 78 | * update, even when it's not necessary (it would duplicate existing data). |
| 79 | */ |
| 80 | $this->fse_template_data_option = $this->theme_slug . '-fse-template-data-v1'; |
| 81 | } |
| 82 | |
| 83 | /** |
| 84 | * Retrieves template parts content. |
| 85 | */ |
| 86 | public function fetch_template_parts() { |
| 87 | // Use default data if we don't want to fetch from the API. |
| 88 | if ( 'use-local' === $this->loading_strategy ) { |
| 89 | $this->header_content = $this->get_default_header(); |
| 90 | $this->footer_content = $this->get_default_footer(); |
| 91 | return; |
| 92 | } |
| 93 | |
| 94 | $request_url = 'https://public-api.wordpress.com/wpcom/v2/full-site-editing/templates'; |
| 95 | |
| 96 | $request_args = array( |
| 97 | 'body' => array( 'theme_slug' => $this->theme_slug ), |
| 98 | ); |
| 99 | |
| 100 | $response = $this->fetch_retry( $request_url, $request_args ); |
| 101 | |
| 102 | if ( ! $response ) { |
| 103 | do_action( |
| 104 | 'a8c_fse_log', |
| 105 | 'template_population_failure', |
| 106 | array( |
| 107 | 'context' => 'WP_Template_Inserter->fetch_template_parts', |
| 108 | 'error' => 'Fetch retry timeout', |
| 109 | 'theme_slug' => $this->theme_slug, |
| 110 | ) |
| 111 | ); |
| 112 | $this->header_content = $this->get_default_header(); |
| 113 | $this->footer_content = $this->get_default_footer(); |
| 114 | return; |
| 115 | } |
| 116 | |
| 117 | $api_response = json_decode( wp_remote_retrieve_body( $response ), true ); |
| 118 | if ( ! empty( $api_response['code'] ) && 'not_found' === $api_response['code'] ) { |
| 119 | do_action( |
| 120 | 'a8c_fse_log', |
| 121 | 'template_population_failure', |
| 122 | array( |
| 123 | 'context' => 'WP_Template_Inserter->fetch_template_parts', |
| 124 | 'error' => 'Did not find remote template data for the given theme.', |
| 125 | 'theme_slug' => $this->theme_slug, |
| 126 | ) |
| 127 | ); |
| 128 | return; |
| 129 | } |
| 130 | |
| 131 | // Default to first returned header for now. Support for multiple headers will be added in future iterations. |
| 132 | if ( ! empty( $api_response['headers'] ) ) { |
| 133 | $this->header_content = $api_response['headers'][0]; |
| 134 | } |
| 135 | |
| 136 | // Default to first returned footer for now. Support for multiple footers will be added in future iterations. |
| 137 | if ( ! empty( $api_response['footers'] ) ) { |
| 138 | $this->footer_content = $api_response['footers'][0]; |
| 139 | } |
| 140 | |
| 141 | // This should contain all image URLs for images in any header or footer. |
| 142 | if ( ! empty( $api_response['image_urls'] ) ) { |
| 143 | $this->image_urls = $api_response['image_urls']; |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | /** |
| 148 | * Retries a call to wp_remote_get on error. |
| 149 | * |
| 150 | * @param string $request_url Url of the api call to make. |
| 151 | * @param array $request_args Additional arguments for the api call. |
| 152 | * @param int $attempt The number of the attempt being made. |
| 153 | * @return array|null wp_remote_get response array |
| 154 | */ |
| 155 | private function fetch_retry( $request_url, $request_args = null, $attempt = 1 ) { |
| 156 | $max_retries = 3; |
| 157 | |
| 158 | $response = wp_remote_get( $request_url, $request_args ); |
| 159 | |
| 160 | if ( ! is_wp_error( $response ) ) { |
| 161 | return $response; |
| 162 | } |
| 163 | |
| 164 | if ( $attempt > $max_retries ) { |
| 165 | return null; |
| 166 | } |
| 167 | |
| 168 | sleep( pow( 2, $attempt ) ); |
| 169 | ++$attempt; |
| 170 | return $this->fetch_retry( $request_url, $request_args, $attempt ); |
| 171 | } |
| 172 | |
| 173 | /** |
| 174 | * Returns a default header if call to template api fails for some reason. |
| 175 | * |
| 176 | * @return string Content of a default header |
| 177 | */ |
| 178 | public function get_default_header() { |
| 179 | return '<!-- wp:a8c/site-description /--> |
| 180 | <!-- wp:a8c/site-title /--> |
| 181 | <!-- wp:a8c/navigation-menu /-->'; |
| 182 | } |
| 183 | |
| 184 | /** |
| 185 | * Returns a default footer if call to template api fails for some reason. |
| 186 | * |
| 187 | * @return string Content of a default footer |
| 188 | */ |
| 189 | public function get_default_footer() { |
| 190 | return '<!-- wp:a8c/navigation-menu /-->'; |
| 191 | } |
| 192 | |
| 193 | /** |
| 194 | * Determines whether FSE data has already been inserted. |
| 195 | * |
| 196 | * @return bool True if FSE data has already been inserted, false otherwise. |
| 197 | */ |
| 198 | public function is_template_data_inserted() { |
| 199 | return get_option( $this->fse_template_data_option ) ? true : false; |
| 200 | } |
| 201 | |
| 202 | /** |
| 203 | * This function will be called on plugin activation hook. |
| 204 | */ |
| 205 | public function insert_default_template_data() { |
| 206 | do_action( |
| 207 | 'a8c_fse_log', |
| 208 | 'before_template_population', |
| 209 | array( |
| 210 | 'context' => 'WP_Template_Inserter->insert_default_template_data', |
| 211 | 'theme_slug' => $this->theme_slug, |
| 212 | ) |
| 213 | ); |
| 214 | |
| 215 | if ( $this->is_template_data_inserted() ) { |
| 216 | /* |
| 217 | * Bail here to prevent inserting the FSE data twice for any given theme. |
| 218 | * Multiple themes will still be able to insert different templates. |
| 219 | */ |
| 220 | do_action( |
| 221 | 'a8c_fse_log', |
| 222 | 'template_population_failure', |
| 223 | array( |
| 224 | 'context' => 'WP_Template_Inserter->insert_default_template_data', |
| 225 | 'error' => 'Data already exist', |
| 226 | 'theme_slug' => $this->theme_slug, |
| 227 | ) |
| 228 | ); |
| 229 | return; |
| 230 | } |
| 231 | |
| 232 | // Set header and footer content based on data fetched from the WP.com API. |
| 233 | $this->fetch_template_parts(); |
| 234 | |
| 235 | // Avoid creating template parts if data hasn't been fetched properly. |
| 236 | if ( empty( $this->header_content ) || empty( $this->footer_content ) ) { |
| 237 | return; |
| 238 | } |
| 239 | |
| 240 | $this->register_template_post_types(); |
| 241 | |
| 242 | $header_id = wp_insert_post( |
| 243 | array( |
| 244 | 'post_title' => 'Header', |
| 245 | 'post_content' => $this->header_content, |
| 246 | 'post_status' => 'publish', |
| 247 | 'post_type' => 'wp_template_part', |
| 248 | 'comment_status' => 'closed', |
| 249 | 'ping_status' => 'closed', |
| 250 | ) |
| 251 | ); |
| 252 | |
| 253 | if ( ! term_exists( "$this->theme_slug-header", 'wp_template_part_type' ) ) { |
| 254 | wp_insert_term( "$this->theme_slug-header", 'wp_template_part_type' ); |
| 255 | } |
| 256 | |
| 257 | wp_set_object_terms( $header_id, "$this->theme_slug-header", 'wp_template_part_type' ); |
| 258 | |
| 259 | $footer_id = wp_insert_post( |
| 260 | array( |
| 261 | 'post_title' => 'Footer', |
| 262 | 'post_content' => $this->footer_content, |
| 263 | 'post_status' => 'publish', |
| 264 | 'post_type' => 'wp_template_part', |
| 265 | 'comment_status' => 'closed', |
| 266 | 'ping_status' => 'closed', |
| 267 | ) |
| 268 | ); |
| 269 | |
| 270 | if ( ! term_exists( "$this->theme_slug-footer", 'wp_template_part_type' ) ) { |
| 271 | wp_insert_term( "$this->theme_slug-footer", 'wp_template_part_type' ); |
| 272 | } |
| 273 | |
| 274 | wp_set_object_terms( $footer_id, "$this->theme_slug-footer", 'wp_template_part_type' ); |
| 275 | |
| 276 | add_option( $this->fse_template_data_option, true ); |
| 277 | |
| 278 | // Note: we set the option before doing the image upload because the template |
| 279 | // parts can work with the remote URLs even if this fails. |
| 280 | $image_urls = $this->image_urls; |
| 281 | if ( ! empty( $image_urls ) ) { |
| 282 | // Uploading images locally does not work in the WordPress.com environment, |
| 283 | // so we use an action to handle it with Headstart there. |
| 284 | if ( has_action( 'a8c_fse_upload_template_part_images' ) ) { |
| 285 | do_action( 'a8c_fse_upload_template_part_images', $image_urls, array( $header_id, $footer_id ) ); |
| 286 | } |
| 287 | } |
| 288 | |
| 289 | do_action( |
| 290 | 'a8c_fse_log', |
| 291 | 'template_population_success', |
| 292 | array( |
| 293 | 'context' => 'WP_Template_Inserter->insert_default_template_data', |
| 294 | 'theme_slug' => $this->theme_slug, |
| 295 | ) |
| 296 | ); |
| 297 | } |
| 298 | |
| 299 | /** |
| 300 | * Register post types. |
| 301 | */ |
| 302 | public function register_template_post_types() { |
| 303 | register_post_type( |
| 304 | 'wp_template_part', // phpcs:ignore WordPress.NamingConventions.ValidPostTypeSlug.Reserved |
| 305 | array( |
| 306 | 'labels' => array( |
| 307 | 'name' => _x( 'Template Parts', 'post type general name', 'jetpack-mu-wpcom' ), |
| 308 | 'singular_name' => _x( 'Template Part', 'post type singular name', 'jetpack-mu-wpcom' ), |
| 309 | 'menu_name' => _x( 'Template Parts', 'admin menu', 'jetpack-mu-wpcom' ), |
| 310 | 'name_admin_bar' => _x( 'Template Part', 'add new on admin bar', 'jetpack-mu-wpcom' ), |
| 311 | 'add_new' => _x( 'Add New', 'Template', 'jetpack-mu-wpcom' ), |
| 312 | 'add_new_item' => __( 'Add New Template Part', 'jetpack-mu-wpcom' ), |
| 313 | 'new_item' => __( 'New Template Part', 'jetpack-mu-wpcom' ), |
| 314 | 'edit_item' => __( 'Edit Template Part', 'jetpack-mu-wpcom' ), |
| 315 | 'view_item' => __( 'View Template Part', 'jetpack-mu-wpcom' ), |
| 316 | 'all_items' => __( 'All Template Parts', 'jetpack-mu-wpcom' ), |
| 317 | 'search_items' => __( 'Search Template Parts', 'jetpack-mu-wpcom' ), |
| 318 | 'not_found' => __( 'No template parts found.', 'jetpack-mu-wpcom' ), |
| 319 | 'not_found_in_trash' => __( 'No template parts found in Trash.', 'jetpack-mu-wpcom' ), |
| 320 | 'filter_items_list' => __( 'Filter template parts list', 'jetpack-mu-wpcom' ), |
| 321 | 'items_list_navigation' => __( 'Template parts list navigation', 'jetpack-mu-wpcom' ), |
| 322 | 'items_list' => __( 'Template parts list', 'jetpack-mu-wpcom' ), |
| 323 | 'item_published' => __( 'Template part published.', 'jetpack-mu-wpcom' ), |
| 324 | 'item_published_privately' => __( 'Template part published privately.', 'jetpack-mu-wpcom' ), |
| 325 | 'item_reverted_to_draft' => __( 'Template part reverted to draft.', 'jetpack-mu-wpcom' ), |
| 326 | 'item_scheduled' => __( 'Template part scheduled.', 'jetpack-mu-wpcom' ), |
| 327 | 'item_updated' => __( 'Template part updated.', 'jetpack-mu-wpcom' ), |
| 328 | ), |
| 329 | 'menu_icon' => 'dashicons-layout', |
| 330 | 'public' => false, |
| 331 | 'show_ui' => true, // Otherwise we'd get permission error when trying to edit them. |
| 332 | 'show_in_menu' => false, |
| 333 | 'rewrite' => false, |
| 334 | 'capability_type' => 'template_part', |
| 335 | 'capabilities' => array( |
| 336 | // You need to be able to edit posts, in order to read templates in their raw form. |
| 337 | 'read' => 'edit_posts', |
| 338 | // You need to be able to customize, in order to create templates. |
| 339 | 'create_posts' => 'edit_theme_options', |
| 340 | 'edit_posts' => 'edit_theme_options', |
| 341 | 'delete_posts' => 'edit_theme_options', |
| 342 | 'edit_published_posts' => 'edit_theme_options', |
| 343 | 'delete_published_posts' => 'edit_theme_options', |
| 344 | 'edit_others_posts' => 'edit_theme_options', |
| 345 | 'delete_others_posts' => 'edit_theme_options', |
| 346 | 'publish_posts' => 'edit_theme_options', |
| 347 | ), |
| 348 | 'map_meta_cap' => true, |
| 349 | 'supports' => array( |
| 350 | 'title', |
| 351 | 'editor', |
| 352 | 'revisions', |
| 353 | ), |
| 354 | ) |
| 355 | ); |
| 356 | |
| 357 | register_taxonomy( |
| 358 | 'wp_template_part_type', |
| 359 | 'wp_template_part', |
| 360 | array( |
| 361 | 'labels' => array( |
| 362 | 'name' => _x( 'Template Part Types', 'taxonomy general name', 'jetpack-mu-wpcom' ), |
| 363 | 'singular_name' => _x( 'Template Part Type', 'taxonomy singular name', 'jetpack-mu-wpcom' ), |
| 364 | 'menu_name' => _x( 'Template Part Types', 'admin menu', 'jetpack-mu-wpcom' ), |
| 365 | 'all_items' => __( 'All Template Part Types', 'jetpack-mu-wpcom' ), |
| 366 | 'edit_item' => __( 'Edit Template Part Type', 'jetpack-mu-wpcom' ), |
| 367 | 'view_item' => __( 'View Template Part Type', 'jetpack-mu-wpcom' ), |
| 368 | 'update_item' => __( 'Update Template Part Type', 'jetpack-mu-wpcom' ), |
| 369 | 'add_new_item' => __( 'Add New Template Part Type', 'jetpack-mu-wpcom' ), |
| 370 | 'new_item_name' => __( 'New Template Part Type', 'jetpack-mu-wpcom' ), |
| 371 | 'parent_item' => __( 'Parent Template Part Type', 'jetpack-mu-wpcom' ), |
| 372 | 'parent_item_colon' => __( 'Parent Template Part Type:', 'jetpack-mu-wpcom' ), |
| 373 | 'search_items' => __( 'Search Template Part Types', 'jetpack-mu-wpcom' ), |
| 374 | 'not_found' => __( 'No template part types found.', 'jetpack-mu-wpcom' ), |
| 375 | 'back_to_items' => __( 'Back to template part types', 'jetpack-mu-wpcom' ), |
| 376 | ), |
| 377 | 'public' => false, |
| 378 | 'publicly_queryable' => false, |
| 379 | 'show_ui' => false, |
| 380 | 'show_in_menu' => false, |
| 381 | 'show_in_nav_menu' => false, |
| 382 | 'show_in_rest' => true, |
| 383 | 'rest_base' => 'template_part_types', |
| 384 | 'show_tagcloud' => false, |
| 385 | 'hierarchical' => true, |
| 386 | 'rewrite' => false, |
| 387 | 'capabilities' => array( |
| 388 | 'manage_terms' => 'edit_theme_options', |
| 389 | 'edit_terms' => 'edit_theme_options', |
| 390 | 'delete_terms' => 'edit_theme_options', |
| 391 | 'assign_terms' => 'edit_theme_options', |
| 392 | ), |
| 393 | ) |
| 394 | ); |
| 395 | } |
| 396 | } |