Skip to content

From Magic Fields 1.x to ACF, Part 8

Other posts in this series: Part 1; Part 2; Part 3; Part 4; Part 5; Part 6; Part 7; Part 8 (this post); Part 9


  1. A WordPress installation running Magic Fields version 1.x.
  2. Access to the command line to run WP-CLI.
  3. Access to the database, either through something like PHPmyAdmin or skill at the command line.
  4. The ability to clone a site.
  5. A little scripting knowledge.
  6. A lot of patience.
  7. Good attention to detail.

I’ve got #1 — that’s why we’re here. As for #2, fortunately I’ve got root-level access to the server in question so I installed CLI when the server was first put up.

For #3 I’ve got PHPmyAdmin, but I can fall back on the command line in an emergency.

For #4, I’m doing this on a multisite install, so it’s easy for me to clone a site using the MultiSite Clone Duplicator plugin.

I’ve put together PHP and Perl scripts to glue together various CLI commands in the past. That takes care of #5.

#6 I suck at. And I don’t want to talk about that any more.

But I can handle #7. Let’s dig in.


Make a clone of your working site. You know, just in case we break something. Maybe your hosting service let’s you set up a staging server. Go for it!

Then, have your live site and your cloned site up side-by-side in your browser. In the live site pull up your Magic Fields dashboard. In the clone, disable the Magic Fields plugin, and install and activate Advanced Custom Fields, ACF Photo Gallery Field, Get Custom Field Values and Advanced Custom Fields: Theme Code.

In the live site, I have two sections of Magic Fields. One is about the Parish in general (quick review: this is a site about church buildings in Newark, NJ); and the other is details about the current church building. Here’s the first:

The first section, names and dates. Very simple.

And here’s the second:

The more complicated detail section.

The tricky part here is going to be the fields that allow duplicate entries. Which is most of them, but they top out at around 10. Except for images — there are as many as 85 images per post. But that’s where the Photo Gallery Field plugin comes into play.

Just for organization’s sake I’m going to replicate the two-part structure of the posts in the cloned site. And then I’m going to create new fields using Advanced Custom Fields, keeping the names of all of the old fields.

By using the old field names, something magical happens. The fields were created in Magic Fields, which is disabled, but they still work with Advanced Custom Fields. When I open a random post, the data is pre-populated:

Sample screen.
The data from the simpler fields is still in the right place!

The Get Custom Field Values plugin offers a way to generate shortcodes that you can place in the body of your post, or in a text widget. Here’s my code that I dropped into a sidebar widget to show the four basic pieces of parish information:

Name of Parish: <b>[custom_field field="name_of_parish" this_post="1" limit="1"  /]</b>
Established: <b>[custom_field field="date_of_establishment" this_post="1" none="not specified" limit="1" /]</b>
Founding Pastor: <b>[custom_field field="founding_pastor" this_post="1" limit="1" none="not specified" /]</b>
Ethnicity: <b>[custom_field field="ethnicity" this_post="1" limit="1" none="not specified" /]</b>

And it renders like this:

Details screen cap

Now for the second part, the “Church Building,” we’ve got the more complex situation of repeated fields. And as in the previous episode, I’m going to set these up as groups. The difference this time is how I’ll name the field. By appending a number I’ll be able to iterate through them more quickly.

Screen capture of fields
Using _1, _2, etc., simplifies that later iterations.

And by using the Theme Code plugin it shows me right away what code I can drop into my template for each custom field. Of course I’ll add formatting and tests in-and-around all of these.

<?php if ( have_rows( 'church_building_architect' ) ) : ?>
<?php while ( have_rows( 'church_building_architect' ) ) : the_row(); ?>
<?php the_sub_field( '_1' ); ?>
<?php the_sub_field( '_2' ); ?>
<?php endwhile; ?>
<?php endif; ?>

So just incrementing the_sub_field I can do things.

Capturing Fields

A few simple SQL queries against the database gave me what I needed, and using some find/replace regex-fu on the results let me put together a couple of useful arrays that I can drop into my PHP script.

$multiples = array( 'church_building_architect',
'church_building_renovations' );

I already knew which fields took multiples, so I grabbed these from the web interface for Advanced Custom Fields.

The SQL query of this:
SELECT ID, post_excerpt, post_name, post_title FROM wp_1776_posts WHERE post_type='acf-field' ORDER BY post_title ASC, post_excerpt ASC;

Gave me the result

