Intro

When you are creating fields in Ultimate Fields, you have to assign them to a container. In order to display the fields in the right place, as well as the container, you need to assign the appropriate location(s) to that container.

To understand locations, let's take a look at a quick example:

You need to add custom fields to your pages. Pages in WordPress are a post type, which means that you need to assign the container to a post type location, particularly for the post type with slug page, which is exactly what you will see in the example below.

Important

This article is only applicable if you are using PHP for container and field creation.
If you are managing your containers through the administration interface, you may skip it.

Usage

To add a location to a container, you need to use the add_location method of the container, like this:

Container::create( 'page-settings' )->add_location( 'post_type', 'page' );

The method accepts a single basic parameter called $type. It can either be:

  • An instance of a location class (a sub-class of Ultimate_Fields\Location).
  • A string, indicating which location class to use. In this case, the string is used to determine which class name to use and the rest of the paremeters, given to add_location are passed down to the constructor of the class.

Since the Location class also has a create method, the following three examples are equivalent:

use Ultimate_Fields\Container;
use Ultimate_Fields\Field;
use Ultimate_Fields\Location\;
use Ultimate_Fields\Location\Post_Type;

$container = Container::create( 'page-settings' );

// Example 1:
$container->add_location( 'post_type', 'page', array(
	'templates' => 'default'
));

// Example 2:
$location = Location::create( 'post_type', 'page', array(
	'templates' => 'default'
));

$container->add_location( $location );

// Example 3:
$location = new Post_Type( 'page', array(
	'templates' => 'default'
));

$container->add_location( $location );

If you are using an IDE editor like PhpStorm, you will probably prefer to use the third option. This way you would be able to enjoy proper type hinting for every location. However, please keep in mind that there a few limitations to using that:

  1. If you are using a location from Ultimate Fields Pro, you must remember to use the proper class (ex. Ultimate_Fields\Pro\Location\Taxonomy).
  2. Some classes may be overwritten by extensions and you wouldn't even know it. For example, Ultimate Fields Pro extends Ultimate_Fields\Location\Post_Type in Ultimate_Fields\Pro\Location\Post_Type while adding support for the Customizer.
  3. In case the extension (ex. Ultimate Fields Pro) is not active, the whole site might get broken.

Therefore, even if you need to extract a location as a variable, please use Location::create rather than instantiating a new object., if possible.

Available Classes

These are the locations, available in Ultimate Fields, their keywords as well as their classes.

Location Keyword Class name Example
Ultimate Fields
Post Type post_type Ultimate_Fields\Location\Post_Type ->add_location( 'post_type', 'page' )
Options Page options Ultimate_Fields\Location\Options ->add_location( 'options' )
Ultimate Fields Pro
Post Type post_type Ultimate_Fields\Pro\Location\Post_Type ->add_location( 'post_type', 'post' )
Options Page options Ultimate_Fields\Pro\Location\Options ->add_location( 'options' )
Taxonomy taxonomy Ultimate_Fields\Pro\Location\Taxonomy ->add_location( 'taxonomy', 'category' )
Comment comment Ultimate_Fields\Pro\Location\Comment ->add_location( 'comment' )
User user Ultimate_Fields\Pro\Location\User ->add_location( 'user' )
Widget widget Ultimate_Fields\Pro\Location\Widget ->add_location( 'widget' )
Shortcode shortcode Ultimate_Fields\Pro\Location\Shortcode ->add_location( 'shortcode' )
Menu Item menu_item Ultimate_Fields\Pro\Location\Menu_Item ->add_location( 'menu_item' )
Attachment attachment Ultimate_Fields\Pro\Location\Attachment ->add_location( 'attachment' )
Customizer customizer Ultimate_Fields\Pro\Location\Customizer ->add_location( 'customizer' )

As you can see, Ultimate Fields Pro has upgraded classes for the post type and options page locations.

Examples

In order to improve your grasp on locations, let's take a look at a few examples:

Add fields to... Location Subtype Method call
...pages Post Type page ->add_location( 'post_type', 'page' )
...a posts Post Type post ->add_location( 'post_type', 'post' )
...the "Events" custom post type Post Type event ->add_location( 'post_type', 'event' )
...a new options page Options Page - ->add_location( 'options' )
...categories Taxonomy category ->add_location( 'taxonomy', 'category' )
...the "Genre" custom taxonomy Taxonomy genre ->add_location( 'taxonomy', 'genre' )
...comments Comment - ->add_location( 'comment' )
...users User - ->add_location( 'user' )
...the "Text" widget Widget "WP_Widget_Text" ->add_location( 'widget', 'WP_Widget_Text' )
...a "Quote" Shortcode "quote" ->add_location( 'shortcode', 'quote' )
...all menu items Menu Item - ->add_location( 'menu_item' )
...images Attachment - ->add_location( 'attachment' )
...the Customizer Customizer - ->add_location( 'customizer' )

To understand all locations, please read their individual articles in this chapter the documentation.

Method Chaining

As you probably already know from the "Method Chaining" section in the "Using the PHP API" article, the following two examples are equivalent. Because of this, all further examples on this page will simply use a $container variable for demonstrations.

$container = Container::create( 'page-settings' );
$container->add_location( 'post_type', 'page' );
$container->add_fields(array( /** ... */ ))

add_location always returns the instance of the container, so you can chain other methods like this:

Container::create( 'page-settings' )
	->add_location( 'post_type', 'page' )
	->add_fields(array( /** ... */ ));

Arguments and methods

As mentioned in Usage above, calling the add_location method expects either:

  1. A single argument which would be a new location (ex. Ultimate_Fields\Location::create( 'post-type' ), which extends Ultimate_Fields\Location)
  2. A string as the first argument, indicating which class to use (ex. post_type). In this case, the rest of the arguments are forwarded to the constructor of the location class.

Scenario 1 is better for IDE autocompletion, while scenario 2 allows you to chain other methods and avoid additional variables.

Type argument

If you are using the second scenario (string type argument), it must be a string which contains a basic version of the base name of the location class. Uppercased letters will be added automatically and the text will be concatenated with Ultimate_Fields\Location as a namespace. This means that the type goes through the following cycle in order to generate the complete location class:

  1. post_type
  2. Post_Type
  3. Ultimate_Fields\Location\Post_Type

Additional parameters

As you can see in the examples table above, some locations accept additional parameters. The Post_Type location for example, expects a second parameter which indicates what post types to associate the location with.

The amount of additional accepted parameters is individual for each location. Some locations accept a single parameter, but most of them accept between two and three arguments. Generally:

  • if you see a subtype in the examples table above, the second parameter should be that subtype and there is a third (also last) parameter, which may contains *additional arguments.
  • if you do not see a subtype in the table, the second (and last) parameter may contain additional arguments.

We will refer to the additional arguments simply as $args.

The $args parameter

$args must always be an array and is always the last argument when creating a location. The purpose of this array is to let the containers' add_location method to fully initialize a new location, without you having to call any additional methods.

// Shorthand syntax
$container->add_location( 'post_type', 'page', array(
	'context' => 'side'
));

// Longer syntax
$location = new Ultimate_Fields\Location\Post_Type( 'page', array(
	'context' => 'side'
));
$container->add_location( $location );

// Full syntax
$location = new Ultimate_Fields\Location\Post_Type();
$location->add_post_type( 'page' );
$location->contaxt = 'side';
$container->add_location( $location );

All of the snippets above perform the same action, but are useful in different scenarios. As you can see, you can fully avoid using the $args array if you save the location as a variable.

When locations receive an $args parameter, those arguments are parsed by it and proxied to the proper methods, typically setters. When you read the documentation and you see that a location has a set_* or an add_* method, you can always remove the verb from the beginning in order to figure out what the key of the value in the $args array should be. For example:

  • The set_context method can be avoided by adding a context element to the $args array.
  • The set_dynamic_fields method can be avoided by adding a dynamic_fields element to the $args array.
  • The add_post_type method can be avoided by adding a post_type element to the $args array.

Multi-Value Include-Exclude Rules (MVIE Rules)

Most of the additional rules, available in locations, can have several options and each of them can both include and exclude a value.

Let's use the Templates of the Post Type location as an example. A theme can have multiple templates and your logic might want to treat them in separate ways:

  1. Show fields on a particular template
  2. Show fields on multiple templates
  3. Show fields on all templates, excluding a specific one
  4. Show fields on all templates, excluding several ones

Ultimate Fields can handle all of these scenarios by using multi-value rules. For the Post Type location, templates are set either through the set_templates method or through the templates argument.

Information

Even if you use a single value for an MVIE rule, locations will internally convert it to an array, meaning that you can provide both a single option, or an array of options.

// Using a simple template
$container->add_location( 'post_type', 'page', array(
	'templates' => 'template-home.php'
));

// Using multiple templates
$location = new Ultimate_Fields\Location\Post_Type( 'page' );
$location->set_templates( array( 'template-home.php', 'template-pro.php' ) );

