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

[WIP] 2.x Raw meta #1904

Merged
merged 49 commits into from
Sep 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
356c846
Add raw_meta() method for classes that implement MetaInterface
gchtr Jan 12, 2019
d5991fa
Update documentation with new methodology for accessing meta values
gchtr Jan 13, 2019
278d9b3
Update upgrade guide
gchtr Jan 13, 2019
9ab7219
Add missing api tags
gchtr Jan 13, 2019
85aaad7
Update return description for meta() and raw_meta()
gchtr Jan 13, 2019
fc305d3
Add raw_meta() method to MenuItem
gchtr Jan 13, 2019
cf34ed6
Catch some failing tests
gchtr Jan 13, 2019
b3cf321
Update test that accesses a property dynamically
gchtr Jan 13, 2019
3df26e7
Merge branch '2.x' into 2.x-raw-meta
gchtr Jun 21, 2019
2a46e1c
Fix merged tests
gchtr Jun 26, 2019
4244a8f
Add deprecation message when trying to access the now protected custo…
gchtr Jun 26, 2019
449d61c
Make field name required for Post::meta()
gchtr Jun 26, 2019
48eb68b
Update tests for different use cases related to access through magic …
gchtr Jun 26, 2019
a1daac9
Rename test-timber-custom-fields.php to test-timber-post-meta.php
gchtr Jun 30, 2019
658c623
Move post meta tests to TestTimberPostMeta
gchtr Jun 30, 2019
0c5717c
Do not import meta values
gchtr Jul 1, 2019
f663b65
Add meta testing class and move meta filter functions to separate tes…
gchtr Jul 1, 2019
c6dc185
Fix class name
gchtr Jul 1, 2019
cf3e56c
Update documentation
gchtr Jul 3, 2019
dd14c95
Streamline testPreGetMetaValuesDisableFetch() methods
gchtr Jul 3, 2019
1b0b549
Fix bug in testMetaDirectAccessInaccessiblePropertyConflict() test
gchtr Jul 3, 2019
1b01c08
Remove ACF filter that makes no sense
gchtr Jul 3, 2019
70696ab
Fix testPreGetMetaValuesDisableFetch tests
gchtr Jul 4, 2019
6281f4e
Merge branch '2.x' into 2.x-raw-meta
jarednova Jul 5, 2019
54e8b32
Remove $custom property and the import of the meta values on object i…
gchtr Aug 6, 2019
a5afeb1
Move logic of get_meta_values() into meta(), update raw_meta()
gchtr Aug 6, 2019
d5cf1bf
Rename $value variable to something more meaningful
gchtr Aug 11, 2019
ed6e3ef
Make meta function arguments consistent
gchtr Aug 12, 2019
2934320
Make fetching all meta values work
gchtr Aug 12, 2019
4c4618f
Fix result handling after fetching meta data
gchtr Aug 20, 2019
7ff7beb
Update tests
gchtr Aug 20, 2019
f50b72d
Merge branch '2.x' into 2.x-raw-meta
gchtr Aug 20, 2019
95ea225
Deprecated Post::$_thumbnail_id, add Post::thumnbail_id()
gchtr Aug 20, 2019
992888c
Deprecate MenuItem::$_menu_item_object_id, add MenuItem::$object_id
gchtr Aug 20, 2019
3088804
Make sure Attachment::$_wp_attached_file exists
gchtr Aug 20, 2019
f2c373a
Load ACF plugin only when we test the ACF integration
gchtr Aug 26, 2019
f65e6ae
Update tests for deprecated meta filters
gchtr Aug 26, 2019
48af19c
Remove deprecated post meta filter, generalize test for pre_meta filt…
gchtr Aug 26, 2019
d31425b
Fix tests
gchtr Aug 26, 2019
86a187d
Update documentation
gchtr Aug 26, 2019
7dcb88f
Merge separate meta test functions into one, remove deprecated test f…
gchtr Aug 26, 2019
b1f7eb8
Fix test
gchtr Aug 26, 2019
b21dab4
Make ACF filter functions removable
gchtr Aug 27, 2019
f6766a8
Fix meta tests when ACF filters were still active
gchtr Aug 27, 2019
3af5611
Fix more meta tests when ACF filters were still active
gchtr Aug 27, 2019
1f05c24
Some spelling/grammar fixes; update to revision test
jarednova Sep 2, 2019
fd43ded
Remove code that does nothing
gchtr Sep 8, 2019
29a9b8e
Merge branch '2.x' into 2.x-raw-meta
jarednova Sep 15, 2019
c4fd18d
Merge branch '2.x' into 2.x-raw-meta
jarednova Sep 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
126 changes: 100 additions & 26 deletions docs/guides/custom-fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,22 @@ menu:
parent: "guides"
---

