Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Allow block templates to be customised and saved #5062

Merged
merged 25 commits into from
Nov 5, 2021

Conversation

opr
Copy link
Contributor

@opr opr commented Nov 3, 2021

This PR focuses on allowing blocks templates to be customised, saved, and retrieved without errors.

The changes made are:

  • Copy the following functions from Gutenberg (because they are private and we need access to them in Blocks) into our BlockTemplateUtils class.
    • gutenberg_add_template_info
    • gutenberg_build_template_result_from_post
  • Wherever a $template's properties are accessed, normalise $template to be an object. This is necessary because this is sometimes an array or sometimes a WP_Block_Template.
  • When building templates from the woo-blocks filesystem, ensure the theme is set to woocommerce. This is necessary as the current behaviour is to save the template under the current theme's slug, which seems incorrect.
  • Add a maybe_return_blocks_template function - this is used to short-circuit the gutenberg_get_block_templates method from Gutenberg. It checks the db to see if there's a saved template already. If there is then we can return that.
  • Add get_single_block_template function. Until now there was no way to get a single woo-blocks block template in our codebase. This function is used within gutenberg_get_block_templates to override/augment the data returned by the gutenberg_get_block_templates function. This is how we inject our templates into the list of templates gutenberg finds.
  • Update get_block_templates in BlockTemplatesController.php to check the database first to see if any saved templates from the woocommerce theme exist. If they do, build a template from the saved posts.
  • Update add_block_templates in BlockTemplatesController.php to only add the templates from files that don't have a customised version in the database.
  • If the query passed to the get_block_templates filter contains slug__in then filter the results of BlockTemplatesController's get_block_templates method to only included those that match the queried slugs.
  • Add a remove_theme_templates_with_custom_alternative - this will cater for the situation described in Second testing steps for alternative scenario (Theme adds a template for an already customised template)

Fixes #5024

Other Checks

  • I've updated this doc for any feature flags or experimental interfaces implemented in this pull request.
  • I tagged two reviewers because this PR makes queries to the database or I think it might have some security impact.

Screenshots

Testing

Automated Tests

  • Changes in this PR are covered by Automated Tests.
    • Unit tests
    • E2E tests

Manual Testing

How to test the changes in this Pull Request:

  1. Use an FSE theme.
  2. Add a file named single-product.html to woocommerce-gutenberg-products-block/templates/block-templates/ - insert the following content:
<!-- wp:template-part {"slug":"header"} /-->
<!-- wp:paragraph --> <p>single-product</p> <!-- /wp:paragraph -->
<!-- wp:woocommerce/legacy-template {"template":"single-product"} /-->
<!-- wp:template-part {"slug":"footer"} /-->
  1. On the site editor change the content of this file, add a block, move things about, etc.
  2. Open the network tab in the inspector and save the page. Ensure there are no errors.
  3. Reload the editor, verify your changes have persisted.
  4. Delete the single-product.html file
  5. Reload the editor, verify your changes have persisted.
  6. Go to Appearance > Templates and verify the template is saved with a theme of woocommerce.
  7. Delete the template.
  8. Add single-product.html with the above contents to wp-content/themes/<your-theme>/block-templates/ and repeat steps 3-6.

Second testing steps for alternative scenario (Theme adds a template for an already customised template)

  1. Delete all saved templates from the Appearance > Templates page.
  2. Add the single-product.html file above to woo-blocks/templates/block-templates. Ensure there is not a single-product.html file in your theme's block-templates directory!
  3. Go to the site editor, and verify you can edit the template.
  4. Customise it and save it.
  5. Add the single-product.html file to your theme's block-templates directory.
  6. Reload the editor and ensure your customised version of single-product is loaded.
  7. Ensure there is only one entry for the template in the General templates list in the editor.

User Facing Testing

