wp-graphql / wp-graphql-acf

WPGraphQL for Advanced Custom Fields
https://wpgraphql.com/acf
627 stars 123 forks source link

Field groups not in schema when location is a page template #76

Closed nathobson closed 3 years ago

nathobson commented 5 years ago

When a field group is tied to a page template for its location, it seems as though it is not available to pages in the GraphQL schema. I also tried combining the rules so it's set as page AND page template === whatever but that still didn't work.

Any possibility of this landing in the future? I did see you can use page === page name as the location but usually we want to tie them to template so either us or a client can create a new page based off of a template without having to change field group settings.

intelligence commented 5 years ago

Had this issue myself and asked for a workaround on Slack and got this helpful advice:

I added this custom method to the Config class and added it to the list of calls here: https://github.com/wp-graphql/wp-graphql-acf/blob/master/src/class-config.php#L39. This is probably a dirty way of getting this to work, but it works! It mostly follows how add_acf_fields_to_individual_posts works, so it also exposes the field groups to the general Page type in GraphQL. For example: if you have a field group assigned to only the frontpage, it'll be queryable for all pages. I assume this is fine as that is how add_acf_fields_to_individual_posts field groups work, but just be aware.

Code snippet: add_acf_fields_to_pages.txt

Don't forget to add $this->add_acf_fields_to_pages(); in the init function

JodiWarren commented 4 years ago

I've got a particular variation on this issue. I have a field group which has the following location rules:

<?php
$location = [
    0 => [
        0 => [
            'param'    => 'post_type',
            'operator' => '==',
            'value'    => 'post',
        ],
    ],
    1 => [
        0 => [
            'param'    => 'post_type',
            'operator' => '==',
            'value'    => 'page',
        ],
        1 => [
            'param'    => 'page_type',
            'operator' => '!=',
            'value'    => 'posts_page',
        ],
        2 => [
            'param'    => 'page_type',
            'operator' => '!=',
            'value'    => 'front_page',
        ],
        3 => [
            'param'    => 'page_template',
            'operator' => '!=',
            'value'    => 'page-locations.php',
        ],
    ],
];

In English, show if:

add_acf_fields_to_post_object_types() happily adds it to the post post type. However, this relies on the ACF core function, acf_get_field_groups(). It's called like so:

// $post_type is each of your registered post types, called one by one
$field_groups = acf_get_field_groups(
    [
        'post_type' => $post_type,
    ]
);

This doesn't return the field group for the page post type, because there are some exceptions to that rule.

I took a deep dive into the ACF internals, and into how ACF resolves the visibility of a particular field group based on a set of location rules.

Here are the main functions that runs in order to determine visibility of the field group given the context:

  1. acf_get_field_group_visibility()
  2. acf_get_location_screen() - this returns ['lang' => '', 'ajax' => false, 'post_type' => 'post',] for the /graphql endpoint
  3. If there are multiple AND rules for a location, it runs acf_match_location_rule() against each of them, and fails if one doesn't pass.
  4. Finally, it runs the field group rules against the following filters:
$result = apply_filters( "acf/location/match_rule/type={$rule['param']}", $result, $rule, $screen, $field_group );
$result = apply_filters( "acf/location/match_rule", $result, $rule, $screen, $field_group );
$result = apply_filters( "acf/location/rule_match/{$rule['param']}", $result, $rule, $screen, $field_group );
$result = apply_filters( "acf/location/rule_match", $result, $rule, $screen, $field_group );

I plan on experimenting with these filters, to see if I can get the rules to pass for all fields which are registered on the page post type, and will report back if so. I just thought I'd post up some of my research in case it helps anyone else.

JodiWarren commented 4 years ago

I ended up with a pretty crude solution to this, but one that works with the plugin out of the box:

function whitelistedFieldGroups( $result, $rule, $screen, $field_group) {
    $graphqlFieldNames = [ 'homepage', 'pageFields' ];

    if (
        in_array($field_group['graphql_field_name'], $graphqlFieldNames)
        && $screen['post_type'] === 'page'
    ) {
        return true;
    }

    return $result;
}
add_filter('acf/location/rule_match', 'whitelistedFieldGroups', 10, 4);

Hopefully this might help someone else!

