0

I am using the WP-STRAP Wordpress plugin boilerplate https://github.com/wp-strap/wordpress-plugin-boilerplate .

I want to send data from frontend via a form to backend with JS Fetch API. Upon request a custom post is created and populated with data from frontend like post title and so on.

Issue: I get 400/500 response error if i place my php action/functions in a class.

According to the Github page, all public-facing hooks/functionality should be placed in src -> App -> Frontend folder. I tried but it doesn't work. For example i created a file/class FrontEndRequests.php and i placed my actions/functions there, but with no luck - action isn't working upon ajax request.

So my guess is that the action/function are called via the wrong hook or due to the boilerplate structure they are called at the wrong time (too late or too soon).

I made it "work" by having the following structure but i want to do it the right way :

  1. In the main plugin file the-plugin-name.php in the root folder i added my action hooks and function :
add_action( 'init', 'my_ajax_actions');


function my_ajax_actions() {
    add_action("wp_ajax_nopriv_insert_post", 'insert_post');
    add_action("wp_ajax_insert_post", 'insert_post');
}

/**
 * Insert post with data from frontend
 *
 * @since 1.0.0
 */
function insert_post() {

    if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'fetch_fr_ajax_nonce' ) ) {
        wp_send_json_error( 'Invalid nonce' );
    }

    echo "inserting Post" . "\n";

    $page_slug = 'custom-post'; // Slug of the Post
    $new_page = array(
        'post_type'     => PostTypes::POST_TYPE['id'],              // Post Type Slug eg: 'page', 'post'
        'post_title'    => $_POST['title'], // Title of the Content
        'post_content'  => 'Test Page Content', // Content
        'post_status'   => 'pending',           // Post Status
        'post_name'     => $page_slug       // Slug of the Post
    );

    if (!get_page_by_path( $page_slug, OBJECT, 'page')) { // Check If Page Doesn't Exits
        $new_page_id = wp_insert_post($new_page);
    }

    echo "Post Inserted successfully" . "\n";

        wp_die();
    }
  1. Script localization in Frontend folder -> Enqueue.php file :
        global $wp_query;

        // localize script and send variables
        wp_localize_script( 'ajax-frontend', 'fetch_fr_ajax',
            [
                'ajax_url'  => admin_url( 'admin-ajax.php' ),
                'wp_query_vars' => $wp_query->query_vars,
                'nonce' => wp_create_nonce( 'fetch_fr_ajax_nonce' ),
            ]
        );
  1. In assets -> src -> js i have the JS Fetch file :
'use strict'

window.addEventListener('load', function () {
    const sendCTA = document.querySelector('.insert-post-btn');

    const data = new FormData()

    data.append('action', 'insert_post')
    data.append( 'nonce', fetch_fr_ajax.nonce);
    data.append('title', 'Post from frontend');

    const params = new URLSearchParams(data)

    if (sendCTA) {
        sendCTA.addEventListener('click', function () {
            console.log('.post-btn clicked')

            fetch(fetch_fr_ajax.ajax_url, {
                method: 'POST',
                mode: 'same-origin',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                credentials: 'same-origin',
                body: params,
            })
            .then(response => {
                if (response.ok) {
                    return response.text();
                }
                throw new Error('Network response was not ok');
            })
            .then(data => {
                console.log(data); // handle the response from the server
            })
            .catch(error => {
                console.error(error); // handle any errors that occur
            });
        })
    }
})

For demo purposes i added a button in templates -> test-template.php and upon click via Fetch Api the respective action is called and the post is inserted into Wordpress Custom Post Type.

How can i structure it in the OOP way that the boilerplate is made ?

Yes, i could ask this here https://github.com/wp-strap/wordpress-plugin-boilerplate but it seems that project has been abandoned, as nobody replies there. Please help !

