-
Notifications
You must be signed in to change notification settings - Fork 0
Factor1 ACF Pro Standards
On every Factor1 site, our goal is to make our admins as easy/fill-in-the-blank/client-proof as possible. Therefore, we do not want clients stumbling into our Custom Fields and messing around, since a) they likely don’t know what they’re doing, and b) they will not have access to the code files to make the corresponding code changes even if they knew what they were doing.
To that end, include the following snippet on every site (inc/tweaks.php
is a nice place for this snippet). It will ensure that the Custom Fields menu item is only visible to the factor1admin
user:
// Hide ACF from everyone except factor1admin
$us = get_user_by('login', 'factor1admin');
// If the current logged-in user is not us, hide ACF
if(wp_get_current_user()->user_login !== $us->user_login) :
add_filter('acf/settings/show_admin', '__return_false');
endif;
Also make sure to include the following snippets (in inc/tweaks.php
) to prevent clients from doing any Sneaky Pete things like adding styles that conflict with ours/cause issues:
// Remove additional css option from customizer
function prefix_remove_css_section( $wp_customize ) {
$wp_customize->remove_section( 'custom_css' );
}
add_action( 'customize_register', 'prefix_remove_css_section', 15 );
// Remove theme/plugin editors from admin
define( 'DISALLOW_FILE_EDIT', true );
Many of our themes feature a flexible template that uses the ACF Flexible Content field.
For long pages, it can be difficult to find a particular section in a long list of sections, even when collapsed. So to that end, include the following snippets that allow users to add a label on the field:
// Add ACF Flexible Content section titles
function my_acf_fields_flexible_content_layout_title( $title, $field, $layout, $i ) {
// load text sub field
if( $text = get_sub_field('section_title') ) {
$title .= ': <b>' . esc_html($text) . '</b>';
}
return $title;
}
add_filter('acf/fields/flexible_content/layout_title/name=page_sections', 'my_acf_fields_flexible_content_layout_title', 10, 4);
Note that the flexible content field in this example is named page_sections
and that there needs to be a plain text field in each layout group called section_title
for this snippet to work appropriately.
At Factor1 we design sites with reusable components/modularization in mind. This saves us a lot of time in the long run as it is easier to build a section once and apply it where needed. The ACF Clone field is our best friend in making this possible.
When beginning a project and reviewing design files, spend time identifying sections that are reused throughout the design. Note which sections are identical or similar enough (with some helpful conditional logic) to be considered modules.
Let’s look at a common scenario on Factor1 sites: a text/image split.
Let’s say this section appears once on our home template and as an option on our flexible content section-based default template.
(Note that this section’s code is built to F1 standards, including the use of Ginger Grid. See notes on F1 required/recommended libraries and packages here)
1. Build the ACF Field Group
- Make sure to set this group to be inactive so that it doesn’t appear in random places and confuse the client
- Create all fields necessary for this group. Be sure to include any requirements like the option to choose where the image sits
2. Apply the section where needed in the admin via the ACF Clone Field
- It is highly recommended that you create the flexible content section group as its own cloneable ACF Group, in case multiple page templates/views end up needing it
3. Create the corresponding code block
parts/global/text-image-split.php
<?php
/*
* Text/Image Split
*
* Template part used on various/templates views
*
* @package F1 Project Name
* @author Factor1 Studios
* @since 0.0.1
*/
// Text/Image Split Custom Fields
$layout = get_field('text_image_split_layout'); // img on left or right
$image = get_field('text_image_split_image');
$img = wp_get_attachment_image_src($image, 'text_image_split');
$alt = get_post_meta($image, '_wp_attachment_image_alt', true);
$content = get_field('text_image_split_content');
$btnToggle = get_field('text_image_split_button_toggle');
$btn = get_field('text_image_split_button');
// Conditional classes
// Use Ginger Grid's "row--reverse" class to handle image positioning
$rowClass = $layout == 'right' ? ' row--reverse' : ''; ?>
<section class="text-image-split">
<div class="container">
<div class="row<?php echo $rowClass; ?>">
<?php // Image ?>
<div class="col-6 sm-col-11 sm-col-centered">
<img src="<?php echo $img[0]; ?>" alt="<?php echo $alt; ?>">
</div>
<?php // Text ?>
<div class="col-6 sm-col-11 sm-col-centered">
<?php echo $content;
// Optional button
if( $btnToggle ) : ?>
<a href="<?php echo esc_url($btn['url']); ?>" class="button" role="link" title="<?php echo $btn['title']; ?>">
<?php echo $btn['title']; ?>
</a>
<?php endif; ?>
</div>
</div>
</div>
</section>
4. Apply it to the templates/views needed
templates/home.php
<?php
/*
* Template Name: Home
*
* Template used on the home page
*
* @package F1 Project Name
* @author Factor1 Studios
* @since 0.0.1
*/
get_header();
...
get_template_part('parts/global/text-image-split');
...
get_footer(); ?>
- Depending on how this block is used, you may need to add conditional logic to ensure WordPress is looking for the appropriate instances of the fields. For example, if you use it in an ACF repeater or flexible content section, it will need to look for sub fields, not just fields.
page.php
<?php
/**
* The default page template.
*
* Used when a default template individual page is queried.
*
* @package F1 Project Name
* @author Factor1 Studios
* @since 0.0.1
*/
get_header();
get_template_part('parts/global/hero');
get_template_part('parts/global/page-sections');
get_footer(); ?>
parts/global/page-sections.php
<?php
/*
* Page Sections
*
* Template part used on various templates/views
*
* @package F1 Project Name
* @author Factor1 Studios
* @since 0.0.1
*/
if( have_rows('page_sections') ) : while( have_rows('page_sections') ) : the_row();
if( get_row_layout() == 'text_image_split' ) :
get_template_part('parts/global/text-image-split');
endif;
endwhile; endif; ?>
parts/global/text-image-split.php
<?php
/*
* Text/Image Split
*
* Template part used on various/templates views
*
* @package F1 Project Name
* @author Factor1 Studios
* @since 0.0.1
*/
// Check if default template
$isDefault = is_page() && !is_page_template();
// Text/Image Split Custom Fields
// Here we use ternary operators to ensure WP is looking for sub fields on the default template
$layout = $isDefault ? get_sub_field('text_image_split_layout') : get_field('text_image_split_layout'); // img on left or right
$image = $isDefault ? get_sub_field('text_image_split_image') : get_field('text_image_split_image');
...
$content = $isDefault ? get_sub_field('text_image_split_content') : get_field('text_image_split_content');
$btnToggle = $isDefault ? get_sub_field('text_image_split_button_toggle') : get_field('text_image_split_button_toggle');
$btn = $isDefault ? get_sub_field('text_image_split_button') : get_field('text_image_split_button');
...
?>
- A note about ternary operators: We like them. Within reason. If you have more than 3 conditions, opt for the standard PHP if/else blocks or switch statements instead of ternary operators to make sure the code is easy to understand.
<?php
/*
* Text/Image Split
*
* Template part used on various/templates views
*
* @package F1 Project Name
* @author Factor1 Studios
* @since 0.0.1
*/
// Check if default template
$isDefault = is_page() && !is_page_template();
// Check if blog
$isBlog = is_home();
// Bad Bad Bad
// Text/Image Split Custom Fields
$layout = $isDefault ? get_sub_field('text_image_split_layout') : ($isBlog ? get_field('text_image_split_layout', get_option('page_for_posts)) : get_field('text_image_split_layout')); // img on left or right
$image = $isDefault ? get_sub_field('text_image_split_image') : ($isBlog ? get_field('text_image_split_image', get_option('page_for_posts)) : get_field('text_image_split_image'));
...
// Good Good Good
// Text/Image Split Custom Fields
if( $isDefault ) :
$layout = get_sub_field('text_image_split_layout');
$image = get_sub_field('text_image_split_image');
elseif( $isBlog ) :
$layout = get_field('text_image_split_layout', get_option('page_for_posts'));
$image = get_field('text_image_split_image', get_option('page_for_posts'));
else :
$layout = get_field('text_image_split_layout');
$image = get_field('text_image_split_image');
endif; ?>
Wherever possible, we prefer to create ACF groups by template. While it’s possible to create a field group for only a section and set it to appear on a template or page, this increases confusion for both developers and clients and should be avoided as much as possible. Kubra, for example, is built this way:
Bad:
Instead, Factor1 standards are to create field groups by template, and use tab fields to keep things organized (modularization also comes into play in this setup - see notes on modularization above)
To see this in action, look at sites such as CT Group or UPG.
ACF Options pages are a great place to store random fields for things like the header, footer, global options/information, etc.
They are also a good option for custom fields that appear on CPT archive views (since those don’t live on a WP page).
Make sure these are organized in a way that makes sense to the client, have appropriate page titles and icons, etc.
<?php
/*
* ACF
*
* @package F1 Project Name
* @author Factor1 Studios
* @since 0.0.1
*/
// Options pages
if( function_exists('acf_add_options_page') ) {
acf_add_options_page( array(
'page_title' => 'Mega Menu',
'icon_url' => 'dashicons-menu',
'position' => 4,
) );
acf_add_options_page( array(
'page_title' => 'Newsroom (Press Archive)',
'icon_url' => 'dashicons-megaphone',
'position' => 8,
) );
acf_add_options_page( array(
'page_title' => 'Communities Archive View',
'icon_url' => 'dashicons-admin-multisite',
'position' => 21,
) );
} ?>
There are also some great add-on plugins for ACF that we can use on F1 sites. A common and recommended one we use all the time is the ACF Gravity Forms add-on.
This plugin creates a dropdown menu of all the forms on the site, allowing clients to choose the form they want to display and saving us developers from making a code change every time they change their minds about which form to display.
<?php
/*
* Form Section
*
* Template part used on the contact template
*
* @package F1 Project Name
* @author Factor1 Studios
* @since 0.0.1
*/
// Form Section Custom Fields
$form = get_field('contact_form'); ?>
<section class="form-section">
<div class="container">
<div class="row">
<div class="col-10 sm-col-10 col-centered">
<?php echo gravity_form($form['id'], false, false, false, false, true); ?>
</div>
</div>
</div>
</section>
At the time of this writing (12/2019), WP Multilang does not read/translate the ACF Link field. If you are working on a site that is using WP Multilang, create buttons and links the old F1 way instead to ensure proper translation.
<?php // Button Fields
$btnType = get_field('button_type');
$btnInt = get_field('button_internal');
$btnExt = get_field('button_external');
$btnLink = $btnType == 'internal' ? $btnInt : $btnExt;
$btnText = get_field('button_text'); ?>
<a href="<?php echo $btnLink; ?>" class="button button--ghost" role="link" title="<?php echo $btnText; ?>">
<?php echo $btnText; ?>
</a>
See CT Group or Smile NY Dental for examples
Feel free to open an issue if you find mistakes, enhancements, or would like to see some new documentation.