izzygld commented 4 years ago

@JodiWarren can you post a sample of the code here please?

JodiWarren commented 4 years ago

@izzygld This is all I have (and need) in my current setup:

function whitelistedFieldGroups( $result, $rule, $screen, $field_group) {
    $graphqlFieldNames = [ 'homepage', 'pageFields' ];

    if (
        in_array($field_group['graphql_field_name'], $graphqlFieldNames)
        && $screen['post_type'] === 'page'
    ) {
        return true;
    }

    return $result;
}
add_filter('acf/location/rule_match', 'whitelistedFieldGroups', 10, 4);

This also requires a location rule that's tied to a particular post type. So if you only want a set of fields to appear on a certain page template, you'll need to set it to a combination location rule of post type and page template.

Why this works:

Let's take a field group with the location rules of:

post_type === 'post'
OR
post_type === 'page' AND 'post_template' !== 'locations.php'

The WPGraphQL ACF plugin loops through all post types and fetches their related field groups like so:

function add_acf_fields_to_post_object_types() {
    /**
     * Get a list of post types that have been registered to show in graphql
     */
    $graphql_post_types = get_post_types( [ 'show_in_graphql' => true ] );

    // Do some checks

    /**
     * Loop over the post types exposed to GraphQL
     */
    foreach ( $graphql_post_types as $post_type ) {

        /**
         * Get the field groups associated with the post type
         */
        $field_groups = acf_get_field_groups(
            [
                'post_type' => $post_type,
            ]
        );

        // Do the magic of adding those field groups to GraphQL etc
    }
}

We basically hijack one of the checks that happens within the function add_acf_fields_to_post_object_types(). By default, our example field group will be returned on a post, but the extra rules on the page rule mean that it won't be returned. That's because it fails the acf/location/rule_match filter that it relies on internally. By hooking into that filter, we can make our own rules.

The main takeaway should not necessarily be the exact code that I used, but that this filter allows you to adjust what gets included.

esamattis commented 4 years ago

We workaround this issue by conditionally skipping the rule definitions on the graphql context.

Using acf-codifier:

if (!is_graphql_http_request()) {
    $rule_group->add_rule( 'page_template', '==', 'template.php' );
}

We did discuss some solutions to this with @jasonbahl yesterday on Slack https://wp-graphql.slack.com/archives/C3NM1M291/p1575991879025000

joaokrabbe commented 4 years ago

We workaround this issue by conditionally skipping the rule definitions on the graphql context.

Using acf-codifier:

if (!is_graphql_http_request()) {
  $rule_group->add_rule( 'page_template', '==', 'template.php' );
}

We did discuss some solutions to this with @jasonbahl yesterday on Slack https://wp-graphql.slack.com/archives/C3NM1M291/p1575991879025000

Could you detail this application better? I would like to use WPGraphql to return the fields of an ACF group "FrontPage". But I never used the acf-codifier. I'm not able to apply it properly.

esamattis commented 4 years ago

ACF Codifier discussion is offtopic for this thread but my guess is that you are missing show_in_graphql if you see the fields on wp-admin but not in graphql. It can be done with the wpgraphql_acf_should_field_group_show_in_graphql filter:

add_filter(
        'wpgraphql_acf_should_field_group_show_in_graphql',
        function ( $show, $field_group ){
            if ( "my_group" === $field_group['key'] ) {
                return true;
            }
            return $show;
        },
        10,
        2
);

...but now that I think of it this might work too

$field_group->show_in_graphql = true;
cfxd commented 4 years ago

I tried @JodiWarren's setup (thank you!) but it only got me 99% of the way there because it has the unfortunate side effect of displaying all ACF fields on all pages in the admin edit screens. Then I caught a great tidbit from @esamattis's solution that brought it home, so I check for is_graphql_http_request(), like this:

function expose_acf_to_graphql_only($result, $rule, $screen, $field_group) {
  if(!is_graphql_http_request()) {
    return $result;
  }

  $page_template_acf_groups = [
    'acfPageTemplateHome',
    'acfPageTemplateAbout',
    'acfPageTemplateContact',
  ];

  if(in_array($field_group['graphql_field_name'], $page_template_acf_groups) && $screen['post_type'] === 'page') {
    return true;
  }

  return $result;
}
add_filter('acf/location/rule_match', __NAMESPACE__.'\\expose_acf_to_graphql_only', 10, 4);

