Category Archives: Advanced Custom Fields

Creating dynamic sliders with Soliloquy for free

I wanted to create a simple dynamic slider using Advanced Custom Fields, but Soliloquy does not have this option out of the box. After poking around the source of Soliloquy I found the filter soliloquy_custom_slider_data which lets us add our own slider data. Here is the required code to dynamically generate a slider. (This code is not ACF-specific, you can use any data source.)

Your slider will be available using the shortcode:

[soliloquy type="frontpage"]

The code to generate this dynamic slider:

add_filter('soliloquy_custom_slider_data', function($data, $atts, $post) {

    //Bail early if not our slider type
    if(isset($atts['type']) && $atts['type'] !== 'frontpage') {
        return $data;
    }

    $data_dynamic = [
        'id' => 0,
        'slider' => [
            //This is where you enter all your dynamic slides!
            //72 and 73 below are the IDs of the image attachments used in the slider. 
            72 => [
                'status' => 'active',
                'id' => 72,
                'attachment_id' => 72,
                'title' => 'Image title',
                'link' => 'http://example.com',
                'alt' => 'Alt text',
                'caption' => 'Caption',
                'type' => 'image',
                'linktab' => 0
            ],
            73 => [
                'status' => 'active',
                'id' => 73,
                'attachment_id' => 73,
                'title' => 'Image title',
                'link' => 'http://example.com',
                'alt' => 'Alt text',
                'caption' => 'Caption',
                'type' => 'image',
                'linktab' => 0
            ]
        ],
        'config' => [
            //This is the general slider config
            'type' => 'default',
            'slider_theme' => 'base',
            'slider_width' => 1080,
            'slider_height' => 400,
            'transition' => 'fade',
            'duration' => 5000,
            'speed' => 400,
            'gutter' => 20,
            'slider' => 1,
            'aria_live' => 'polite',
            'classes' => [
                'frontpage-slider'
            ],
            'title' => '',
            'slug' => '',
            'rtl' => 0
        ]
    ];

    return $data_dynamic;
}, 11, 3);

I am using Soliloquy Lite, although I’m sure this works with the paid options as well. You can also easily create multiple dynamic sliders, each pulling data from different sources.

Add custom CSS style to Advanced Custom Fields WYSIWYG field

Example of adding styles from a theme. The code below works on frontend (if you are using acf_form()) as well as in the backend.

add_filter( 'tiny_mce_before_init', function($mce_init) {
  $content_css = get_stylesheet_directory_uri() . '/your-custom-css.css';

  if (isset($mce_init[ 'content_css' ])) {
    $mce_init[ 'content_css' ] = "{$mce_init['content_css']},{$content_css}";
  }

  return $mce_init;
});

Source

Building the right administration interface for every user

WordPress Stockholm meetup group, January 2017

When building websites a lot of work is put into the frontend of the site – with good reason! But often the experience for content editors and site administrators isn’t given as much thought. However WordPress has a wide array of tools to easily make great admin interfaces.

In this talk we’ll take a look at the ecosystem of administration tools available for WordPress, as well as the different types of users that are likely to use the backend of the sites you build.

Slides

Dynamically populating Advanced Custom Fields values

Populating fields dynamically in ACF is simple using the acf/load_field hook. An example:

//Populate the Select field field_5587fccf24f38 with dynamic values
add_filter('acf/load_field/key=field_5587fccf24f38', function($field) {
  //These can be dynamically generated using any PHP code
  $field['choices']['one'] = 'Choice one';
  $field['choices']['two'] = 'Choice two';

  return $field;
});

But if you also use ACF Local JSON, you’ll notice that your dynamic field values get exported whenever you re-resave one of your field groups. Not ideal.

Using dynamic values with ACF Local JSON

We need a way to remove the dynamic values just before they are exported to Local JSON. Luckily, there’s an undocumented filter called acf/prepare_field_for_export that will let us do this! Example:

//Don't export dynamic values via Local JSON
add_filter('acf/prepare_field_for_export', function($field) {

  //If we're at the correct field
  if(isset($field['key']) && $field['key'] === 'field_5587fccf24f38') {

    //Blank out the select options with an empty array
    $field['choices'] = array();
  }

  return $field;
});

Updated 2016-01-15
There was another filter called acf/prepare_fields_for_export (note fields instead of field). This filter was removed in ACF 5.3.3.

Determine if WordPress AJAX request is a backend of frontend request

