Skip to content

Instantly share code, notes, and snippets.

@danielpost
Last active October 30, 2018 11:00
Show Gist options
  • Save danielpost/d30a3e1a5f1020061b752313251edbbd to your computer and use it in GitHub Desktop.
Save danielpost/d30a3e1a5f1020061b752313251edbbd to your computer and use it in GitHub Desktop.
Simplify ACF fields

Turns this:

[
    'subtitle',
    'video' => [
        'type' => 'oembed',
    ],
    'quote',
    'quote_author',
    'deadline' => [
        'type' => 'date_picker'
    ]
]

Into this:

array(6) {
  ["label_placement"]=>
  string(4) "left"
  ["active"]=>
  int(1)
  ["location"]=>
  array(1) {
    [0]=>
    array(1) {
      [0]=>
      array(3) {
        ["param"]=>
        string(9) "post_type"
        ["operator"]=>
        string(2) "=="
        ["value"]=>
        string(4) "page"
      }
    }
  }
  ["fields"]=>
  array(5) {
    [0]=>
    array(4) {
      ["name"]=>
      string(15) "castle_subtitle"
      ["key"]=>
      string(36) "field_castle_metaboxes_home_subtitle"
      ["type"]=>
      string(4) "text"
      ["label"]=>
      string(8) "Subtitle"
    }
    [1]=>
    array(4) {
      ["type"]=>
      string(6) "oembed"
      ["name"]=>
      string(12) "castle_video"
      ["key"]=>
      string(33) "field_castle_metaboxes_home_video"
      ["label"]=>
      string(5) "Video"
    }
    [2]=>
    array(4) {
      ["name"]=>
      string(12) "castle_quote"
      ["key"]=>
      string(33) "field_castle_metaboxes_home_quote"
      ["type"]=>
      string(4) "text"
      ["label"]=>
      string(5) "Quote"
    }
    [3]=>
    array(4) {
      ["name"]=>
      string(19) "castle_quote_author"
      ["key"]=>
      string(40) "field_castle_metaboxes_home_quote_author"
      ["type"]=>
      string(4) "text"
      ["label"]=>
      string(12) "Quote Author"
    }
    [4]=>
    array(4) {
      ["type"]=>
      string(11) "date_picker"
      ["name"]=>
      string(15) "castle_deadline"
      ["key"]=>
      string(36) "field_castle_metaboxes_home_deadline"
      ["label"]=>
      string(8) "Deadline"
    }
  }
  ["key"]=>
  string(21) "castle_metaboxes_home"
  ["title"]=>
  string(8) "Homepage"
}
<?php
namespace Drawbridge\Metaboxes;
class ACFConverter
{
/**
* Options to be converted to an ACF metabox.
*
* @var array
*/
protected $options = [];
/**
* Prefix to be added to the post meta key.
*
* @var string
*/
protected $prefix = 'castle';
/**
* @param array $options Options to be converted to an ACF metabox.
*/
public function __construct(array $options = [])
{
if (empty($options['key'])) {
wp_die(__('The ACF metabox can\'t be registered without a key.', 'acf-php'));
}
$this->setOptions($options);
}
/**
* @return array
*/
public function convert()
{
return $this->convertFields();
}
/**
* @param array $options
*/
protected function setOptions($options)
{
$this->options = $options;
}
/**
* @return array
*/
protected function getOptions()
{
return $this->options;
}
/**
* @param mixed $location
*/
protected function setLocation($location)
{
$this->options['location'] = $location;
}
/**
* @return mixed
*/
protected function getLocation()
{
return $this->options['location'] ?? [];
}
/**
* @param array $fields
*/
protected function setFields($fields)
{
if (!is_array($fields)) {
return;
}
$this->options['fields'] = $fields;
}
/**
* @return array
*/
protected function getFields()
{
return $this->options['fields'];
}
/**
* Set default options before the meta boxes are created.
*/
protected function setDefaultOptions()
{
$options = $this->getOptions();
/**
* Set default options.
*/
$options = array_merge(
[
'label_placement' => 'left',
'active' => 1,
'location' => [
[
[
'param' => 'post_type',
'operator' => '==',
'value' => 'post',
],
],
],
'fields' => [],
],
$options
);
/**
* `after_title` is an alias for `acf_after_title`.
*/
if (isset($options['position']) && $options['position'] === 'after_title') {
$options['position'] = 'acf_after_title';
}
$this->setOptions($options);
}
/**
* Convert location if necessary.
*/
protected function convertLocation()
{
$location = $this->getLocation();
/**
* If the location entered in shorthand declaration, we need to convert it.
*
* Example: 'location' => 'post_type == page'
*/
if (is_string($location)) {
$location = explode(' ', $location);
/**
* Throw an error if the location entered is not in the correct syntax.
*/
if (count($location) !== 3) {
wp_die(__('The location entered for the metabox is incorrect.', 'acf-php'));
}
/**
* Convert location to ACF-compatible format.
*/
$location = [
[
[
'param' => $location[0],
'operator' => $location[1],
'value' => $location[2],
],
],
];
}
$this->setLocation($location);
}
/**
* Convert conditional logic
*
* @param array $field Field options
* @return array $field Field with conditional logic added
*/
protected function convertConditionalLogic($field)
{
/**
* First, make sure conditional logic is definitely entered.
*/
if (empty($field['conditional_logic'])) {
return;
}
/**
* Get options for key.
*/
$options = $this->getOptions();
$key = $options['key'];
$conditional = $field['conditional_logic'];
/**
* If it's an array (ACF-compatible version), add the ACF
* `field_` prefix and metabox key to each `field` declaration.
*/
if (is_array($conditional)) {
foreach ($conditional as $i => $outer) {
foreach ($outer as $j => $inner) {
$fieldValue = $conditional[$i][$j]['field'];
/**
* Only add `field_` and metabox key if the value
* doesn't start with `field_` already.
*/
if (strpos($fieldValue, 'field_') !== 0) {
$field['conditional_logic'][$i][$j]['field'] = 'field_' . $key . '_' . $fieldValue;
}
}
}
}
/**
* If it's the shorthand declaration, make it ACF-compatible.
* Same logic as used in convertLocation().
*
* Example: 'conditional_logic' => 'some_checkbox == 1'
*/
if (is_string($conditional)) {
$conditional = explode(' ', $conditional);
/**
* Throw an error if the conditional logic entered is not in the correct syntax.
*/
if (count($conditional) !== 3) {
wp_die(__('The conditional logic entered is incorrect.', 'acf-php'));
}
/**
* Check if `field_` has already been added.
*/
if (strpos($conditional[0], 'field_') !== 0) {
$conditional[0] = 'field_' . $key . '_' . $conditional[0];
}
/**
* Convert conditional logic to ACF-compatible format.
*/
$conditional = [
[
[
'field' => $conditional[0],
'operator' => $conditional[1],
'value' => $conditional[2],
],
],
];
$field['conditional_logic'] = $conditional;
}
return $field;
}
/**
* Set field defaults
*
* @param array $field Field options
* @param string $fieldName Name of the field
* @param bool $isLayout True if the field is a Flexible Content layout
* @return array $field Field with defaults added
*/
protected function setDefaults($field, $fieldName, $isLayout = false)
{
/**
* Default tab placement is `left`.
*/
if (isset($field['type']) && $field['type'] === 'tab' && !isset($field['placement'])) {
$field['placement'] = 'left';
}
/**
* Default for sub_fields is repeater.
*/
if (!$isLayout && !isset($field['type']) && isset($field['sub_fields'])) {
$field['type'] = 'repeater';
}
/**
* Default Repeater layout is `block`.
*/
if (isset($field['type']) && $field['type'] === 'repeater' && !isset($field['layout'])) {
$field['layout'] = 'block';
}
/**
* Default Flexible Content display is `row`.
*/
if ($isLayout && !isset($field['display'])) {
$field['display'] = 'row';
}
/**
* If no `type` is set and and it's not a Flexible Content layout,
* set `type` to `text`.
*/
if (!$isLayout && !isset($field['type'])) {
$field['type'] = 'text';
}
/**
* If no label is given, automatically generate label based
* on the field name.
*/
if (!isset($field['label'])) {
$field['label'] = esc_html(ucwords(str_replace('_', ' ', $fieldName)));
}
/**
* Escaping for labels and instructions.
*/
if (isset($field['label'])) {
$field['label'] = esc_html($field['label']);
}
if (isset($field['instructions'])) {
$field['instructions'] = esc_html($field['instructions']);
}
/**
* Sensible escaping for messages.
*/
if (isset($field['type']) && $field['type'] === 'message') {
$field['esc_html'] = 0;
$field['message'] = wp_kses_post($field['message']);
}
return $field;
}
/**
* Convert field (add key, name and optional settings to fields).
*
* @param array $field Field options
* @param string $fieldName Name of the field
* @param string $parentKey Optional: key of the parent field
* @param bool $isLayout True if the field is a Flexible Content layout
* @return array $field Converted field
*/
protected function convertField($field, $fieldName, $parentKey = '', $isLayout = false)
{
/**
* If the field is empty, don't do anything.
*/
if (empty($field)) {
return;
}
/**
* Get options. Used to get the key of the metabox, which we
* use to prefix the keys of the individual fields.
*/
$options = $this->getOptions();
/**
* Custom prefix, added to the name of top-level fields.
*/
$prefix = (!empty($this->prefix) && empty($parentKey)) ? $this->prefix . '_' : '';
/**
* If the user only passes a string, this field becomes a text field,
* and the name of the field is the string passed.
*
* PHP automatically changes the key to an integer and assigns the string
* to the value, which is what we use to check for this scenario.
*/
if (is_int($fieldName) && is_string($field)) {
$fieldName = $field;
}
/**
* If the field is not an array, turn it into an empty array.
*/
if (!is_array($field)) {
$field = [];
}
/**
* Set field name.
*/
$field['name'] = $prefix . $fieldName;
/**
* Set field key.
*/
$key = !empty($parentKey) ? $parentKey . '_' . $fieldName : $fieldName;
/**
* Check if the field has sub fields. If so, it's either a Repeater field
* or a Flexible Content field layout. We convert all sub fields.
*/
if (!empty($field['sub_fields']) && is_array($field['sub_fields'])) {
$field['sub_fields'] = array_map(function ($name, $field) use ($key) {
return $this->convertField($field, $name, $key);
}, array_keys($field['sub_fields']), $field['sub_fields']);
}
/**
* Check if the field is a Flexible Content field. If so, convert all
* layouts.
*/
if (!empty($field['layouts']) && is_array($field['layouts'])) {
$field['layouts'] = array_map(function ($name, $field) use ($key) {
return $this->convertField($field, $name, $key, true);
}, array_keys($field['layouts']), $field['layouts']);
}
/**
* Convert conditional logic if necessary.
*/
if (!empty($field['conditional_logic'])) {
$field = $this->convertConditionalLogic($field);
}
/**
* After converting all possible sub fields, add `field_` and options
* key to the key of the field.
*/
if (!isset($field['key'])) {
$field['key'] = 'field_' . $options['key'] . '_' . $key;
}
/**
* Set defaults.
*/
return $this->setDefaults($field, $fieldName, $isLayout);
}
/**
* Convert fields.
*/
protected function convertFields()
{
if (!is_admin() || !class_exists('ACF') || empty($this->getOptions())) {
return;
}
$fields = $this->getFields();
$this->setDefaultOptions();
$this->convertLocation();
$convertedFields = array_map(function ($name, $field) {
return $this->convertField($field, $name);
}, array_keys($fields), $fields);
$this->setFields($convertedFields);
return $this->getOptions();
}
}
<?php
namespace Castle\Metaboxes;
use Drawbridge\Metaboxes\Metabox;
class Home extends Metabox
{
public static function title()
{
return esc_html__('Homepage', 'castle');
}
public static function location()
{
return 'post_type == page';
}
public static function fields()
{
return [
'subtitle',
'video' => [
'type' => 'oembed',
],
'quote',
'quote_author',
'deadline' => [
'type' => 'date_picker'
]
];
}
}
<?php
namespace Drawbridge\Metaboxes;
use Drawbridge\Exceptions\AdvancedCustomFieldsNotInstalledException;
use Drawbridge\Exceptions\MetaboxRegistrationException;
use Drawbridge\Metaboxes\ACFConverter;
class Metabox
{
public static function key()
{
return strtolower(str_replace('\\', '_', static::class));
}
public static function title()
{
return ucwords(str_replace('_', ' ', static::key()));
}
public static function location()
{
return 'post_type == post';
}
public static function fields()
{
return null;
}
final public static function register()
{
if (!function_exists('acf_add_local_field_group')) {
throw new AdvancedCustomFieldsNotInstalledException();
}
$key = static::key();
$title = static::title();
$location = static::location();
$fields = static::fields();
if (empty($key)) {
throw new MetaboxRegistrationException('Key not set');
}
if (empty($fields)) {
throw new MetaboxRegistrationException('Fields not set');
}
$options = [
'key' => $key,
'location' => $location,
'title' => $title,
'fields' => $fields
];
$converter = new ACFConverter($options);
acf_add_local_field_group($converter->convert());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment