Skip to content

Factor1 ACF Pro Standards

Melanie Rhodes edited this page Oct 13, 2022 · 4 revisions

Operation No Touchy!

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.

No Touchy!

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 );

Flexible Page Sections

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.

Modularization

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

ACF Inactive Field Group

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

ACF Cloned Group on Home Template

ACF Cloned Group on Flexible Content Section

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; ?>

Field Groups by Template

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: Kubra ACF Field Groups

Kubra Admin Result

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)

ACF Template Field Groups

ACF Admin by Template

To see this in action, look at sites such as CT Group or UPG.

Options Pages

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,
  ) );
} ?>

ACF Options Pages

Add Ons

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.

ACF Gravity Forms Plugin Install

ACF Form Field

ACF Form Field Result

<?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>

Known Conflicts with Other Plugins

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.

ACF Cloneable Button Group

<?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