advancedforms / advanced-forms

WordPress plugin to create forms using Advanced Custom Fields
75 stars 15 forks source link

Create form post when a form is registered programmatically #127

Open git-temak opened 2 months ago

git-temak commented 2 months ago

When a form is registered programmatically, just the form is created and a form post (very important in WP use) is not created. The form post attached to each form allows the form to be used across WP in various locations, such as ACF relationship fields that use 'af_form'. The form post saves the forms to the db and allows the form to be retrieved in WP queries. This extends the usage of forms created programmatically and is very handy for scalability.

This also means programmatically registered forms don't need to be added to the array of valid forms manually image

A working solution to extend the af_register_form() function can be found below:


 function af_register_form( $form ) {
    global $af_registered_forms;

    if ( ! $af_registered_forms || ! is_array( $af_registered_forms ) ) {
        $af_registered_forms = array();
    }

    $form = af_get_valid_form( $form );

        if ( $form ) {
                $form_key = $form['key'];
                $existing_form = af_get_form( $form_key );

                // if form post exists, update the form
                if ( $existing_form ) {
                    $form_post_id = get_post_meta( $existing_form->ID, 'form_post_id', true );
            af_form_to_post( $form, $form_post_id );
                    return $form;
                } else {
                    // create new form post when a form is created programmatically
                    $form_title = $form['title'] ?: 'New Programmatic Form - ' . date('d/m/y h:i:s');
                    if (af_is_valid_form_key($form_key)) {
                        $form_post = wp_insert_post([
                            'post_title' => $form_title,
                            'post_type' => 'af_form',
                            'post_status' => 'publish',
                            'post_content' => '',
                        ]);
                    }

                    // save the post id to the form array and post object so it can be used to retrieve the form post later to check for if the programmatic form has a form post
                    if ( !is_wp_error( $form_post ) ) {
                        $form['form_post_id'] = $form_post;
                        update_post_meta( $form_post, 'form_key', $form_key );
                        update_post_meta( $form_post, 'form_post_id', $form_post );
                        update_post_meta( $form_post, 'form_create_entries', $form['create_entries'] ); //ensure entries are created if ticked in options
                    }
                }

        $af_registered_forms[ $form_key ] = $form;
    }

    return $form;
} 
git-temak commented 2 months ago

With the above solution, one thing to note to users is that if a form that was registered programmatically is no longer needed and the code has been removed from the plugin/theme where it was registered, the corresponding form post needs to be deleted from the site manually.

mishterk commented 2 months ago

Hi @git-temak

Thanks for the suggestion here. The ability to register forms programmatically is designed specifically to allow devs to have forms that are not in the database. If we were to change this to suddenly create post objects, I'm sure I would cause a lot of problems for existing users who are avoiding have forms in the DB.

The other issue that would arise out of this is that on every single request to WordPress, that post object would be either updated or created. The af/register_forms action hook runs on acf/init which runs on WordPress' core init hook.

What is the ultimate goal here? In my mind, if you want a form to be in the database, you just create it via the UI and it's there. If you don't want the form to be in the database, you can register it using PHP. It sounds like you are in need of a way to create forms for the UI using PHP. Is that correct? If so, I'm keen to understand why.

Cheers, Phil

git-temak commented 2 months ago

Hi @mishterk

I see what you mean, I guess this is more case-specific. In this case, im using the forms on a portal where there are managers (company staff) that set up different parts of the platform and they can sort of pick from a range of options. One of these options is an ACF relationship field on the backend of the site and it assigns a form to a post. In the ACF relationship field, a WP Query is used to retrieve all the forms from the posttype of 'af_form'. This is the default ACF behaviour and there is a hook to adjust the args passed to the query https://www.advancedcustomfields.com/resources/acf-fields-relationship-query/ but ACF still uses the WP query to display the list of forms.

Due to how AF is registered programmatically, these forms dont show up since they dont have an attached form post so aren't saved as forms to the DB.

In this use case, i'm taking advantage of the programmatic approach to set up fields and forms for easier management, while leaving the managers to handle how and where the forms are being used. Maybe some sort of extension might be a way around this sort of use case so users can choose if programmatic forms should be saved and visible in WP queries or should simply remain fully code-managed as it currently is.

Love all the work with the plugin!

mishterk commented 2 months ago

@git-temak,

I have some thoughts on how you could work around this.

  1. I would have a look to see if there is another filter that allows you to modify the results of the WP_Query executed by the ACF field. If you can filter the results before they are returned to the browser, you could basically get a list of registered forms, create mock WP_Post objects using information from those forms, and then append them to the results set. If ACF doesn't provide a filter to modify the returned post results, you could of course filter the WP_Query directly, but you'll probably want to use the query args filter to set a flag to identify the query object as a means of isolating callbacks on core filters. I usually use something like 'pk_query_id' => 'my_something_query' and then check for that in my hooked query callbacks.

  2. Perhaps a better/simpler alternative could be to use something like a simple select field and dynamically control the available values in that select field using ACF's filters. This is something I've done quite a bit over the years and it work well.

git-temak commented 1 month ago

Hi @mishterk thanks for the suggestions and the helping looking into this!

  1. No worries at all, I'll appreciate if you can find a filter that could work to modify the results from ACF to include the programmatic forms as well.
  2. This might have worked but we currently have 700+ posts using the relationship field and a new solution will require a lot of reworking , which is why I was looking for a solution that's compatible with the existing setup. At the moment, the snippet I posted works fine and i'm able to use both the UI and PHP to register new forms and that works fine with the relationship field. Of course, updating the plugin will mean the code is reverted but would be helpful also if there were some additional filters or an addon like this that could extend the core plugin further.

Appreciate all your work!