If you need to exclude a value, you simply need to add a "-" (minus for numbers, dash for strings) in the beginning of the value. When the value is matched, the rule will be negative:

$location->set_templates( array( '-template-home.php', '-template-pro.php' ) );

In this case, the container will be shown only when the selected template is neither template-home.php nor template-pro.php.

Each MVIE rule will have a notice next to its description in the documentation, linking to this section.

Controllers

Advanced Content

The rest of this article focuses on the internal implementation of locations and the controllers that manage them. If you are not looking to extend an existing location or create a new one, the rest of this article will be inapplicable for you.

Overview

The Class structure and logic article explains in details how locations and containers actually connect to WordPress.

A single container may have multiple, completely separate locations, but it might also have several very similar locations. Check this container for example:

Container::create( 'Section Details' )
    ->add_location( 'post_type', 'page', array(
        'templates' => 'template-home.php'
    ))
    ->add_location( 'post_type', 'page', array(
        'levels'    => 1
    ))
    ->add_fields(array( /*...*/ ));

In this example we are adding two locations to the container. Both of them will appear on the same post type, page, but one of them has additional rules based on template, while the other one has rules based on hierarchical levels.

The container will be visible when either of those rules are matched, but a container should not appear twice on the same page. This means that locations cannot display containers based on their own rules. Instead, for each location class, there is an additional Controller class.

Internal mechanism

As a high-level generalization, here is what controllers do:

  • They group location rules based on the container and then smartly combine those rules in an OR relationship for when the time comes to validate and display the container.
  • When saving, the validation process for a whole screen is initiated by controllers, meaning that all containers must report that their data is valid before any of them can save it.
  • Ensures that multiple fields with the same names cannot be used within the same context.
  • Manages admin columns.
  • During database read-write operations, link containers to the proper datastore.

To understand how controllers work, take a look at the following abstraction:

<?php
class Container {
    /**
     * When a location is added to the container, the container attaches itself to
     * the controller, provided by the particular location.
     */
    function add_location( $location ) {
        $controller = $location->get_controller();
		$controller->attach( $this, $location );
        return $this;
    }
}

class Location\Post_Type extends Location {
    protected $post_types = array();

    function __construct( $post_type ) {
        $this->post_types = array_merge( $this->post_types, (array) $post_type );
    }

    function get_post_types() {
        return $this->post_types;
    }

    /**
     * Each location class returns a different controller, which should handle it.
     */
    function get_controller() {
        return Controller\Post_Type::instance();
    }    
}

class Controller {
    protected $combinations = array();

    /**
     * When a controller attaches itself to the controller, the controller saves
     * both the actual container and the location that is currently being added.
     */
    function attach( $container, $location ) {
        $id = $container->get_id();

        if( ! isset( $this->combinations[ $id ] ) ) {
            $this->combinations[ $id ] = array(
                'container' => $container,
                'locations' => array()
            );
        }
        
        $this->combinations[ $id ]['locations'] = $location;
    }
}

class Controller\Post_Type extends Controller {
    /**
     * Each controller is a singleton and can only have a single instance.
     */
    public static function instance() {
		static $instance;

        return is_null( $instance )
            ? $instance = new self()
            : $instance;
	}

    /**
     * Controllers attach themselves to the right hooks.
     */
    protected function __construct() {
        add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
		add_action( 'save_post', array( $this, 'save_post' ) );
    }

    /**
     * The post type controller combines the post types (and additional rules) from each
     * location, added to a container. This way there is a single meta box for each container.
     */
    function add_meta_boxes() {
        foreach( $this->combinations as $combination ) {
            $container  = $combination['container'];
            $post_types = array();

            foreach( $combination['locations'] as $location ) {
                $post_types = array_merge( $post_types, $location->get_post_types() );                
            }

            $callback = new Ultimate_Fields\Helper\Callback( array( $this, 'display' ) );
    		$callback[ 'container' ] = $container;

            add_meta_box(
    			$container->get_id(),
    			$container->get_title(),
    			$callback->get_callback(),
    			$post_types
    		);
        }
    }

    /**
     * Displays a single container, as well as all of its settings.
     */
    function display( $callback_data, $post ) {
        $container = $callback_data[ 'container' ];

        $json = $container->export_settings();
        ?>
        <div class="uf-container">
            <script type="text/json"><?php echo $json ?></script>
        </div>
        <?php
    }
}

The role of containers and locations, for the most part, is to simply hold definitions. As you can see above, the heavy lifting is mainly perfomed by the controllers.