0

Context

Learning plugin creation, i want to save the content of a div each time the user click outside of it. In a nutshell, the purpose is to create a text backup of a notepad.

I'm working in Local.

Steps

  1. Some text is written in a div (thanks to Trumbowyg.js).
  2. On div blur event, the plugin sends the content data with ajax.
  3. Plugin function opens a json file, and update its content with the new text.

Issues

Several. I think i'm blocked at step 3. Hours of info search made me lost my mind.

Update

I tried to use wp_remote_get() instead of file_get_contents(), and it returns an error : [0] => A valid URL was not provided. However i can't figure out what path it needs.

jdr.js

    $path = plugin_dir_path ( __FILE__ ) . "assets/json/notes.json";
    // $path = plugin_dir_url ( __FILE__ ) . "assets/json/notes.json";
    // $path = ABSPATH . "assets/json/notes.json";
    // $path = plugins_url("/jdr/assets/json/notes.json");
    
    $args_for_get = array(
        'stream' => true,
        'sslverify' => false,
        'filename' => $chemin3,
        'reject_unsafe_urls' => true,
        'method' => "POST"
    );
    $response = wp_remote_get( $chemin, $args_for_get );
    print_r(response);

$response in console

WP_Error Object
(
  [errors] => Array
    (
        [http_request_failed] => Array
            (
                [0] => L’URL fournie n’est pas valide. // A valid URL was not provided
            )

    )

[error_data] => Array
    (
    )

)

My code

Wordpress plugin structure:
plugins/
       -jdr.php
       -lbh-jdr/
               -inc/
                   -enqueue-scripts.php
                   -fonctions.php (= functions)
               -assets/
                      -js/
                           -plugin-jdr.js
                      -json/
                           -notes.json

jdr.php

<?php
/*
Plugin Name: jdr
etc..
*/

function lbh_jdr() {

  defined('MYPLUGIN_DIR') or define('MYPLUGIN_DIR', plugin_dir_path( __FILE__ ));

  include_once( MYPLUGIN_DIR . '/inc/enqueue-scripts.php');
  include_once( MYPLUGIN_DIR . '/inc/fonctions.php');

  $texte = $_REQUEST["texte"];
  $sessionPerso = $_REQUEST["sessionPerso"];
  $appelFonction = $_REQUEST["ajaxAction"];

  switch($appelFonction) {
    case 'sauvegarderNote' : do_action("sauvegarderNote", $texte); break;
  }

  if(defined('DOING_AJAX') && DOING_AJAX) die();
}
add_action("wp_ajax_lbh_jdr", "lbh_jdr");
add_action("wp_ajax_nopriv_lbh_jdr", "lbh_jdr");
?>

enqueue-scripts.php

note : If i encapsulate enqueued script + add_action, it doesn't work. Can't figure out why

The proper way :

function my_script_enqueuer() {
  wp_enqueue_script( "jdr-script", plugins_url("jdr/assets/js/plugin-jdr.js", __FILE__), 
  array("jquery"), "", true );
  wp_localize_script( "jdr-script", "myAjax", array(
    "ajaxurl" => admin_url( "admin-ajax.php"
    )));
}
add_action( 'wp_enqueue_scripts', 'my_script_enqueuer' );

The working-for-me way :

wp_enqueue_script( "jdr-script", plugins_url("jdr/assets/js/plugin-jdr.js", __FILE__), 
array("jquery"), "", true );
wp_localize_script( "jdr-script", "myAjax", array(
    "ajaxurl" => admin_url( "admin-ajax.php"
    )));

Fonctions.php

add_action('wp_ajax_nopriv_sauvegarderNote', 'sauvegarderNote');
add_action('wp_ajax_sauvegarderNote', 'sauvegarderNote');

function sauvegarderNote() { // sauvergarderNote means saveNote

  $json = getJsondata();
  $jsonTableau = $json[0]; // Tableau means array
  $jsonChemin = $json[1];  // Chemin means path

  $jsonTableau["notes"] = [$texte];
  array_push($jsonTableau, $texte);

  $newJsonString = json_encode($jsonTableau, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | 
  JSON_UNESCAPED_SLASHES);
  file_put_contents($jsonChemin, $newJsonString);
}

 function getJsondata() {

   $chemin = plugin_dir_url( __FILE__ ) . "inc/json/notes.json";
   $jsonString = file_get_contents($chemin);
   $tempArray = json_decode($jsonString, true);
   return $tempArray;
}

plugin-jdr.js

jQuery( document ).ready( function() {
  
    let newData = {
        action: "lbh_jdr",
        ajaxAction: "sauvegarderNote",
        texte: jQuery("#blocNotes").text(),
        sessionPerso: sessionPerso
    };

    jQuery.ajax({
        url: myAjax.ajaxurl,
        data: newData,
        type: 'post',
        error: function(e) {
            console.log(e);
        }
    })
        .done(function(response) {
            console.log(response);
        });
});

Questions

So... Why my enqueue_scripts does not work if i do it the way intended ? Why file_get_contents returns nothing ? What is the proper way to read/write in a json file using wordpress + Ajax ?

Thank you by advance, i will stay tuned !