Timber tries to make it as easy as possible for you to retrieve custom meta data for post, term and user objects. And it works with a range of plugins that make it easier for you to create custom fields, like Advanced Custom Fields. While most of this guide applies to everything you do with custom fields, we have separate guides in the [Integrations sections](https://timber.github.io/docs/integrations/).
Timber tries to make it as easy as possible for you to retrieve custom meta data for Post, Term, User and Comment objects. And it works with a range of plugins that make it easier for you to create custom fields, like Advanced Custom Fields. While most of this guide applies to everything you do with custom fields, we have separate guides in the [Integrations section](https://timber.github.io/docs/integrations/).

## Accessing custom values

When you have a custom field that’s named `my_custom_field`, you can access it in 3 different ways:

1. Access it through the `meta()` method.
2. Directly access it through the field’s name.
3. Access it through the `custom` property.
1. Access it [through the `meta()` method](#the-meta-method).
2. Access it [through the `raw_meta()` method](#the-raw-meta-method).
3. Define [your own method](#define-your-own-method).
4. Directly access it [through the field’s name](#direct-access-through-custom-field-name).

Each of these methods behaves a little differently. Let’s look at each one in more detail.

### The `meta()` method

This method is the recommended way to access meta values. You’ll get values that are **filtered** by third-party plugins (e.g. Advanced Custom Fields).
This method is the **recommended way to access meta values**. You’ll get values that are **filtered** by third-party plugins (e.g. Advanced Custom Fields).

**Twig**

Expand All @@ -34,9 +35,27 @@ This method is the recommended way to access meta values. You’ll get values th
$my_custom_field = $post->meta( 'my_custom_field' );
```

### Direct access through field name
### The `raw_meta()` method

We *recommend* to use this method when you’ve defined a custom method that **modifies the value of the custom field before it is returned**.
With this method, values are **raw** (directly from the `wp_meta` table in the database) and are **not filtered** by third-party plugins (e.g. Advanced Custom Fields).

**Twig**

```twig
{{ post.raw_meta('my_custom_field') }}
```

**PHP**

```
$my_custom_field = $post->raw_meta( 'my_custom_field' );
```

### Define your own method

Sometimes you need to modify a meta value before it is returned. You can do that by [extending a Timber object](https://timber.github.io/docs/guides/extending-timber/) and defining your own method. In the following example, we set a custom `price()` method to format the price that’s saved in a custom field named `price`.

**PHP**

```php
class CustomPost extends Timber\Post {
Expand All @@ -52,49 +71,104 @@ class CustomPost extends Timber\Post {
}
```

In Twig, you would access it like this:

**Twig**

```twig
{{ post.price }}
```

This way, you’ll know from looking at the code that you call a method. If that method doesn’t exist, Timber will fall back to the equivalent `{{ post.meta('post') }}` call. This means that you can always write `{{ post.price }}`, even if you don’t have method. We don’t recommend to use this method, because you might run into conflicts with existing methods on an object.
You’ll know from looking at the code that you call a defined property or method. Be aware that through this method, you might overwrite a method that already exists on for a `Timber\Post` object (like [`date`](https://timber.github.io/docs/reference/timber-post/#date)), which is totally fine if you know what you’re doing.

#### Caveat: Conflicts with Timber methods
### Direct access through custom field name

This method might not work in all cases. For example, when you use a custom field that you name `date` and try to get its value through `{{ post.date }}`, it won’t work. That’s because [`date`](https://timber.github.io/docs/reference/timber-post/#date) is a method of the `Timber\Post` object that returns the date a post was published.
If a directly accessed property or method doesn’t exist, Timber will fall back to the equivalent `{{ post.meta('post') }}` call. This means that you can always write `{{ post.price }}`, even if you don’t have a method defined.

In PHP, you’d access the property through `$post->date` and call the `date` method through `$post->date()`. But in Twig, you can call methods without using brackets. And methods take precedence over properties. Timber uses a technique called [Overloading](http://de.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.members) to get meta values using PHP’s [`__get` magic method](http://php.net/manual/en/language.oop5.overloading.php#object.get) on `Timber\Post`, `Timber\Term`, `Timber\User` and `Timber\Comment` objects. This means that when you use `{{ post.date}}`, it will
**We don’t recommend to use this method, because you might run into conflicts with existing properties or methods on an object.**

- Check if method method `date` exists. If it does, it will return what the method produces.
For example, when you use a custom field that you name `date` and try to get its value through `{{ post.date }}`, it won’t work. That’s because [`date`](https://timber.github.io/docs/reference/timber-post/#date) is a method of the `Timber\Post` object that returns the date a post was published.

In PHP, you’d access the property through `$post->date` and call the `date` method through `$post->date()`. But in Twig, you can call methods without using parentheses. And methods take precedence over properties. Timber uses a PHP technique called [Overloading](http://de.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.members) to get meta values using PHP’s [`__get` magic method](http://php.net/manual/en/language.oop5.overloading.php#object.get) on `Timber\Post`, `Timber\Term`, `Timber\User` and `Timber\Comment` objects. This means that when you use `{{ post.date }}`, it will ...

- Check if method `date` exists. If it does, it will return what the method produces.
- Otherwise, it will check if a property `date` exists. If it does, it will return its value.
- Otherwise, it will hit the `__get()` method, which handles undefined or inaccessible properties. Timber’s `__get()` method will look for existing custom values and return these.
- Otherwise, it will hit the `__get()` method, which handles undefined or inaccessible properties. Timber’s `__get()` method will look for existing meta and return these.

Now, if you run into this problem, you should probably use the [`meta()`](#the-meta-method) method.
Now, if you ever run into this problem, you should either use the [`meta()`](#the-meta-method) or [`raw_meta()`](#the-raw-meta-method) method.

### The `custom` property
### Accessing all meta values

The `custom` property that’s always set on an object is an array that hold the values of all the meta values from the `postmeta` table in the database. With this method, values are **raw** (directly from the database) and are **not filtered** by third-party plugins (e.g. Advanced Custom Fields).
In earlier versions of Timber, you could use the `custom` property on an object to check which meta values were set on a post, term, user or comment object.

**Twig**
Now, to get the values for all meta values that are set on an object, you can use `meta()` or `raw_meta()` without passing a field name.

```twig
{{ dump(post.meta()) }}
{{ dump(post.raw_meta()) }}
```
{{ post.custom.my_custom_field }}

This is only recommended **for development purposes**, because it might affect your performance if you always request all values.

### Performance

You might be tempted to set variables for repeated calls to `meta()` or `raw_meta()` in Twig to save performance. This is not really necessary. Timber uses `get_post_meta()` internally, which fetches all meta values for an object from the database when the first value is requested and caches the meta values for later meta requests. The only thing you will be bypassing are a couple of filters, which is unlikely to result in a performance savings.

For example, in most cases it’s totally fine to use `meta()` in an if statement before using it again to display the value:

```twig
{% if book.meta('author') %}
<h3>Author</h3>
<span>{{ book.meta('author') }}</span>
{% endif %}
```

**PHP**
However, you should be careful when you use custom field plugins like Advanced Custom Fields, where a lot of processing can happen in filters (like with repeaters or flexible content fields). Here, you might want to cache the result of the meta function either in PHP or in Twig:

```php
$my_custom_field = $post->custom['my_custom_field'];
```twig
{% set gear_items = post.meta('gear_items') %}

{% if gear_items %}
<h2>Gear Items</h2>

{% for gear in gear_items %}
<h3>{{ gear.brand_name }}</h3>

{% for gear_feature in gear.features %}
<li>{{ gear_feature }}</li>
{% endfor %}
{% endfor %}
{% endif %}
```

You might also think that you could load all meta values by not passing a field name and access all required values from there. You shouldn’t do this either, because then a lot of filters might run that you won’t need.

**DON’T DO THIS**

```twig
{% set meta = book.meta() %}

{% if meta.author %}
<h3>Author</h3>
<span>{{ meta.author }}</span>
{% endif %}
```

## Site options

You can also get site options directly through their name. Here’s an example to retrieve the admin email address:
You can get site options through a similar method. Instead of `meta()`, it’s called `option()`. Here’s an example to retrieve the admin email address:

```twig
{{ site.option('admin_email') }}
```

For site options, it’s also possible to access it directly through its name:

```twig
{{ site.admin_email }}
```

Please be aware that using this might also [conflict with existing Timber methods](#conflicts-with-timber-methods). As a workaround, you can also use the `custom` property or the `meta()` method.
Please be aware that using this might conflict with existing Timber methods on the `Timber\Site` object. That’s why the `option()` method is the preferred way to retrieve site options.

## Query by custom field value

Expand All @@ -103,9 +177,9 @@ This example that uses a [WP_Query](http://codex.wordpress.org/Class_Reference/W
```php
$args = array(
'numberposts' => -1,
'post_type' => 'post',
'meta_key' => 'color',
'meta_value' => 'red'
'post_type' => 'post',
'meta_key' => 'color',
'meta_value' => 'red',
);

$context['posts'] = new Timber\PostQuery($args);
Expand Down
53 changes: 33 additions & 20 deletions docs/integrations/advanced-custom-fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ aliases:
- /guides/acf-cookbook
---

Timber is designed to play nicely with (the amazing) [Advanced Custom Fields](http://www.advancedcustomfields.com/). It’s not a requirement to work with it, of course.

While data saved by ACF is available via `{{ post.my_acf_field }}`, you will often need to do some additional work to get back the _kind_ of data you want. For example, images are stored as image IDs, which you might want to translate into a specific image object. Read on to learn more about those specific exceptions.
Timber is designed to play nicely with the amazing [Advanced Custom Fields](http://www.advancedcustomfields.com/) plugin.

## Getting data from ACF

If you’ve worked with ACF before, you’re use to use `get_field( 'my_acf_field' )` all the time. In Timber, getting data from ACF works the same way as getting WordPress's native meta data in general:
If you’ve worked with ACF before, you’re use to use `get_field( 'my_acf_field' )` all the time. In Timber, getting data from ACF works the same way as getting WordPresss native meta data in general:

**Twig**

Expand All @@ -27,6 +25,8 @@ If you’ve worked with ACF before, you’re use to use `get_field( 'my_acf_fiel
$meta = $post->meta( 'my_acf_field' );
```

### Unformatted values

In ACF, all values are filtered. If you want to use unfiltered, raw values from the database, you’re probably used to using the third parameter for `get_field()`, which is called `$format_value`. This defaults to true. In Timber, you’d pass it like so:

**Twig**
Expand All @@ -43,10 +43,19 @@ $meta = $post->meta( 'my_acf_field', [
] );
```

You can also use the **faster `raw_meta()` method**, which accesses values directly from the database and bypasses any ACF filters:

**Twig**

```twig
{{ post.raw_meta('my_acf_field') }}
```

## WYSIWYG Field (and other requiring text)

```twig
<h3>{{ post.title }}</h3>

<div class="intro-text">
{{ post.meta('my_wysiwyg_field') }}
</div>
Expand Down Expand Up @@ -75,7 +84,7 @@ This is where we’ll start in PHP.
$post = new Timber\Post();

if (isset($post->hero_image) && strlen($post->hero_image)){
$post->hero_image = new Timber\Image($post->hero_image);
$post->hero_image = new Timber\Image($post->hero_image);
}

$context = Timber::context();
Expand Down Expand Up @@ -129,13 +138,13 @@ You can access repeater fields within Twig files:
```twig
<h2>{{ post.title }}</h2>
<div class="my-list">
{% for item in post.meta('my_repeater') %}
<div class="item">
<h4>{{ item.name }}</h4>
<h6>{{ item.info }}</h6>
<img src="{{ Image(item.picture).src }}" />
</div>
{% endfor %}
{% for item in post.meta('my_repeater') %}
<div class="item">
<h4>{{ item.name }}</h4>
<h6>{{ item.info }}</h6>
<img src="{{ Image(item.picture).src }}" />
</div>
{% endfor %}
</div>
```

Expand All @@ -145,7 +154,7 @@ When you run `meta` on an outer ACF field, everything inside is ready to be trav

```twig
{% for item_outer in post.meta('outer') %}
{{item_outer.title}}
{{ item_outer.title }}

{% for item_inner in item_outer.inner_repeater %}
{{ item_inner.title }}
Expand Down Expand Up @@ -219,25 +228,29 @@ Similar to nested repeaters, you should only call the `meta` method once when yo

## Options Page

**PHP**

```php
$context['site_copyright_info'] = get_field('copyright_info', 'options');
$context['site_copyright_info'] = get_field( 'copyright_info', 'options' );

Timber::render('index.twig', $context);
```

**Twig**

```twig
<footer>{{site_copyright_info }}</footer>
<footer>{{ site_copyright_info }}</footer>
```

### Get all info from your options page

```php
$context['options'] = get_fields('options');
$context['options'] = get_fields( 'options' );

Timber::render('index.twig', $context);
Timber::render( 'index.twig', $context );
```

ACF Pro has a built in options page, and changes the `get_fields('options')` to `get_fields('option')`.
ACF Pro has a built in options page, and changes the `get_fields( 'options' )` to `get_fields( 'option' )`.

```twig
<footer>{{ options.copyright_info }}</footer>
Expand All @@ -249,7 +262,7 @@ To use any options fields site wide, add the `option` context to your **function

```php
<?php
add_filter( 'timber/context', 'global_timber_context' );
add_filter( 'timber/context', 'global_timber_context' );

/**
* Filters global context.
Expand Down Expand Up @@ -281,7 +294,7 @@ You can grab specific field label data like so:
**single.php**

```php
$context['acf'] = get_field_objects($data['post']->ID);
$context['acf'] = get_field_objects( $data['post']->ID );
```

```twig
Expand Down