I was also able to extrapolate this and apply it to other field groups, such as a field group that applies to a post type in a certain taxonomy. Works great until there's a more solid solution within the plugin itself!

AbeCole commented 4 years ago

I believe this is similar to an issue I've just resolved, take a look at my pull request https://github.com/wp-graphql/wp-graphql-acf/pull/134

sirichards commented 4 years ago

Hi, is this going to be fixed? seems like quite an important feature? this fix does the trick but it would be nice to be implemented into the main build. Thanks

AbeCole commented 4 years ago

@sirichards Yes there is discussion about a major change in how you specify which fields are added to which GraphQL types, this would address this issue, see details https://github.com/wp-graphql/wp-graphql-acf/issues/135

madderlake commented 4 years ago

Typo? This finally worked for me after I changed "page_type" to "post_type" here: $allowed_page_params = [ '**page_type**', 'page_template', ];

ljanecek commented 3 years ago

Had this issue myself and asked for a workaround on Slack and got this helpful advice:

I added this custom method to the Config class and added it to the list of calls here: https://github.com/wp-graphql/wp-graphql-acf/blob/master/src/class-config.php#L39. This is probably a dirty way of getting this to work, but it works! It mostly follows how add_acf_fields_to_individual_posts works, so it also exposes the field groups to the general Page type in GraphQL. For example: if you have a field group assigned to only the frontpage, it'll be queryable for all pages. I assume this is fine as that is how add_acf_fields_to_individual_posts field groups work, but just be aware.

Code snippet: add_acf_fields_to_pages.txt

Don't forget to add $this->add_acf_fields_to_pages(); in the init function

Is there some merge request? I do not understand why does not include yet.

maweo-mathis commented 3 years ago

Is there a solution for this problem? The ACF-Fields aren't in the schema, when the condition is set to a specific page-template..

ljanecek commented 3 years ago

Is there a solution for this problem? The ACF-Fields aren't in the schema, when the condition is set to a specific page-template..

Sure, this comment: https://github.com/wp-graphql/wp-graphql-acf/issues/76#issuecomment-555733586

maweo-mathis commented 3 years ago

Is there a solution for this problem? The ACF-Fields aren't in the schema, when the condition is set to a specific page-template..

Sure, this comment: #76 (comment)

So I have to edit the class-config.php?

AbeCole commented 3 years ago

I would try using https://github.com/wp-graphql/wp-graphql-acf/pull/207 if possible, that seems like the most relevant/new solution and probably the one that will be used going forward.

@maweo-mathis any of the 'workaround' solutions such as the one suggested here (or my own one: https://github.com/wp-graphql/wp-graphql-acf/pull/134) require you to edit src/class-config.php

jasonbahl commented 3 years ago

This is addressed by v0.5.0 (#250).

With this release, we can now assign field groups to templates and we can see that it's properly assigned to the GraphQL Type for the template:

Screen Shot 2021-04-20 at 2 12 05 PM

Then, we can see the Field Group available on the Template in the Schema:

Screen Shot 2021-04-20 at 2 13 15 PM

Then, we can query like so:

{
  posts {
    nodes {
      id
      template {
        __typename
        ... on Template_AboutUs {
          acfDocs { # <-- This is the ACF Field Group assigned to the Template location
            text
          }
        }
      }
    }
  }
}
rburgst commented 3 years ago

It appears that this does not work as planned yet, see #251

jasonbahl commented 3 years ago

@rburgst see #253. If there's still issues after that, please let me know and provide details on reproducing 🙏🏻

ivanyankov commented 1 year ago

Hey team, I still have the same issue. I am using the latest version of the plugin and also the latest version of the graphql plugin. When I assign the ACF fields to a page template they appear in the admin page but they does not appear in the graphql schema. My wordpress version is 6.2.2

ivanyankov commented 1 year ago

@jasonbahl I can provide more details regarding https://github.com/wp-graphql/wp-graphql-acf/issues/76#issuecomment-1563970080 if needed or open a new ticket.