2
  • I see you used the old Admin AJAX API to implement your handler, rather than a modern REST API endpoint. I also notice you called plugin_dir_url, this will return a URL rather than a file path, and I also notice you did not pass it the plugin file, but rather a file in a subfolder ( fonctions.php ) which is an incorrect usage. It must be the main plugin file, if it is not the main plugin file it will not work – Tom J Nowell Nov 14 '20 at 14:43
  • @TomJNowell I know that my code is a little bit messy, but it is fonctions.php. So i figure out the main issue : file_put_contents didn't work because of an URL conflict, my local website was in https. Now i can get data from my json file, but file_put_contents still does nothing. Could you give me a good link for REST API ? In hours of search in SO and other forums, i never seen that way to use ajax in wordpress ! – Lbh Nov 14 '20 at 14:49
0

Here is your main problem:

chemin = plugin_dir_url( __FILE__ ) . "inc/json/notes.json";

There are 5 critical mistakes in the code:

  1. file_get_contents requires a file path, but you're asking for a URL by using plugin_dir_url, not all servers support remote URLs in file_get_contents for security reasons, and if they do, this would be a big performance hit as it involves a HTTP request
  2. plugin_dir_... type functions need the main plugin file to be passed as a parameter so it knows which plugin you need the value for, but __FILE__ is not the main plugin file path, you're calling it in fonctions.php! This is a huge mistake, and not how these functions work.
  3. The same mistake is made in lots of other places, e.g. all uses of the function plugins_url
  4. Your plugins structure is highly unusual, with a single file plugin that then references a plugin folder that contains no plugins
  5. You're pulling $texte out of thin air, in your AJAX handler, you can't just use a variable that doesn't exist, PHP will replace it with '', it has to come from somewhere

So first, you need to use the correct function, use plugin_dir_path instead:

plugin_dir_path( string $file )

Get the filesystem directory path (with trailing slash) for the plugin FILE passed in.

https://developer.wordpress.org/reference/functions/plugin_dir_path/

Next, we need to give it the actual plugin file.

For this you will need to retrieve and store this value inside the main plugin file, and it must be done in that file. According to your folder/file list, this would be jdr.php as that's the file containing the plugin header.

I believe the easiest way to do this for you is something like this: define( 'JDR_MAIN_FILE', __FILE__ );, then using JDR_MAIN_FILE instead of __FILE__ when calling these functions, like this:

chemin = plugin_dir_path( JDR_MAIN_FILE ) . "inc/json/notes.json";

Then, we need to do the same to every instance of plugins_url

So this:

wp_enqueue_script( "jdr-script", plugins_url("jdr/assets/js/plugin-jdr.js", __FILE__ ), 
array("jquery"), "", true );

Becomes this:

wp_enqueue_script( "jdr-script", plugins_url("assets/js/plugin-jdr.js", JDR_MAIN_FILE ), 
array("jquery"), "", true );

Notice the jdr/ was removed from the path parameter.

Finally, your plugins folder structure is broken.

Plugins follow 1 of 2 patterns:

  • A single file in the /plugins/ folder that is self contained
  • A folder that contains a plugin PHP file, as well as other supporting files such as JS/CSS/PHP etc, as well as a readme.txt

You appear to have done neither. As a result none of the plugin_ and plugins_ functions will work correctly.

Move your jdr.php into the lbh-jdr folder and adjust the include paths in jdr.php and remove any reference to jdr/ in the URL parameters. You will need to go into WP Admin and reactivate your plugin after doing this.

A final note

I know that you mention your code is messy, and you might think that's ok, but it is not. This isn't like when your bedroom is messy but nobody comes to visit, the mess has actively contributed things being broken, and is what we would term technical debt.

Bonus Note

You should invest in using the REST API, you can make a request just the same. Instead of making it to an obscure PHP file in the wp-admin folder and passing an action parameter, you make it to a pretty permalink URL, e.g. example.com/wp-json/hurlemort/v1/example:

add_action( 'rest_api_init', function () {
        register_rest_route( 'hurlemort/v1', '/example/', array(
                'methods' => 'GET',
                'callback' => 'hurlemorts_endpoint'
        ) );
} );

function hurlemorts_endpoint( $request ) {
    return "Hello World!";
}

It will JSON encode anything the callback returns automatically. Here's the javascript to fetch that example:

<script>
jQuery.ajax({
    url: <?php echo wp_json_encode( esc_url_raw( rest_url( 'hurlemort/v1/example' ) ) ); ?>
}).done(function( data ) {
    alert( data ); // prints Hello World!
});
</script>

Don't forget to re-save permalinks after you add it. You can expand the register_rest_route to call different callbacks on POST OR DELETE etc, give it a list of arguments, how to sanitise them, validate them, who has access to the endpoint, etc etc and core will do all that work for you. It even gives human readable error messages if you use it incorrectly, unlike admin ajax that just says 0 and leaves you wondering what happened.

2
  • Thank you so much for the time and the accuracy you put in this answer. I was sarcastic about my code, and i will follow your advices carefully. Plus i was reading about REST API and it looks so interesting. Tanks again. – Lbh Nov 14 '20 at 15:28
  • Glad I could help – Tom J Nowell Nov 14 '20 at 18:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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