These are steps for user testing (where "user" is someone interacting with this change that is not editing any code).

  • Same as above, or
  • See steps below.
  1. Install a FSE theme, i.e. TT1 Blocks, or Tove.
  2. Make make sure you have this change applied in your WooCommerce plugin.
    The block theme needs to declare add_theme_support( 'woocommerce' ); in the functions.php file.
  3. Go to the site editor and use the arrow at the top of the page to select a template. Select General Templates > Single product.
  4. Modify the template, add some blocks, move some things around etc.
  5. Save the template.
  6. Reload the page, verify the changes have persisted.
  7. Go to the dashboard then Appearance > Templates. Verify there's a template called Single product with the theme woocommerce

@opr opr self-assigned this Nov 3, 2021
@opr opr added focus: FSE Work related to prepare WooCommerce for FSE. skip-changelog PRs that you don't want to appear in the changelog. status: needs review labels Nov 3, 2021
@opr opr marked this pull request as ready for review November 3, 2021 16:40
@opr opr requested review from a team and tjcafferkey and removed request for a team November 3, 2021 16:43
opr added 8 commits November 3, 2021 16:57
This is necessary because the gutenberg helper functions sometimes turn it into a WP_Block_Template object, and other times it's an array. Because of this it's safer to normalise them both as objects.
When a template is saved it gets saved to the database, we need to handle processing these WooCommerce templates that have been saved in the db and we need to use the gutenberg utils that are private, this is why they've been copied over.
This is because the templates we're dealing with here should always belong to WooCommerce, not the currently selected theme
These are needed to get the template from either the DB or the filesystem when saving/retrieving the template.
This will ensure the correct slug is used in the gutenberg editor.
@opr opr force-pushed the fix/saving-modified-template branch from 58f2dab to 3cc5e67 Compare November 3, 2021 17:16
@nerrad
Copy link
Contributor

nerrad commented Nov 3, 2021

Copy the following functions from Gutenberg (because they are private and we need access to them in Blocks) into our BlockTemplateUtils class.

I have just a bit of feedback that you may have already considered/researched. It might be good to check with the GB team regarding the private functions you referenced (and are duplicating logic for) to see:

  • Are there going to be public equivalents in WordPress core when WP 5.9 lands (or are they being worked on)?
  • Is this something that could be provided in WordPress/Gutenberg to have value for other plugins like Woo that might need similar functionality?

The above doesn't necessarily need to block the work here given the tight timelines we have, but it would be good to followup with so we don't end up diverging too much from what may be a more ideal solution in utilizing an API built into WP.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 3, 2021

Size Change: -4 kB (0%)

Total Size: 1.11 MB

Filename Size Change
build/active-filters-frontend.js 8.18 kB -157 B (-2%)
build/active-filters.js 8 kB -1 B (0%)
build/all-products-frontend.js 23.1 kB -160 B (-1%)
build/all-products.js 38 kB -19 B (0%)
build/atomic-block-components/add-to-cart--atomic-block-components/button.js 1.81 kB -1 B (0%)
build/atomic-block-components/add-to-cart--atomic-block-components/image--atomic-block-components/title.js 0 B -334 B (removed) 🏆
build/atomic-block-components/add-to-cart-frontend.js 8.34 kB -168 B (-2%)
build/atomic-block-components/add-to-cart.js 7.85 kB +10 B (0%)
build/atomic-block-components/button.js 874 B -1 B (0%)
build/atomic-block-components/category-list-frontend.js 467 B +2 B (0%)
build/atomic-block-components/category-list.js 469 B -1 B (0%)
build/atomic-block-components/image-frontend.js 1.71 kB -174 B (-9%)
build/atomic-block-components/image.js 1.36 kB +12 B (+1%)
build/atomic-block-components/price-frontend.js 2.13 kB -2 B (0%)
build/atomic-block-components/rating-frontend.js 563 B +2 B (0%)
build/atomic-block-components/sale-badge-frontend.js 861 B +2 B (0%)
build/atomic-block-components/sale-badge.js 868 B -1 B (0%)
build/atomic-block-components/sku.js 393 B +1 B (0%)
build/atomic-block-components/stock-indicator-frontend.js 611 B -1 B (0%)
build/atomic-block-components/summary-frontend.js 908 B +2 B (0%)
build/atomic-block-components/tag-list-frontend.js 467 B +1 B (0%)
build/atomic-block-components/title-frontend.js 1.48 kB -162 B (-10%) 👏
build/atomic-block-components/title.js 1.47 kB +11 B (+1%)
build/attribute-filter-frontend.js 18.1 kB -179 B (-1%)
build/attribute-filter.js 12.1 kB -2 B (0%)
build/blocks-checkout.js 21 kB -22 B (0%)
build/cart-blocks/accepted-payment-methods-frontend.js 1.39 kB +3 B (0%)
build/cart-blocks/checkout-button-frontend.js 1.22 kB -19 B (-2%)
build/cart-blocks/express-payment--checkout-blocks/express-payment--checkout-blocks/payment-frontend.js 4.73 kB +1 B (0%)
build/cart-blocks/express-payment-frontend.js 1.58 kB -3 B (0%)
build/cart-blocks/line-items-frontend.js 5.85 kB -7 B (0%)
build/cart-blocks/order-summary--checkout-blocks/billing-address--checkout-blocks/shipping-address-frontend.js 3.69 kB +3 B (0%)
build/cart-blocks/order-summary-frontend.js 7.4 kB -21 B (0%)
build/cart-blocks/totals-frontend.js 322 B -1 B (0%)
build/cart-frontend.js 52.2 kB -654 B (-1%)
build/cart.js 50.5 kB -38 B (0%)
build/checkout-blocks/actions-frontend.js 1.48 kB -23 B (-2%)
build/checkout-blocks/billing-address-frontend.js 2.64 kB -29 B (-1%)
build/checkout-blocks/contact-information-frontend.js 3.87 kB -18 B (0%)
build/checkout-blocks/express-payment-frontend.js 1.93 kB -2 B (0%)
build/checkout-blocks/order-note-frontend.js 1.56 kB -1 B (0%)
build/checkout-blocks/order-summary-frontend.js 12.8 kB +23 B (0%)
build/checkout-blocks/payment-frontend.js 4.56 kB -26 B (-1%)
build/checkout-blocks/shipping-address-frontend.js 3.03 kB -29 B (-1%)
build/checkout-blocks/shipping-methods-frontend.js 5.54 kB -14 B (0%)
build/checkout-frontend.js 54.4 kB -681 B (-1%)
build/checkout.js 54 kB -31 B (0%)
build/featured-category.js 7.74 kB -1 B (0%)
build/handpicked-products.js 6.27 kB -1 B (0%)
build/mini-cart-component-frontend.js 44.4 kB -651 B (-1%)
build/mini-cart-frontend.js 2.34 kB +7 B (0%)
build/price-filter-frontend.js 14.2 kB -185 B (-1%)
build/product-categories.js 3.37 kB -1 B (0%)
build/product-new.js 6.77 kB -1 B (0%)
build/product-on-sale.js 7.11 kB -1 B (0%)
build/product-tag.js 6.6 kB -1 B (0%)
build/product-top-rated.js 6.74 kB -1 B (0%)
build/reviews-by-category.js 11.4 kB -1 B (0%)
build/reviews-by-product.js 13 kB -1 B (0%)
build/reviews-frontend.js 8.97 kB +4 B (0%)
build/single-product-frontend.js 26.6 kB +3 B (0%)
build/single-product.js 9.75 kB -17 B (0%)
build/stock-filter-frontend.js 8.62 kB -153 B (-2%)
build/stock-filter.js 7.81 kB +1 B (0%)
build/vendors--atomic-block-components/add-to-cart--cart-blocks/order-summary--checkout-blocks/billing-ad--c5eb4dcd-frontend.js 16.1 kB -1 B (0%)
build/vendors--atomic-block-components/add-to-cart-frontend.js 4.77 kB +2 B (0%)
build/vendors--atomic-block-components/price--cart-blocks/line-items--cart-blocks/order-summary--checkout--8a3571de-frontend.js 5.71 kB +1 B (0%)
build/vendors--cart-blocks/order-summary--checkout-blocks/billing-address--checkout-blocks/order-summary---eb4d2cec-frontend.js 5.02 kB +1 B (0%)
build/wc-blocks-editor-style-rtl.css 15.7 kB +8 B (0%)
build/wc-blocks-editor-style.css 15.7 kB +8 B (0%)
build/wc-blocks-middleware.js 1.19 kB -282 B (-19%) 👏
build/wc-blocks-registry.js 3.71 kB +1 B (0%)
build/wc-blocks-shared-context.js 1.54 kB -5 B (0%)
build/wc-blocks-shared-hocs.js 1.92 kB +172 B (+10%) ⚠️
build/wc-blocks-style-rtl.css 21 kB +2 B (0%)
build/wc-blocks-style.css 21 kB +2 B (0%)
build/wc-settings.js 2.91 kB -1 B (0%)
ℹ️ View Unchanged
Filename Size
build/all-reviews.js 9.57 kB
build/atomic-block-components/add-to-cart--atomic-block-components/button--atomic-block-components/image---a7e2bb9b.js 3.19 kB
build/atomic-block-components/button-frontend.js 1.74 kB
build/atomic-block-components/price.js 2.11 kB
build/atomic-block-components/rating.js 565 B
build/atomic-block-components/sku-frontend.js 391 B
build/atomic-block-components/stock-indicator.js 611 B
build/atomic-block-components/summary.js 912 B
build/atomic-block-components/tag-list.js 471 B
build/cart-blocks/empty-cart-frontend.js 349 B
build/cart-blocks/filled-cart-frontend.js 806 B
build/cart-blocks/items-frontend.js 303 B
build/checkout-blocks/fields-frontend.js 346 B
build/checkout-blocks/terms-frontend.js 1.65 kB
build/checkout-blocks/totals-frontend.js 329 B
build/featured-product.js 9.42 kB
build/legacy-template.js 2.12 kB
build/mini-cart.js 5.72 kB
build/price-filter.js 9.65 kB
build/price-format.js 1.37 kB
build/product-best-sellers.js 6.62 kB
build/product-category.js 7.49 kB
build/product-search.js 2.68 kB
build/products-by-attribute.js 7.7 kB
build/vendors--cart-blocks/line-items--checkout-blocks/order-summary-frontend.js 3.14 kB
build/wc-blocks-data.js 11.3 kB
build/wc-blocks-google-analytics.js 1.98 kB
build/wc-blocks-vendors-style-rtl.css 1.37 kB
build/wc-blocks-vendors-style.css 1.37 kB
build/wc-blocks-vendors.js 254 kB
build/wc-blocks.js 3.49 kB
build/wc-payment-method-bacs.js 806 B
build/wc-payment-method-cheque.js 806 B
build/wc-payment-method-cod.js 898 B
build/wc-payment-method-paypal.js 839 B
build/wc-payment-method-stripe.js 12.2 kB

