Using ACF Builder with Sage

Originally published at: https://roots.io/guides/using-acf-builder-with-sage/

Introduction Advanced Custom Fields has quickly become a staple in WordPress theme development. It is incredibly powerful for handling custom field data and is jampacked with just about every field type your heart could desire. If it doesn’t have a specific field type you need for your project, it is extremely likely that it is…

11 Likes

Hello. I’m trying hard to follow this but I don’t believe collect(glob(config('theme.dir').'/app/fields/*.php')) is collecting an array of PHP files in the fields folder, it’s remaining empty.

I’m trying to use Visual Studio Code to debug but have never used this before so I’m not doing very well and am struggling to figure out where I’m going wrong. I would be very grateful for some help.

I used print_r(‘theme.dir’) and managed to glean the following (edited to change the domain and theme name:
theme.dir
xdebug://debug-eval:1:
array (size=2)
‘dir’ => string ‘/srv/www/mysite.com/current/web/app/themes/my-site’ (length=77)
‘uri’ => string ‘http://mysite.test/app/themes/my-site’ (length=64)


By the way, I also noticed

<?php

namespace App;

use StoutLogic\AcfBuilder\FieldsBuilder;

$builder = new FieldsBuilder('builder');

$builder
    ->addTab('builder', ['placement' => 'left'])
        ->addFlexibleContent('components', 'button_label' => 'Add Component'])
            ->addLayout(get_field_partial('components.button'));

return $builder;

has a typo in it, the addFlexibleContent line should be:

`->addFlexibleContent('components', ['button_label' => 'Add Component'])`

Hi.

Good catch on the typo, I will fix that shortly.

What is print_r(glob(config('theme.dir').'/app/fields/*.php')) returning, assuming you have a PHP file of some kind in your app/fields folder?

Hi. Thanks for replying. I cannot for the life of me get anything to show for print_r(glob(config('theme.dir').'/app/fields/*.php')). It just says ‘error evaluating code’. I’m really not familiar with debugging in Visual Studio Code and so I’m not quite sure how to use it.

I’ve definitely got PHP files in my app/fields folder. I have:
app/fields/builder.php
app/fields/general.php
app/fields/header.php
app/fields/page.php
app/fields/post.php
app/fields/partials/components/button.php

Let’s skip the VSCode debugging for now and try @php(var_dump(glob(\App\config('theme.dir').'/app/fields/*.php'))) in one of your views such as header.blade.php just to see if it fetches them.

To verify, you are adding the collect() snippet from my guide into setup.php as shown in the guide (within an init action) and including the use StoutLogic\AcfBuilder\FieldsBuilder namespace?

Also, for the return on config('theme.dir') that you showed above– is that the proper path to your theme? I’m assuming it just differs from the URI due to you editing the domain name before pasting.

If none of the above leads you anywhere, feel free to private message me and I can help you debug further as all should be working. If you have a private repo with your theme, you can also grant me access and I can take a look for you.

Thanks for getting back to me so quickly!

I’ve added @php(var_dump(glob(\App\config('theme.dir').'/app/fields/*.php'))) into header.blade.php and it returns:

/srv/www/mysite.com/current/web/app/uploads/cache/818bba1b3e7e1a72a2bd85fa2fa1f0f1ad667da6.php:3: array (size=0) empty

My full setup.php is:

<?php

namespace App;

use Roots\Sage\Container;
use Roots\Sage\Assets\JsonManifest;
use Roots\Sage\Template\Blade;
use Roots\Sage\Template\BladeProvider;
use StoutLogic\AcfBuilder\FieldsBuilder;

/**
 * Theme assets
 */
add_action('wp_enqueue_scripts', function () {
wp_enqueue_style('sage/main.css', asset_path('styles/main.css'), false, null);
wp_enqueue_script('sage/main.js', asset_path('scripts/main.js'), ['jquery'], null, true);

if (is_single() && comments_open() && get_option('thread_comments')) {
    wp_enqueue_script('comment-reply');
}
}, 100);

/**
 * Theme setup
 */