| ID   | post_excerpt                                  | post_name           | post_title            |
| 1963 | church_building_architect                     | field_5f0e19c193eae | Architect             |
| 1964 | _1                                            | field_5f0e1a1d93eaf | Architect             |
| 1965 | _2                                            | field_5f0e1a3093eb0 | Architect             |
| 1968 | church_building_architectural_style           | field_5f0f3ca3ede62 | Architectural Style   |
| 1990 | _1                                            | field_5f0f3f3b8abf7 | Artwork               |
| 1991 | _2                                            | field_5f0f3f3b8abf8 | Artwork               |
| 1992 | _3                                            | field_5f0f3f3b8abf9 | Artwork               |
| 1993 | _4                                            | field_5f0f3f3b8abfa | Artwork               |
| 1994 | _5                                            | field_5f0f3f678abfb | Artwork               |
| 1979 | _1                                            | field_5f0f3df59611a | Bell                  |
| 1988 | _10                                           | field_5f0f3eff96138 | Bell                  |
| 1980 | _2                                            | field_5f0f3e109611c | Bell                  |
| 1981 | _3                                            | field_5f0f3e299611d | Bell                  |
| 1982 | _4                                            | field_5f0f3e2e9611e | Bell                  |
| 1983 | _5                                            | field_5f0f3e319611f | Bell                  |
| 1984 | _6                                            | field_5f0f3e3496120 | Bell                  |
| 1985 | _7                                            | field_5f0f3e3696121 | Bell                  |
| 1986 | _8                                            | field_5f0f3edd96134 | Bell                  |
| 1987 | _9                                            | field_5f0f3eed96136 | Bell                  |
| 1978 | church_building_bells                         | field_5f0f3dd996119 | Bells                 |
| 2009 | church_building_commentary                    | field_5f0f41eef8ada | Commentary            |
| 2006 | church_building_current_owner_and_use         | field_5f0f4193f8ad7 | Current owner and use |

Earlier, for ease of use, I’d named the sub-fields “_1”, “_2”, etc. So now I’ve got a bunch of duplicative field names. What’s more, they’re not in the correct sequence. So I just move the rows around until they were, and then put in the proper prefix for _1, _2 and so on, eliminated the columns I didn’t need, and put it into a PHP associative array:

$field_codes = array(
'church_building_architect' => 'field_5f0e19c193eae',
'church_building_architect_1' => 'field_5f0e1a1d93eaf',
'church_building_architect_2' => 'field_5f0e1a3093eb0',
'church_building_architectural_style' => 'field_5f0f3ca3ede62',
'church_building_architectural_style_1' => 'field_5f0f3cb5ede63',
'church_building_architectural_style_2' => 'field_5f0f3cc3ede64',
'church_building_architectural_style_3' => 'field_5f0f3cd5ede65',
'church_building_architectural_style_4' => 'field_5f0f3cedede67',
'church_building_bells' => 'field_5f0f3dd996119',
'church_building_bells_1' => 'field_5f0f3df59611a',
'church_building_bells_2' => 'field_5f0f3e109611c',
'church_building_bells_3' => 'field_5f0f3e299611d',
'church_building_bells_4' => 'field_5f0f3e2e9611e',
'church_building_bells_5' => 'field_5f0f3e319611f',
et cetera...

Working off of that, I can start to put together some code. I’m going to write the main script in PHP, and rely on WP-CLI to do the actual commands to the WordPress instance. Basically, the PHP code is going to write code. Here we go:

Adding the Metadata

$churchlist = array();
$meta_inserts = array();

In some circumstances writing scripts like this you’ll need to change the active user from yourself to “apache” or whatever your server runs as in order the have the right permissions. You’ll also need to change the $PATH to include your PHP executable. We’re not creating directories or adding files here, so those permissions aren’t going to be necessary.

So this first chunk sets up an empty array where we’ll store information about the church as we go; and another array that will contain the full text of our CLI commands.

Next, we’ll get an output of all our post IDs.

exec('wp post list --post-type=post --fields=ID,post_name --url= --skip-themes --skip-plugins', $output);

The ins-and-outs of WP-CLI are outside the scope of this article. But I’ve written others. This command drops the entire listing of posts (not pages, or media, or revisions) into $output. I always skip themes and plugins because they throw errors sometimes — especially in a case like this where getting rid of buggy and incompatible plugins and themes is the whole point.

Rename the array, just because:
$churches = $output;

Then go into a loop:
foreach ( $churches as $church ) {

$metas was proving to be persistent, causing problems, so I reset it on each iteration.

$metas = '';

Because I’m going to split the result line on whitespace, and post_name or post_title isn’t mission critical, I’ll go with the no-whitespace post_name.

$churchinfo = preg_split("/\s+/", $church );
echo "\n" . $churchinfo[0] . "\t" . $churchinfo[1] . "\n";
This is for checking what’s going on when I run the PHP from the command line. [0] will the the post ID and [1] will be the post_name.

And then we’re going to construct a WP-CLI command, assign it to the variable $cmd, and run it.

$cmd = 'wp post meta list ' . $churchinfo[0];
$cmd .= ' --url= --skip-themes --skip-plugins';
$cmd .= ' --keys=name_of_parish,date_of_establishment,founding_pastor,ethnicity,church_building_architect,';
$cmd .= 'church_building_architectural_style,church_building_interior_designer,';
$cmd .= 'church_building_fabricator_of_windows,church_building_bells,church_building_specific_notable_works_of_art,';
$cmd .= 'church_building_renovations,church_building_current_status,church_building_current_owner_and_use,';
$cmd .= 'church_building_interior_pictures,church_building_exterior_pictures,church_building_commentary,';
$cmd .= 'church_building_year_opened,church_building_year_closed --format=json';
exec($cmd, $metas);

Why format as json? Because some of the fields are textarea fields — and json encodes the newlines as \r or \n. Having a carriage return in the middle of code is going to break something. For example:

                                                                                                                                                          35644 |    1643 | church_building_renovations                         | 1898 - Baptistry added to south side of apse; balancing niche on north side of apse
1912 - Entrance way and vestibule
1950 - North and south transepts extended - duplicate the design of the original church structure
1980 - new altar   

Those last four lines need to be part of the fourth field of the first line.

If you like you can echo out $meta to see what’s coming your way. But now we’re going to iterate over this list of metadata entries for an individual post.

Start the loop:
foreach ($metas as $meta) {
$newMatches = json_decode($meta);

Json returns objects. And in this case, the object is a set of other objects. Getting into the guts of an object can be difficult. Enough said about that. I’m going to decode $meta into object, $newMatches, then iterate through that to start getting the items I need.

foreach ( $newMatches as $newMatch ) {
$post = '';
$metakey = '';
$metavalue = '';
$metainsert = '';

By re-initiating these variables I prevent any holdover from loop to loop. There are a fair number of meta_value entries that are empty. This could just be because the content creator hit the “add additional” button, or just left a field blank. We’re going to skip them.
if ( '' != $newMatch->meta_value ) {
$post = $newMatch->post_id ;
$metakey = $newMatch->meta_key;
$metavalue = $newMatch->meta_value;

Assigning the values derived from the object to the initialized variables. Finally here’s where some work starts to be done:
if ( in_array( $metakey, $multiples )) {

This determines whether or not the meta key is one of the custom fields that allowed multiple copies. Going back to the original content, these include things like church windows, renovations, bells, artworks, all of which can have multiple entries. But not things like founding date or current use, which can only have one. We’re going to create a WP-CLI meta add for this which captures the parent item — but not the sub-items. Those will come later.
$churchlist[$post][$metakey][] = $metavalue;
Adding the key/value pair to the post’s sub-set of the $churchlist array.

Then we look up the ACF special post type that contains the rules for the field. Something like “field_1234”.
$fieldvalue = $field_codes[$metakey];

And start to cook up a new WP-CLI command:
$metainsert = "wp post meta add $post ";
$metainsert .= "_$metakey $fieldvalue ";
$metainsert .= '--url= ';
$metainsert .= '--skip-themes --skip-plugins';

Notice especially the underscore character in front of $metakey. That’s how ACF creates these entries — one for _my_meta_key, which points to the “field_xyz” post in the posts table, the one that contains the rules; and one for my_meta_key, which holds the actual value for the meta_key.

At this point I’ll echo out the CLI command just to be sure it makes sense. It can be commented out later for speed.
echo "$metainsert\n";

Then I’m going to add it to the $meta_inserts array. The reason why I’m adding it to the array is that as I go through the meta statements there are going to be duplicates, and I only want one entry going into the database for a given meta_key. So into an array it goes, and I’ll de-dupe it later.
$meta_inserts[] = $metainsert;

And close out that if..then:

I do two similar elseif runs, one for each of the image galleries. Notice I’m not adding any values or images yet — just making sure that the corrent _my_meta_key/value pairs are inserted. They too get added to the $meta_inserts array for safekeeping.

And then finally if it’s not a duplicating field or an image gallery, just add the metainsert for the single entries. Recall that these values for meta_keys are already set — they were created in Magic Fields, and since we named all our Advanced Custom Fields fields the same way, nothing more needs to be done with them.

Results So Far

Here is a slice of the $churches array for post 1779:

    [1779] => Array
            [name_of_parish] => Immaculate Conception
            [date_of_establishment] => 1925
            [founding_pastor] => Rev. Cataldo Alessi, Rev. Francis P. Mestice
            [ethnicity] => Italian
            [church_building_architect] => Array
                    [0] => Peter Terrafranca

            [church_building_architectural_style] => Array
                    [0] => Modern
                    [1] => Postmodern

            [church_building_interior_designer] => Array
                    [0] => Peter Vuikovich

            [church_building_specific_notable_works_of_art] => Array
                    [0] => Glass in concrete mural of Resurrection - Peter Vuikovich
Stations of the Cross - Peter Vuikovich

            [church_building_current_status] => In use
            [church_building_current_owner_and_use] => <p>Merged with Our Lady of Good Counsel</p>
            [church_building_interior_pictures] => Array
                    [0] => ics02.jpg

            [church_building_exterior_pictures] => Array
                    [0] => ics01.jpg

            [church_building_year_opened] => 1966

And these are the meta commands (each is one line, of course):

    [197] => wp post meta add 1779 _name_of_parish field_5f0e0c34c433f --url= --skip-themes --skip-plugins
    [198] => wp post meta add 1779 _date_of_establishment field_5f0e0c45c4340 --url= --skip-themes --skip-plugins
    [199] => wp post meta add 1779 _founding_pastor field_5f0e0c58c4341 --url= --skip-themes --skip-plugins
    [200] => wp post meta add 1779 _ethnicity field_5f0e0c8ec4342 --url= --skip-themes --skip-plugins
    [201] => wp post meta add 1779 _church_building_architect field_5f0e19c193eae --url= --skip-themes --skip-plugins
    [202] => wp post meta add 1779 _church_building_architectural_style field_5f0f3ca3ede62 --url= --skip-themes --skip-plugins
    [204] => wp post meta add 1779 _church_building_interior_designer field_5f11c95a53e32 --url= --skip-themes --skip-plugins
    [205] => wp post meta add 1779 _church_building_specific_notable_works_of_art field_5f0f3f3b8abf6 --url= --skip-themes --skip-plugins
    [206] => wp post meta add 1779 _church_building_current_status field_5f0f4135f8ad6 --url= --skip-themes --skip-plugins
    [207] => wp post meta add 1779 _church_building_current_owner_and_use field_5f0f4193f8ad7 --url= --skip-themes --skip-plugins
    [208] => wp post meta add 1779 _church_building_interior_pictures field_5f0f41b0f8ad8 --url= --skip-themes --skip-plugins
    [209] => wp post meta add 1779 _church_building_exterior_pictures field_5f0f41d8f8ad9 --url= --skip-themes --skip-plugins
    [210] => wp post meta add 1779 _church_building_year_opened field_5f0f4201f8adb --url= --skip-themes --skip-plugins

At this point it’s ready to iterate through the images.


All your images attached to posts are stored in [blog_root]/files/files_mf, where they are usually out of reach of the WordPress media library. This is a real flaw. I tried a couple of ways of bringing them in.

Media from FTP: This worked for 1,000 images, then it hung up and kept returning 500 (Gateway Timeout) server errors.
Bulk Register Media: This seems to work. However the fate of the 1,000 already imported images is in doubt. It definitely has the tendency to duplicate things.

However they both produce usable records. In the case of Media from FTP, set logging in the initial configuration and then you can download a CSV file.

"ID","Author","Title:","Permalink:","URL:","File name:","Date/Time:","File type:","File size:","Caption[Exif]:","Length:","Images1","Images2","Images3","Images4","Images5","Images6","Images7","Images8","Images9"
"2013","Tom McGee","004_0a","","","004_0a.jpg","2020-07-16 18:09:42","image/jpeg","573 KB","","","","","","","",""
"2014","Tom McGee","004_0a10","","","004_0a10.jpg","2020-07-16 18:09:43","image/jpeg","553 KB","","","","","","","",""

For example. you could open this into Excel and keep the columns you need for the step of adding them into the image galleries later.

In the case of Bulk Register Media, there is no nice log file. So I did a select-all of the results screen and copied it into my text editor.

ID: 3013
Title: annunciationparamus
File name: annunciationparamus.jpg
Original URL:
Original File name: annunciationparamus.jpg
Date/Time: 2020-7-17 9:11:47
File type: image/jpeg
File size: 586 KB
Images: [medium][large][thumbnail][medium_large][1536x1536][2048x2048][piedmont-full-width][piedmont-featured]

ID: 3014
Title: card00120_fr1
File name: card00120_fr1.jpg
Date/Time: 2020-7-17 9:11:49
File type: image/jpeg
File size: 41 KB
Images: [medium][thumbnail]

You could reconfigure all of this too. But we won’t need to, because WP-CLI has methods for finding what you need from these newly created posts.

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Share This