Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More effective version of get_fields() included #12

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [1.2.0-beta] - 2017-12-01

### Added
- Added a more effective version of ACF's get_fields() method to use with the Codifier

## [1.1.3] - 2017-11-27

## Changed
### Changed
- Another small bug fix regarding Group field

## [1.1.2] - 2017-11-27
Expand Down
2 changes: 1 addition & 1 deletion plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Plugin Name: ACF Codifier
Plugin URI: https://github.com/devgeniem/acf-codifier
Description: A helper class to make defining ACF field groups and fields easier in the code.
Version: 1.1.3
Version: 1.2.0-beta
Author: Geniem Oy
Author URI: https://geniem.fi
License: GPLv2
Expand Down
224 changes: 224 additions & 0 deletions src/Codifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,228 @@ public static function get_label_visibility( $field ) {

return isset( self::$hidden_labels[ $key ] );
}

/**
* A more efficient replacement for ACF' native get_fields function
*
* @param int $id Post id to fetch fields from
* @param array $wanted Keys for wanted fields. Empty or null default to all
* @return array Fields as an associative array
*/
public static function get_fields( $id, $wanted = null ) {
global $wpdb;

// Get the raw meta data from the database
$rows = get_post_meta( $id );

// Filter only the keys we want
if ( ! empty( $wanted ) ) {
$rows = array_filter( $rows, function( $key ) {
// Strip possible trailing underscore
if ( substr( $key, 0, 1 ) === '_' ) {
$key = substr( $key, 1 );
}

return in_array( $key, $wanted );
}, ARRAY_FILTER_USE_KEY );
}

// Sort the meta rows so that the ones deeper in the tree come last
if ( ! ( $sorted = wp_cache_get( 'sorted_meta_' . $id ) ) ) {
uksort( $rows, function ( $a, $b ) {

preg_match_all( '/_(\d+)_/', $a, $a_amount );
preg_match_all( '/_(\d+)_/', $b, $b_amount );
$a_a = count( $a_amount[0] );
$b_a = count( $b_amount[0] );

// If the depth is same, sort alphabetically
if ( $a_a === $b_a ) {
return $a <=> $b;
} else {
return $a_a <=> $b_a;
}
} );
wp_cache_set( 'sorted_meta_' . $id, $rows );
}
else {
$rows = $sorted;
}

$original = [];

// Convert to ordinary key-value form
foreach ( $rows as $key => $row ) {
$original[ $key ] = $row[0];
}

// Initiate a few arrays for the future
$times = [];
$fields = [];
$clones = [];
$layouts = [];
$layout_filters = [];

// Loop through all meta values
foreach ( $original as $key => $value ) {

// We don't want to handle meta-meta values for now
if ( $key[0] === '_' ) {
continue;
}

// If a field doesn't have a meta-meta pair, we don't want to include it
if ( ! isset( $original[ '_' . $key ] ) ) {
continue;
}
else {
// Fetch the appropriate field object
$field_key = $original[ '_' . $key ];

if ( ! ( $field = wp_cache_get( $field_key ) ) ) {
$field = acf_get_local_field( $field_key );

// If there is a meta-meta pair but the field doesn't exist anymore,
// continue without returning the data either
if ( ! $field ) {
continue;
}

// ACF needs this for some reason
$field['_name'] = $field['name'];
wp_cache_set( $field_key, $field );
}

// If there have been cloned fields, we need to run a few checks
if ( ! empty( $clones ) ) {
foreach ( $clones as $clone_key => $clone_value ) {
// Clone would be first in the actual key
$clone_key_array = explode( '_', $clone_key );
$clone_key = $clone_key_array[0];

// Does the key start with a known clone key?
if ( substr( $key, 0, strlen( $clone_key .'_' ) ) === $clone_key .'_' ) {
$initial_path = [ $clone_key ];
$path_key = substr( $key, strlen( $clone_key . '_' ) );
}
else {
$path_key = $key;
$initial_path = [];
}
}
}
else {
$path_key = $key;
$initial_path = [];
}

// Split the key for positioning the values in the tree
$path = preg_split( '/_(\d+)_/', $path_key, 0, PREG_SPLIT_DELIM_CAPTURE );

$path = array_merge( $initial_path, $path );

// Create a reference to the spot where the value is supposed to be placed
$value_node =& $fields;

// Reference magic
foreach ( $path as $pkey ) {
$value_node =& $value_node[ $pkey ];
}

// Do field type specific things
switch( $field['type'] ) {
case 'clone':
// Get the cloned field's field object
if ( ! ( $field_object = wp_cache_get( 'local_field_' . $field['key'], 'acf' ) ) ) {
$field_object = acf_get_local_field( $field['key'] );
wp_cache_set( 'local_field_' . $field['key'], $field_object, 'acf' );
}

// Loop through cloned fields and fetch their field objects
foreach ( $field_object['clone'] as $cloned_fields ) {
if ( ! ( $cloned_field = wp_cache_get( $cloned_fields, 'acf' ) ) ) {
$cloned_field = acf_get_local_field( $cloned_fields );
wp_cache_set( $cloned_fields, $cloned_field, 'acf' );
}

// Store the objects for future use
$clones[ $key .'_' . $cloned_field['name'] ] = $cloned_field;
}

// Create an empty node in the tree to store the actual values later
$value_node = [];
break;
case 'flexible_content':
// Flexible content field's value is an array of the layouts used
$layouts = maybe_unserialize( $value );

// Create an empty node in the tree to store the actual values
$value_node = [];

// Loop through the layouts
foreach ( $layouts as $index => $layout ) {
// Insert the layout name in the data
$value_node[ $index ] = [ 'acf_fc_layout' => $layout ];
if ( ! isset( $layout_filters[ $layout ] ) ) {
$layout_filters[ $layout ] = [];
}
// Store a reference to the layout node for DustPress Component filtering
$layout_filters[ $layout ][] =& $value_node[ $index ];
}
break;
case 'repeater':
// Create an empty node in the tree to store the actual values later
$value_node = [];
break;
default:
if ( ! ( $value_node = wp_cache_get( $id .'-'. $key ) ) ) {
Copy link

@villesiltala villesiltala Dec 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the cache key be in the following format:

wp_cache_get( 'get_field/' . $id .'/key='. $key )

This needs an automatic invalidation functionality before releasing.

// Run the value through a bunch of filters to get the format we want
$value = maybe_unserialize( $value );
$value = apply_filters( "acf/format_value", $value, $id, $field );
$value = apply_filters( "acf/format_value/type={$field['type']}", $value, $id, $field );
$value = apply_filters( "acf/format_value/name={$field['_name']}", $value, $id, $field );
$value = apply_filters( "acf/format_value/key={$field['key']}", $value, $id, $field );
$value_node = $value;
wp_cache_set( $id .'-'. $key, $value_node );
}
break;

}

// Unset the reference to prevent odd bugs🐛 to appear
unset( $value_node );
}
}

// Sort the return array recursively by keys so that the fields are in the right order
// Should be a fairly quick operation because the array should be pretty much in order already
self::ksortRecursive( $fields );

// Run DustPress Components filters for the layouts through previously stored references
foreach( $layout_filters as $key => &$datas ) {
foreach( $datas as &$data ) {
$data = apply_filters( 'dustpress/components/data=' . $key, $data );
}
}

return $fields;
}

/**
* A helper method to sort arrays recursively by key
*
* @param array $array An array to sort
* @param int $sort_flags Possible sorting flags
* @return void
*/
public static function ksortRecursive( &$array, $sort_flags = SORT_REGULAR ) {
if ( ! is_array( $array ) ) {
return false;
}
ksort( $array, $sort_flags );
foreach ( $array as &$arr ) {
self::ksortRecursive( $arr, $sort_flags );
}
return true;
}
}