compressed-size-action

@tjcafferkey
Copy link
Contributor

Hey @nerrad this is something I checked with GB team as we already considered this. They informed us that these were not intended to used publicly outside of Gutenberg for the time being as the APIs for these functions may change at any time unannounced. In the interest of time we have chosen to duplicate these utility functions instead as you've pointed out.

That being said, I plan on writing a P2 post in the aftermath of this project to demonstrate how we have used these duplicate functions and how/why they were useful. Hopefully from this conversation we'll be able to understand what needs to happen to make this available for public usage, if at all possible.

@tjcafferkey
Copy link
Contributor

tjcafferkey commented Nov 4, 2021

The below error happened due to the following:

We need to change references such as $template_file['slug'] to $template_file->slug in the BlockTemplateController since you've normalised that data to become an object.


Seemed to be going OK (refreshing the Site Editor persisted changes) and then I got to the following step.

Go to Appearance > Templates and verify the template is saved with a theme of woocommerce.

The template was present, but when I clicked "Edit" on this template I got the following fatal error:

Screenshot 2021-11-04 at 06 41 45

After this error, when I tried to navigate back to the Site Editor via the menu item, I got stuck on the following loading state:

Screenshot 2021-11-04 at 06 42 16

Copy link
Contributor

@tjcafferkey tjcafferkey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, works as described now without any errors 🎉