AJAX endpoints being natively supported in WordPress is awesome. Not being able to separate a frontend request (coming from a theme/frontend plugin) from a backend request (coming from the admin panel) is not as awesome.

Here’s a function to determine if a request is coming from the frontend or backend. It uses the HTTP referer to determine which URL the incoming call to admin-ajax.php originated from. If it originated from a /wp-admin/ url, we know for sure that it was a backend request.

function request_is_frontend_ajax()
{
  $script_filename = isset($_SERVER['SCRIPT_FILENAME']) ? $_SERVER['SCRIPT_FILENAME'] : '';

  //Try to figure out if frontend AJAX request... If we are DOING_AJAX; let's look closer
  if((defined('DOING_AJAX') && DOING_AJAX))
  {
          //From wp-includes/functions.php, wp_get_referer() function.
          //Required to fix: https://core.trac.wordpress.org/ticket/25294
          $ref = '';
          if ( ! empty( $_REQUEST['_wp_http_referer'] ) )
              $ref = wp_unslash( $_REQUEST['_wp_http_referer'] );
          elseif ( ! empty( $_SERVER['HTTP_REFERER'] ) )
              $ref = wp_unslash( $_SERVER['HTTP_REFERER'] );

    //If referer does not contain admin URL and we are using the admin-ajax.php endpoint, this is likely a frontend AJAX request
    if(((strpos($ref, admin_url()) === false) && (basename($script_filename) === 'admin-ajax.php')))
      return true;
  }

      //If no checks triggered, we end up here - not an AJAX request.
      return false;
}

Usage

if(request_is_frontend_ajax()) {
    //Do something only on frontend ajax
}

Store Advanced Custom Field Local JSON in a plugin

Do you use the ACF Local JSON feature? You should!

Only bad part is, tying custom fields to a theme isn’t optimal, which I’ve spoken about earlier.

Here’s a quick way to put your ACF JSON field groups inside a plugin!

<?php
/*
Plugin Name: ACF Local JSON plugin
Plugin URI: 
Description: Put this file in a plugin folder and create an /acf directory inside the plugin, ie: /my-plugin/acf
Author: khromov
Version: 0.1
*/

//Change ACF Local JSON save location to /acf folder inside this plugin
add_filter('acf/settings/save_json', function() {
    return dirname(__FILE__) . '/acf';
});

//Include the /acf folder in the places to look for ACF Local JSON files
add_filter('acf/settings/load_json', function($paths) {
    $paths[] = dirname(__FILE__) . '/acf';
    return $paths;
});

After you create the main plugin file, make sure to create an empty folder called acf as well in the plugin root – that’s where your local JSON files will be saved.

Escaping and securing Advanced Custom Fields output

Do you use get_field() to output values saved in Advanced Custom Field in your theme or plugin without escaping it first? Maybe something like this?

echo get_field('my_field');

If so, your site is likely vulnerable to cross-site scripting attacks (XSS) and other malicious hijacking by users who have access to your ACF forms! (Through the admin backend or frontend, if using acf_form())

ACF author Elliot Condon is not completely sure whether ACF escapes input when saving posts to database. However, it doesn’t.

Who is affected by this?

The issue arises from potentially harmful data collected on the front or backend using ACF forms, that is then displayed without proper escaping.

If you do not use frontend forms via acf_form()
In this case you are not vulnerable to unauthenticated frontend attacks. However, anyone who has access to /wp-admin and can edit ACF forms that are output on the front or backend, can introduce XSS vulnerabilities unless you escape the output.

Aside from Administrator accounts getting hacked (in which case you are probably not in a great position anyway), you may also have less privileged users that still have access to ACF forms. (For example, using Author or Editor roles.). For these users, /wp-admin is not an “admin” panel in the traditional sense, and again, it would take for only one of these users to have his credentials stolen or brute forced to bring down your site.

Once the attacker logs in, he can input javascript code into any text-based fields, doing things like redirecting users to malicious sites, steal passwords, or collect data about your site and users.

If you use frontend forms and let unauthenticated users fill them out
In this case, you need to make sure that you escape the output everywhere, be it front or backend.

Now that we’ve cleared this out – onwards to the solution!

ACF 4 vs ACF 5 / ACF PRO

In ACF 4, you can escape text and textfield fields by using get_field() and setting “Formatting: No Formatting” when configuring the fields. In the upcoming ACF 5 (and ACF PRO), this has been removed and replace with a format_value filter. Unfortunately, this filter does not let you target an individual field. In that case you have to escape data on the frontend.

Securing Advanced Custom Fields

Luckily, it’s easy to do escaping on the front end with built-in WordPress escaping functions.

I’ve written a handly helper function called get_field_escaped() which you can use instead of get_field() to secure your output.

It takes the same parameters as get_field(), so you can do stuff like:

echo get_field_escaped('my_custom_field');

You can also pass $post_id and $format_value, just like in the regular get_field() function:

echo get_field_escaped('my_custom_field', $post_id, true);

What’s new, a new fourth parameter has been added. It selects which escaping method is used on the data before it is sent back. By default is uses esc_html, which is an all-round function for escaping HTML.

You can easily switch this for any other escaping function that WordPress has, for example here we escape a URL:

<a href="<?php echo get_field_escaped('url', $post_id, true, 'esc_url'); ?>">My link</a>

And here we are escape a HTML attribute:

<div class="<?php get_field_escaped('custom_css_class', $post_id, true, 'esc_attr'); ?>">My div</div>

You can also pass NULL as the fourth parameter instead of the function name to skip the escaping. In that case it works exactly as get_field():

echo get_field_escaped('my_field', $post_id, true, NULL);

And the function itself:

/**
 * Helper function to get escaped field from ACF
 * and also normalize values.
 *
 * @param $field_key
 * @param bool $post_id
 * @param bool $format_value
 * @param string $escape_method esc_html / esc_attr or NULL for none
 * @return array|bool|string
 */
function get_field_escaped($field_key, $post_id = false, $format_value = true, $escape_method = 'esc_html')
{
    $field = get_field($field_key, $post_id, $format_value);

    /* Check for null and falsy values and always return space */
    if($field === NULL || $field === FALSE)
        $field = '';

    /* Handle arrays */
    if(is_array($field))
    {
        $field_escaped = array();
        foreach($field as $key => $value)
        {
            $field_escaped[$key] = ($escape_method === NULL) ? $value : $escape_method($value);
        }
        return $field_escaped;
    }
    else
        return ($escape_method === NULL) ? $field : $escape_method($field);
}

While we’re at it, let’s add the_field_escaped() as well in case we want to print the value directly.

/**
 * Wrapper function for get_field_escaped() that echoes the value isntead of returning it.
 *
 * @param $field_key
 * @param bool $post_id
 * @param bool $format_value
 * @param string $escape_method esc_html / esc_attr or NULL for none
 */
function the_field_escaped($field_key, $post_id = false, $format_value = true, $escape_method = 'esc_html')
{
    //Get field
    $value = get_field_escaped($field_key, $post_id, $format_value, $escape_method );

    //Print arrays as comma-separated strings, as per get_field() behaviour.
    if( is_array($value) )
    {
        $value = @implode( ', ', $value );
    }

    //Echo result
    echo $value;
}

That lets us do:

//Prints the value
the_field_escaped('my_field', $post_id, true, NULL);

XSS self-test

If you want to perform an XSS self-test, you can enter the following code in all your ACF text fields:

<script>alert('pwned')</script>

Then, visit the frontend on your site. If you see something similar to this message, you are susceptible to XSS:

xss

Other resources

Validating, Sanitizing, and Escaping, from WordPress VIP

Integrate Advanced Custom Fields Gallery field into existing themes and slider plugins easily

If you are using ACF Gallery fields, there’s a quick trick to showing them to the end users by utilizing the existing [gallery] shortcode, which most themes have great support for.

If your field name is “gallery_field”, use this function in your theme where you want to display the gallery:

if(get_field('gallery_field') !== '')
{
    $gallery_items_string = '';
    foreach(get_field('gallery_field') as $gallery_item)
        $gallery_items_string .= $gallery_item['id'] . ',';

    echo do_shortcode('[gallery link="file" columns="4" ids="'. $gallery_items_string .'"]');
}

Bonus – Integration with slider plugins:

Now that we’ve integrated with the shortcode, let’s move on to sliders! Below we have an example of an Owl Carousel integration, provided by by Corbin from Commandbase!

$images = get_field('portfolio_gallery');

if( $images ): ?>
    <ul class="portfolio-slider">
        <?php foreach( $images as $image ): ?>
            <li>
                <img src="<?php echo $image['sizes']['large']; ?>" alt="<?php echo $image['alt']; ?>" />
            </li>
        <?php endforeach; ?>
    </ul>
<?php endif; ?>