5
  • "I get 400/500 response error" - there should be more detail in the server error log, the WordPress debug log or the PHP error log if separate. Please can you find those?
    – Rup
    Apr 19, 2023 at 11:38
  • But it shouldn't matter where the code goes as long as it's included/required and the namespaces work OK.
    – Rup
    Apr 19, 2023 at 11:41
  • @Rup usually i received 400 for the case when logged-in and 500 for the case when i wasn't logged (for example incognito). If it doesn't matter where the action/function are placed then why this code works well if placed in functions.php of any theme and the root plugin boilerplate file the-plugin-name.php of this boilerplate . I still think that the boilerplate structure and the order of how things are called matter in this case. Maybe other classes are called too late ?
    – Ion T
    Apr 19, 2023 at 12:27
  • Huh. I could believe there were namespace issues but it looks like it can find my_ajax_actions by name but not insert_post just below it, so there mustn't be. Not sure.
    – Rup
    Apr 19, 2023 at 16:21
  • Oh you've deleted the error message now? It might be useful to edit that into the question. You don't need the stack trace as it's just the calling-a-hook stack trace.
    – Rup
    Apr 19, 2023 at 16:35

1 Answer 1

0

I "resolved" it in the following way:

In src -> App -> Frontend -> FrontRequestHandler.php i added the handler function that will insert the Custom Post.

<?php
/**
 * Class Enqueue
 *
 * @package DemoPlugin\App\Frontend
 * @since 1.0.0
 */
class FrontRequestHandler extends Base {

    /**
     * Initialize the class.
     *
     * @since 1.0.0
     */
    public function init() {
        /**
         * This frontend class is only being instantiated in the frontend as requested in the Bootstrap class
         *
         * @see Requester::isFrontend()
         * @see Bootstrap::__construct
         *
         * Add plugin code here
         */
    }

    /**
     * Insert post with data from frontend
     *
     * @since 1.0.0
     */
    public function insert_post() {

    // Prevent processing requests from external sources
    check_ajax_referer( 'fetch_fr_ajax_nonce', 'nonce' );

    if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'fetch_fr_ajax_nonce' ) ) {
        wp_send_json_error( 'Invalid nonce' );
    }

    echo "inserting Demo Plugin Post" . "\n";

    $page_slug = 'custom-post'; // Slug of the Post
    $new_page = array(
        'post_type'     => PostTypes::POST_TYPE['id'],              // Post Type Slug eg: 'page', 'post'
        'post_title'    => sanitize_text_field($_POST['title']),    // Title of the Content
        'post_content'  => 'Test Page Content', // Content
        'post_status'   => 'pending',           // Post Status
        'post_name'     => $page_slug       // Slug of the Post
    );

    if (!get_page_by_path( $page_slug, OBJECT, 'page')) { // Check If Page Doesn't Exits
        $new_page_id = wp_insert_post($new_page);
    }

    echo "Demo Plugin Post Inserted successfully" . "\n";

    wp_die();
    }
    
}

In src -> App -> General -> PostTypes.php :

In the init function i added the action called on wp_loaded hook (other hooks proved to not work the same).

public function init() {
        /**
         * This general class is always being instantiated as requested in the Bootstrap class
         *
         * @see Bootstrap::__construct
         *
         * Add plugin code here
         */
        add_action( 'wp_loaded', [$this, 'frontAjaxRequest'] );
    }

Then below in the same PostTypes.php file i would have the callback function which contains the ajax action hooks (wp_ajax_nopriv & wp_ajax for both logged and non logged users) :

/**
     * Add actions for the frontend ajax request
     *
     * @since 1.0.0
     */
    function frontAjaxRequest() {
        $frontRequestHandler = new FrontRequestHandler();

        add_action("wp_ajax_nopriv_insert_post", [ $frontRequestHandler, 'insert_post']);
        add_action("wp_ajax_insert_post", [ $frontRequestHandler, 'insert_post']);  
    }

The game changer seems to be the hook wp_loaded - now the ajax handler works for all users. Also placing the frontAjaxRequest in the init() function of the PostTypes.php also had some impact. I tried placing this function in other Classes/Files and it didn't work. The previous init hook was working only for the non-logged users.

I am not sure this is the best way to do it, but it works, the code logic is separated.

1
  • @Rup What are your thoughts on this ? Again, i am not sure this is the best way to do it...
    – Ion T
    Apr 20, 2023 at 11:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.