Copy link
Contributor

@frontdevde frontdevde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While testing as per testing instructions works well and as expected, I'm seeing issues when testing with additional modified templates.

Could you please test the following:

  • Add an archive-product.html
<!-- wp:template-part {"slug":"header"} /-->
<!-- wp:paragraph --> <p>archive-product</p> <!-- /wp:paragraph -->
<!-- wp:woocommerce/legacy-template {"template":"archive-product"} /-->
<!-- wp:template-part {"slug":"footer"} /-->
  • Modify the archive product template in the editor & save
  • Load a single product page on the Frontend

The expected behavior would be that the single product page still loads the single product template. What I'm seeing in my testing though is that it starts loading the archive product template on the single product page.

Can you confirm this behavior?

@tjcafferkey
Copy link
Contributor

The expected behavior would be that the single product page still loads the single product template. What I'm seeing in my testing though is that it starts loading the archive product template on the single product page.

Can you confirm this behavior?

This does not happen for me, it works as expected here.

frontdevde
frontdevde previously approved these changes Nov 5, 2021
@frontdevde frontdevde dismissed their stale review November 5, 2021 09:33

Still testing, just wanted to dismiss the previous blocking review as the issue reported has been addressed.

Copy link
Contributor

@Aljullu Aljullu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job with this PR @opr! I really like how you solved several of the challenges from this PR.

I left some comments below, most of them are not blocking, but if you feel they make sense, we should either fix them or create an issue to track them.

src/Utils/BlockTemplateUtils.php Outdated Show resolved Hide resolved
Comment on lines 294 to 299
) ||
array_filter(
$this->get_block_templates(),
function ( $template ) use ( $template_name ) {
return $template->slug === $template_name;
}
Copy link
Contributor

@Aljullu Aljullu Nov 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we only care about templates saved in the database, would this be equivalent?

Suggested change
) ||
array_filter(
$this->get_block_templates(),
function ( $template ) use ( $template_name ) {
return $template->slug === $template_name;
}
) || gutenberg_get_block_template( 'woocommerce//' . $template_name );

Not blocking, but I also wonder if this fix should be done directly in WC core. I think that was an overlook from my part. But the WC core function shouldn't ignore templates saved in the database.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Aljullu you are right, WC Core seems to only look at templates on the filesystem. I can address this later. This has changed during the refactor, do you think we should go straight to the Gutenberg function, or do you think it's OK to use our get_block_templates with the $slugs arg?

src/BlockTemplatesController.php Outdated Show resolved Hide resolved
src/BlockTemplatesController.php Show resolved Hide resolved
src/BlockTemplatesController.php Outdated Show resolved Hide resolved
src/BlockTemplatesController.php Show resolved Hide resolved
Copy link
Contributor

@Aljullu Aljullu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I left a couple of corrections to comment typos, but everything else is working great on my end, good job!

* in the theme directory.
*
* @param string[] $slugs An array of slugs to filter templates by. Templates whose slug does not match will not be returned.
* @param array $already_found_templates Templates that have already been found, these will customised templates that are loaded from the database.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: There seems to be a typo in the comment these will customised.

return $query_result;
}

