There are two ways of managing Wordpress dependencies in a project. Just commiting the core and plugins in the repo or use composer in order to keep track of the versions of the core and plugins.
I first thought the composer approach was better than just having directly Wordpress at the root of the repository. But there are actually issues that makes it painful to use composer since Wordpress is not supporting composer at all. I encountered an issue where a simple "composer update "can delete all the folders that are tracked by composer including custom theme/custom plugins or even files. One solution is to track your custom code in other repositories with a composer.json file in order to be able to require them, but splitting the project in multiple repositories is for me creating more mess than just using a regular wordpress without composer.
Another issue of using composer is that a lot of wordpress hosting companies are just not compatible with composer and consequently will need to be removed from the project depending on the hosting picked by the client. (WP Engine is one of those not supporting composer).
If you find solutions to these issues, don't hesitate to update this documentation.
If you want to use composer, there is a template here: https://github.com/wecodemore/wpstarter
Gutenberg combined with advanced custom fields blocks is a powerful combo that allows you to quickly build blocks and to get the powerful Gutenberg interface that Wordpress is now providing. This is the best way to build highly customizable pages.
add_action( 'acf/init', 'mytheme_acf_init' );
function mytheme_acf_init() {
// Bail out if function doesn’t exist.
if ( ! function_exists( 'acf_register_block' ) ) {
return;
}
acf_register_block( array(
'name' => 'custom_block_name',
'title' => __( 'My block name', 'mytheme' ),
'description' => __( 'A block to display something.', 'mytheme' ),
'render_callback' => 'mytheme_block_callback',
'category' => 'mytheme',
'icon' => 'align-pull-left',
'keywords' => array( 'block', 'custom' ),
) );
}
This block of code can be put in your theme. It allows to declare a new ACF block type. The render_callback needs also to be written in code, but we'll come back to it in later steps.
Before adding fields, make sure you have a folder name "acf-json" at the root of your theme. It will automatically write the JSON configuration of your fields in your folder when you will create the fields using the interface. This configuration can then be synchronized on other environments in order to recreate/delete/modify the fields in just one click.
If you need more information, the whole system is explained in detail in the ACF website: https://www.advancedcustomfields.com/resources/local-json/
Go to the ACF interface and create a new field group. You just have to add a title (the name of your block to keep it consistent) and then you have to change the Location rule. Pick "Block" for the first select, and then you should be able to find the block type that you defined in the code in the second select.
You should be able to create fields by using the button "+ Add field", it will give you plenty of option fir the field types, default value, required etc... Setup the fields as you want them to, they will show up when creating a new block of that type. Finish by pressing "Publish".
Now that we have the block type and the fields attached to it, we can now render the data into actual HTML. The callback we talked about allows you to print the block HTML and access the fields values. There is an example of how to render a block with Twig.
function mytheme_get_default_context_block($block, $content = '', $is_preview = false) {
$context = Timber::context();
$context['block'] = $block;
$context['fields'] = get_fields();
$context['is_preview'] = $is_preview;
if (!empty($block['className']) && strpos($block['className'], 'is-style-') !== FALSE) {
$context['style'] = str_replace("is-style-", "", $block['className']);
}
return $context;
}
function mytheme_block_callback( $block, $content = '', $is_preview = false ) {
$context = rsaw_get_default_context_block($block, $content, $is_preview);
$template_name = str_replace('acf/', '', $block['name']);
Timber::render( 'block/'.$template_name.'.twig', $context );
}
The first function is a helper that you can reuse for each block type in order to access the regular data like the block and the fields. If you need some custom logic for your block type like pulling the latest posts, then you would do this in the callback of your block. Once you returned the HTML, then you just have to write some CSS in your theme to style and your block integration is over.
Block Styles allows you to display the same block type in different ways. For example, if you have a regular card block (image, title, text), there is a high chance that your design displays these cards in different ways. The block style will allow you to let the user pick a look for the card block instead of having to create two different block types with the same fields.
function mytheme_register_block_styles() {
register_block_style(
'acf/card',
array(
'name' => 'box_card',
'label' => __( 'Box', 'textdomain' ),
'is_default' => true,
'style_handle' => 'components_card_box',
)
);
register_block_style(
'acf/card',
array(
'name' => 'box_icon_card',
'label' => __( 'Box Icon', 'textdomain' ),
'is_default' => false,
'style_handle' => 'components_card_boxIcon',
)
);
}
add_action( 'after_setup_theme' , 'mytheme_register_block_styles' );
This code sample allows you to declare two different block styles for the block type "card". Any block type created using the ACF hook needs to be with the format "acf/block_type". The "style_handle" is the name of a registered style, wordpress will load automatically the stylesheet for the preview in the admin.
This is an example of the interface with different block styles for a certain block type:
Block Patterns are a collection of predefined blocks that you can insert into pages and posts and then customize with your own content. To define a pattern, you can use the code below:
function mytheme_register_my_patterns() {
register_block_pattern_category(
'mytheme',
array( 'label' => __( 'My Theme', 'mytheme' ) )
);
register_block_pattern(
'mytheme/section_title',
array(
'title' => __( 'Section Title', 'mytheme' ),
'categories' => [
'mytheme'
],
'description' => _x( 'A section title in a container.', 'Block pattern description', 'mytheme' ),
'content' => "<!-- wp:acf/columns {\"id\":\"block_6253c2bc7b38b\",\"name\":\"acf/columns\",\"data\":{\"columns\":\"1\",\"_columns\":\"field_620a24ce28845\",\"container\":\"default\",\"_container\":\"field_620a2549825e9\",\"background_color\":\"\",\"_background_color\":\"field_620a258de53a7\",\"background_image\":\"\",\"_background_image\":\"field_623154b4d7206\",\"padding_top\":\"none\",\"_padding_top\":\"field_620a25b29c41b\",\"padding_bottom\":\"none\",\"_padding_bottom\":\"field_620a260924815\",\"vertical_spacing\":\"default\",\"_vertical_spacing\":\"field_621fad45a3b31\",\"horizontal_spacing\":\"default\",\"_horizontal_spacing\":\"field_6246c8e0e7780\",\"mobile\":\"0\",\"_mobile\":\"field_624ae58cc4617\",\"activate_container_spacings\":\"0\",\"_activate_container_spacings\":\"field_623175ce6cabe\"},\"align\":\"\",\"mode\":\"preview\"} -->\n<!-- wp:acf/column {\"id\":\"block_6253c2cc7b38c\",\"name\":\"acf/column\",\"data\":{},\"align\":\"\",\"mode\":\"preview\"} -->\n<!-- wp:acf/section-title {\"id\":\"block_6253c2e07b38d\",\"name\":\"acf/section-title\",\"data\":{\"title\":\"Section title\",\"_title\":\"field_6221fdbb7419b\",\"link\":{\"title\":\"View all\",\"url\":\"#\",\"target\":\"\"},\"_link\":\"field_6221fdc57419c\"},\"align\":\"\",\"mode\":\"preview\"} /-->\n<!-- /wp:acf/column -->\n<!-- /wp:acf/columns -->",
)
);
}
add_action( 'init', 'mytheme_register_my_patterns' );
The content part is what wordpress is using to copy blocks inside Gutenberg. To copy, click on the three dots and use the "Copy" feature
Then you will need to escape that string in order to paste it in the "content" entree of the associative array. You can use a tool such as this one to escape: https://onlinetexttools.com/escape-text
Once done, you custom pattern should show up in the interface among the other ones.
advanced-custom-fields: This plugin is a must have and allows you to add fields to different
svg-support: Upload SVG files to the Media Library and render SVG files inline for direct styling/animation of an SVG's internal elements using CSS/JS.
enable-media-replace: Enable replacing media files by uploading a new file in the "Edit Media" section of the WordPress Media Library.
redirection: Manage all your 301 redirects and monitor 404 errors
regenerate-thumbnails: Regenerate the thumbnails for one or more of your image uploads. Useful when changing their sizes or your theme.
resizable-editor-sidebar: Enables functionality to make the Gutenberg sidebar width resizable
tinymce-advanced: Extends and enhances the block editor (Gutenberg) and the classic editor (TinyMCE).
wordpress-seo: Yoast SEO is a must have for seo purpose, it allows you customize for each post, the meta description, keywords, facebook/twitter thumbnails etc...
In order to improve performance of the website, you'll need to use at least one caching plugin because wordpress core is not doing a great job. These are two suggestions for caching plugins:
All in one caching plugin, it has lots of features like page caching, JS/CSS cache, minify, critical CSS, image lazy loading...
All in one caching plugin, it has lots of features like page caching, minify, browser cache, database cache, CDN...
Note: using autoptimize and wp-super-cache together can lead to some issues.
Timber is a wordpress tool that allows you to use Twig in the templates of wordpress instead of mixing HTML and PHP code.
There is a plugin called timber-library that allows to automatically load Timber into Wordpress. It is recommended to use it for a simpler integration but it is also possible to load it yourself if you don't want the dependency on this plugin.
When starting a new theme, it is highly recommended to use a starter theme when working with Timber. Indeed, there is quite some code to write in order to have a working base to use Twig in your templates. The starter theme is taking care of that base that is linking Wordpress to the Twig template system of Timber.
The Timber starter theme can be found here: https://github.com/timber/starter-theme
Timber has a detailed documentation that is very useful to find wordpress related function available in Twig.
https://timber.github.io/docs/
There are already plenty of Twig functions to access data and modify it, so make sure to read the documentation before trying to rewrite something that already exists.
It is possible to create your own custom Twig Function. The code below is showing you how to do that, and gives you two very useful ones "enqueue_style" and "enqueue_script". It allows you to enqueue your CSS and JS directly from your Twig templates. If you adopt a component based approach, using these functions you can load only the CSS and JS that you need. One limitation is when you are loading content using Ajax. If you do, you will have to make sure that the styles and JS are already there because wp_enqueue_style and wp_enqueue_script in wordpress are not working when used through AJAX requests.
class StarterSite extends Timber\Site {
/** Add timber support. */
public function __construct() {
add_filter( 'timber/twig', array( $this, 'add_to_twig' ) );
parent::__construct();
}
public function mytheme_unique_id( $text ) {
return wp_unique_id($text);
}
/** This is where you can add your own functions to twig.
*
* @param string $twig get extension.
*/
public function add_to_twig( $twig ) {
$twig->addExtension( new Twig\Extension\StringLoaderExtension() );
$twig->addFilter( new Timber\Twig_Filter( 'uniqueId', array( $this, 'mytheme_unique_id' ) ) );
$function = new \Twig_SimpleFunction('enqueue_script', function ($handle) {
// register it elsewhere
wp_enqueue_script( $handle);
});
$twig->addFunction($function);
$function = new \Twig_SimpleFunction('enqueue_style', function ($handle) {
// register it elsewhere
wp_enqueue_style( $handle);
});
$twig->addFunction($function);
return $twig;
}
}
This section is just about sharing some code samples that are solving common issues encountered across Wordpress projects.
In case you want to disable the Gutenberg editor for a specific post type, you can use this code sample to achieve it. This is useful if your post type has a fixed template and you just want the editor users to fill the fields and not manage a complicated layout with the block editor.
add_filter('use_block_editor_for_post_type', 'function_disable_gutenberg', 10, 2);
function function_disable_gutenberg($current_status, $post_type) {
// Disable gutenberg.
if ($post_type === 'type') return false;
return $current_status;
}
In some cases, the restriction of Wordpress on the files you can upload is a problem. For example Lottie files are JSON and you cannot upload these by default. This code sample will allow the upload of JSON files.
function mytheme_mime_types($mimes) {
$mimes['json'] = 'application/json';
return $mimes;
}
add_filter('upload_mimes', 'mytheme_mime_types');
You can create your own image styles in order to optimize the size of the images. For example, this code allows you to define two image styles.
add_action( 'after_setup_theme', 'mytheme_add_image_sizes' );
function mytheme_add_image_sizes() {
add_image_size( 'news_teaser', 302, 302, true );
add_image_size( 'event_teaser', 260, 220, true );
}
In order to customize the colors of an ACF color field, you will need to create a javascript file and to include that file using the hook "acf/input/admin_enqueue_scripts".
js/acf.js
(function($) {
acf.add_filter('color_picker_args', function( args, $field ){
args.palettes = ['#FFFFFF', '#F9F6F2', '#F4F4F4', '#F9F9F9', '#E7E7E7', '#5C6F7C', '#F9F6F2', '#1B3665', '#000000']
return args;
});
})(jQuery);
functions.php
function mytheme_acf_enqueue_scripts() {
wp_enqueue_script(
'acf-mytheme',
get_template_directory_uri() . '/js/acf.js',
array(),
'1.0.0',
true
);
}
add_action('acf/input/admin_enqueue_scripts', 'mytheme_acf_enqueue_scripts');
Wordpress has a system of shortcodes, it is a specific code that will be replaced dynamically by something else that is calculated or pulled from the database. You can defined your own shortcodes. There is a simple one to display the current year that is often displayed in the footer of websites.
function year_shortcode() {
$year = date('Y');
return $year;
}
add_shortcode('year', 'year_shortcode');