add_action('after_setup_theme', function () {
/**
 * Enable features from Soil when plugin is activated
 * @link https://roots.io/plugins/soil/
 */
add_theme_support('soil-clean-up');
add_theme_support('soil-jquery-cdn');
add_theme_support('soil-nav-walker');
add_theme_support('soil-nice-search');
add_theme_support('soil-relative-urls');

/**
 * Enable plugins to manage the document title
 * @link https://developer.wordpress.org/reference/functions/add_theme_support/#title-tag
 */
add_theme_support('title-tag');

/**
 * Register navigation menus
 * @link https://developer.wordpress.org/reference/functions/register_nav_menus/
 */
register_nav_menus([
    'primary_navigation' => __('Primary Navigation', 'sage')
]);

/**
 * Enable post thumbnails
 * @link https://developer.wordpress.org/themes/functionality/featured-images-post-thumbnails/
 */
add_theme_support('post-thumbnails');

/**
 * Enable HTML5 markup support
 * @link https://developer.wordpress.org/reference/functions/add_theme_support/#html5
 */
add_theme_support('html5', ['caption', 'comment-form', 'comment-list', 'gallery', 'search-form']);

/**
 * Enable selective refresh for widgets in customizer
 * @link https://developer.wordpress.org/themes/advanced-topics/customizer-api/#theme-support-in-sidebars
 */
add_theme_support('customize-selective-refresh-widgets');

/**
 * Use main stylesheet for visual editor
 * @see resources/assets/styles/layouts/_tinymce.scss
 */
add_editor_style(asset_path('styles/main.css'));
}, 20);

/**
 * Register sidebars
 */
add_action('widgets_init', function () {
$config = [
    'before_widget' => '<section class="widget %1$s %2$s">',
    'after_widget'  => '</section>',
    'before_title'  => '<h3>',
    'after_title'   => '</h3>'
];
register_sidebar([
    'name'          => __('Primary', 'sage'),
    'id'            => 'sidebar-primary'
] + $config);
register_sidebar([
    'name'          => __('Footer', 'sage'),
    'id'            => 'sidebar-footer'
] + $config);
});

/**
 * Updates the `$post` variable on each iteration of the loop.
 * Note: updated value is only available for subsequently loaded views, such as partials
 */
add_action('the_post', function ($post) {
sage('blade')->share('post', $post);
});

/**
 * Setup Sage options
 */
add_action('after_setup_theme', function () {
/**
 * Add JsonManifest to Sage container
 */
sage()->singleton('sage.assets', function () {
    return new JsonManifest(config('assets.manifest'), config('assets.uri'));
});

/**
 * Add Blade to Sage container
 */
sage()->singleton('sage.blade', function (Container $app) {
    $cachePath = config('view.compiled');
    if (!file_exists($cachePath)) {
        wp_mkdir_p($cachePath);
    }
    (new BladeProvider($app))->register();
    return new Blade($app['view']);
});

/**
 * Create @asset() Blade directive
 */
sage('blade')->compiler()->directive('asset', function ($asset) {
    return "<?= " . __NAMESPACE__ . "\\asset_path({$asset}); ?>";
});
});

/**
 * Initialize ACF Builder
 */
add_action('init', function () {
collect(glob(config('theme.dir').'/app/fields/*.php'))->map(function ($field) {
    return require_once($field);
})->map(function ($field) {
    if ($field instanceof FieldsBuilder) {
        acf_add_local_field_group($field->build());
    }
});
});

Yes, the return on config('theme.dir') is the proper path to my theme, I’ve just edited the domain name.

For anyone reading any of the above, I gave @Log1x access to my repository and he could see I’d got my directory structure wrong. I had:

app/fields/builder.php
app/fields/general.php
app/fields/header.php
app/fields/page.php
app/fields/post.php
app/fields/partials/components/button.php

but it should have been:

app/fields/components/button.php
app/fields/partials/builder.php
app/fields/partials/general.php
app/fields/partials/header.php
app/fields/page.php
app/fields/post.php

Thanks again @Log1x!

Is it possible to add custom acf field types with ACF Builder? For example, the ACF image crop field https://wordpress.org/plugins/acf-image-crop-add-on/

Use the name of the field along with ->addField('key', 'field_type')

So in this case, it is image_crop which can then be used with ->addField('my_image_crop', 'image_crop').

You can find field settings here for image-crop in particular and add them after the field_type in an array.

An alternate option would be to add a test field in ACF’s field editor and then export it as PHP so you can find the key and available config values to then use with ACF Builder.

I’m open to any PR’s on the ACF Builder Cheatsheet to begin adding high-quality third-party fields.

1 Like

thanks!
The following appears to work:

->addField('background', 'image_crop', [
            'label' => 'Background',
            'target_size' => 'custom',
            'width' => '1600',
            'height' => '400',
            'force_crop' => 'yes',
            'crop_type' => 'hard',
            'preview_size' => 'medium',
            'save_format' => 'id',
            'save_in_media_library' => 'yes',
            'library' => 'all',
            'retina_mode' => 'no'
        ])
            ->setInstructions('background image. If none set the global defaults will be used.');

I’m going to be doing a lot of this so I’ll keep some notes with the intent of doing a PR

Thanks for the tip on exporting as PHP, that is very useful.

Has anyone run into the issue of the field group labels displaying on all posts/pages despite the conditions set using setLocation? I’m currently seeing the following below the Gutenberg panel:

Here’s what’s in my fields/page.php file for example:

<?php

namespace App;

use StoutLogic\AcfBuilder\FieldsBuilder;

$page = new FieldsBuilder('page');

$page
->setLocation('post_type', '==', 'page');
  
$page
->addFields(get_field_partial('partials.general'))
    ->removeField('enable_featured_image')
    
->addFields(get_field_partial('partials.builder'));

return $page;

And here’s what’s in fields/post.php:

<?php

namespace App;

use StoutLogic\AcfBuilder\FieldsBuilder;

$post = new FieldsBuilder('post');

$post
    ->setLocation('post_type', '==', 'post')
      ->and('post_type', '!=', 'page');
  
$post
    ->addFields(get_field_partial('partials.general'))
    ->addFields(get_field_partial('partials.header'));

return $post;

Thanks in advance!

Because of the way Gutenberg has been rushed through, ACF isn’t fully compatible with it yet (same with lots of other tools). See: https://www.advancedcustomfields.com/blog/the-night-before-gutenberg/

3 Likes

Hahahah! That’s it alright. Thanks so much for the quick reply.

1 Like

TL;DR: Looking for a way to reference fields defined in my builder in my views.

Sorry in advance if this is obvious :grimacing: ! I’ve been searching the documentation but haven’t found a way to display the values of fields created with the page builder / flexible content in my templates. For example, I’m pulling a logo into header.blade using the following in my App.php controller:

  public function headerLogo()
  {
    return get_field('header_logo', 'option');
  }

and the following in header.blade.php:

  @if ($header_logo)
    <img src="{{ $header_logo['url'] }}" class="logo" alt="{{ $header_logo['alt'] }}"/>
  @endif

This is working great! What I’m now looking for is a sane way to return all the values attached to fields/page.php. For example, here’s a field group I’ve defined in fields/components/call-to-action.php (which has been included in my builder):

<?php

namespace App;

use StoutLogic\AcfBuilder\FieldsBuilder;

$config = (object) [
    'ui' => 1,
    'wrapper' => ['width' => 50],
];

$callToAction = new FieldsBuilder('Call to Action');

$callToAction
    ->addGroup('Call to Action')
        ->addText('heading', ['wrapper' => $config->wrapper])
          ->setInstructions('Heading for your CTA.')

        ...

    ->endGroup();

return $callToAction;

Thanks for looking!

Hey @zack - take a look at the Controller docs section on ACF here and let us know if that helps get you pointed in the right direction! https://github.com/soberwp/controller/#advanced-custom-fields-module

1 Like

PS - also check out the Blade Debugger and Blade Coder sections of that doc and give them a test. They can be very helpful.

1 Like

My directives package may also be of use for displaying values in your views that do not contain heavy logic.

It has no docs yet (I’ve been swamped)– but have a look at ACF.php.

3 Likes

Definitely a great place to start. Looks like all of my flexible content is stored within an array called “components”. Should probably be fine once I figure out the best way to iterate over it!

Thanks for passing this along. My php wasn’t strong enough to figure out what was happening by reviewing ACF.php alone, but I’ll definitely hang onto this for future reference.

More or less it’s just basic blade directives for ACF.

An example for your Call to Action group being:

@group('call_to_action')
   <h2>@sub('heading')</h2>
   <p>[...]</p>
@endgroup

Same goes for using normal fields, @field('example'), @sub('example'), etc. There are also helpers for handling conditionals like:

@hasfield('example')
   <h2>@field('example')</h2>
@endfield

@field can accept multiple parameters to help with fields that return an array, such as image:

<img src="@field('my_image', 'url')" class="image" alt="@field('my_image', 'alt')" />

That being said, I just realized I forgot to add the above @field functionality (accepting arguments like you need for your header_logo) to @option– I’ll add it shortly followed by actual documentation for each field so people can start making use of these. :slight_smile:

3 Likes