/**
* Get and build the block template objects from the block template files.
* Removes templates that were added to a theme's block-templates director, but already had a customised version saved in the database.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Removes templates that were added to a theme's block-templates director, but already had a customised version saved in the database.
* Removes templates that were added to a theme's block-templates directory, but already had a customised version saved in the database.


/**
* This function checks if there's a blocks template (ultimately it resolves either a saved blocks template from the
* database or a template file in `woo-gutenberg-products/block/templates/block-templates/`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* database or a template file in `woo-gutenberg-products/block/templates/block-templates/`)
* database or a template file in `woo-gutenberg-products-block/templates/block-templates/`)

@opr opr merged commit 08709e2 into trunk Nov 5, 2021
@opr opr deleted the fix/saving-modified-template branch November 5, 2021 19:07
jonny-bull pushed a commit to jonny-bull/woocommerce-gutenberg-products-block that referenced this pull request Dec 14, 2021
* Ensure $template is object before accessing properties

This is necessary because the gutenberg helper functions sometimes turn it into a WP_Block_Template object, and other times it's an array. Because of this it's safer to normalise them both as objects.

* Add Gutenberg utils for processing templates based on a post from the db

When a template is saved it gets saved to the database, we need to handle processing these WooCommerce templates that have been saved in the db and we need to use the gutenberg utils that are private, this is why they've been copied over.

* Force theme to always be WooCommerce

This is because the templates we're dealing with here should always belong to WooCommerce, not the currently selected theme

* Add maybe_return_blocks_template and get_single_block_template funcs

These are needed to get the template from either the DB or the filesystem when saving/retrieving the template.

* Set theme to always be woocommerce when making templates from files

This will ensure the correct slug is used in the gutenberg editor.

* Check if template has been customised and saved in the database first

* Prevent filesystem templates being used if a custom database one exists

* Fix syntax error from rebase

* Remove unnecessary code from BlockTemplateUtils

* Ensure template item is an object containing correct properties

* Prevent warnings from appearing

* Ensure title is added to the template when saving

* Filter templates that don't match the queried slug.

* Remove unused code

* Check if a saved version of the template exists when trying to render

* Rename default_block_template_is_available to block_template_is_available

* Re-hook pre_get_block_template before returning from maybe_return_blocks_template

* Make comment easier to read

* Look for template in woocommerce theme or real theme taxonomy

* Remove duplicated title assignment

* Prevent template being added twice when loading from the db

* Filter templates before returning if slugs are supplied

* Simplify `get_block_templates` function into two functions

* Add function to stop theme templates that are added after db ones showing

* Fix typographical errors
jonny-bull pushed a commit to jonny-bull/woocommerce-gutenberg-products-block that referenced this pull request Dec 16, 2021
* Ensure $template is object before accessing properties

This is necessary because the gutenberg helper functions sometimes turn it into a WP_Block_Template object, and other times it's an array. Because of this it's safer to normalise them both as objects.

* Add Gutenberg utils for processing templates based on a post from the db

When a template is saved it gets saved to the database, we need to handle processing these WooCommerce templates that have been saved in the db and we need to use the gutenberg utils that are private, this is why they've been copied over.

* Force theme to always be WooCommerce

This is because the templates we're dealing with here should always belong to WooCommerce, not the currently selected theme

* Add maybe_return_blocks_template and get_single_block_template funcs

These are needed to get the template from either the DB or the filesystem when saving/retrieving the template.

* Set theme to always be woocommerce when making templates from files

This will ensure the correct slug is used in the gutenberg editor.

* Check if template has been customised and saved in the database first

* Prevent filesystem templates being used if a custom database one exists

* Fix syntax error from rebase

* Remove unnecessary code from BlockTemplateUtils

* Ensure template item is an object containing correct properties

* Prevent warnings from appearing

* Ensure title is added to the template when saving

* Filter templates that don't match the queried slug.

* Remove unused code

* Check if a saved version of the template exists when trying to render

* Rename default_block_template_is_available to block_template_is_available

* Re-hook pre_get_block_template before returning from maybe_return_blocks_template

* Make comment easier to read

* Look for template in woocommerce theme or real theme taxonomy

* Remove duplicated title assignment

* Prevent template being added twice when loading from the db

* Filter templates before returning if slugs are supplied

* Simplify `get_block_templates` function into two functions

* Add function to stop theme templates that are added after db ones showing

* Fix typographical errors
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
focus: FSE Work related to prepare WooCommerce for FSE. skip-changelog PRs that you don't want to appear in the changelog.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Can't save modified WC block template if theme doesn't include it
6 participants