J'ai créé un plugin qui lit un fichier CSV et importe des données dans les tables correspondantes.
Cependant, l'action semble créer une erreur:
Erreur fatale: la taille de mémoire autorisée de 134217728 octets a été épuisée (tentative d'allocation de 65015808 octets) dans /var/www/proj/wp-includes/functions.php en ligne
ce qui m'a amené à ce code dans functions.php:
function wp_ob_end_flush_all() {
$levels = ob_get_level();
for ($i=0; $i<$levels; $i++)
ob_end_flush();
}
J'ai fait un Google et suis tombé sur deux solutions populaires, les deux ne semblant pas fonctionner.
Solution 1: désactivation de zlib - ceci est déjà désactivé.
Solution 2:remove_action('shutdown', 'wp_ob_end_flush_all', 1);
La solution 2 continue à générer des erreurs, mais pas de message, ce qui n’est pas vraiment idéal.
C'est le script qui cause l'erreur:
<?php
ini_set('display_startup_errors', 1);
ini_set('display_errors', 1);
error_reporting(-1);
# load core wp fnc
require_once $_SERVER['DOCUMENT_ROOT']. '/wp-load.php';
# load db functions
require_once $_SERVER['DOCUMENT_ROOT']. '/wp-admin/includes/upgrade.php';
# load admin fnc
require_once $_SERVER['DOCUMENT_ROOT']. '/wp-content/plugins/vendor-module/admin/inc/admin-functions.php';
global $wpdb;
$admin_functions = new vendor_module_admin_functions();
# get csv
$file = $_FILES['csv'];
$name = $file['name'];
$dir = $file['tmp_name'];
# rm spaces, replace with _
$name = str_replace(' ', '_', $name);
$file_location = $_SERVER['DOCUMENT_ROOT']. '/wp-content/plugins/vendor-module/uploads/import/'. $name;
# if successfully moved, carry on, else return
$moved = ($file['error'] == 0 ? true : false);
$error = false;
if (!$moved) {
echo 'Error! CSV file may be incorrectly formatted or there was an issue in reading the file. Please try again.';
} else {
move_uploaded_file($dir, $file_location);
$id = $_POST['val'];
$type = $_POST['type'];
$table = ($type == 1 ? 'vendor_module_type_one' : 'vendor_module_type_two');
$handle = fopen($file_location, 'r');
$parts = array();
$components = array();
$i = 0;
while (($data = fgetcsv($handle, 1000, ',')) !== false)
{
if (is_array($data)) {
if (empty($data[0])) {
echo 'Error! Reference can\'t be empty. Please ensure all rows are using a ref no.';
$error = true;
continue;
}
# get data
$get_for_sql = 'SELECT `id` FROM `'. $wpdb->prefix. $table .'` WHERE `ref` = %s';
$get_for_res = $wpdb->get_results($wpdb->prepare($get_for_sql, array($data[0])));
if (count($get_for_res) <= 0) {
echo 'Error! Reference has no match. Please ensure the CSV is using the correct ref no.';
$error = true;
exit();
}
$get_for_id = $get_for_res[0]->id;
# create data arrays
$parts[$i]['name'] = $data[1];
$parts[$i]['ref'] = $data[2];
$parts[$i]['for'] = $get_for_id;
$components[$i]['part_ref'] = $data[2];
$components[$i]['component_ref'] = $data[3];
$components[$i]['sku'] = $data[4];
$components[$i]['desc'] = utf8_decode($data[5]);
$components[$i]['req'] = $data[6];
$components[$i]['price'] = $data[7];
unset($get_for_id);
unset($get_for_res);
unset($get_for_sql);
$i++;
}
}
fclose($handle);
unlink($file_location);
# get unique parts only
$parts = array_unique($parts, SORT_REGULAR);
# check to see if part already exists, if so delete
$search_field = ($type == 1 ? 'id_field_one' : 'id_field_two');
$check_sql = 'SELECT `id` FROM `'. $wpdb->prefix .'vendor_module_parts` WHERE `'. $search_field .'` = %d';
$delete_parts_sql = 'DELETE FROM `'. $wpdb->prefix .'vendor_module_parts` WHERE `'. $search_field .'` = %d';
$delete_components_sql = 'DELETE FROM `'. $wpdb->prefix .'vendor_module_components` WHERE `part_id` = %d';
$check_res = $wpdb->get_results($wpdb->prepare($check_sql, array($id)));
if ($check_res) {
$wpdb->query($wpdb->prepare($delete_parts_sql, array($id)));
}
$part_ids = $admin_functions->insert_parts($parts, $type);
unset($parts);
unset($delete_parts_sql);
unset($search_field);
unset($check_sql);
unset($check_res);
unset($i);
# should never be empty, but just as a precaution ...
if (!empty($part_ids)) {
foreach ($components as $key => $component)
{
$components[$key]['part_id'] = $part_ids[$component['part_ref']];
}
# rm components from assoc part id
foreach ($part_ids as $id)
{
$wpdb->query($wpdb->prepare($delete_components_sql, array($id)));
}
# insert components
$admin_functions->insert_components($components);
} else {
echo 'Error!';
}
echo (!$error ? 'Success! File Successfully Imported.' : 'There be something wrong with the import. Please try again.');
}
il est déclenché par une pression sur un bouton et utilise AJAX pour le gérer, etc.
Je ne sais pas pourquoi une fuite de mémoire se produit ou pourquoi WordPress n'offre pas de messages d'erreur plus utiles. Je n’appelle pas cette fonction. Je suppose donc que c’est quelque chose que WordPress fait en arrière-plan lorsque les choses tournent.
Mon info:
PHP 7.2.10
Apache 2.4
Linux Mint 19
Cela se passe aussi sur mon serveur:
PHP 7.1.25
Apache 2.4
CentOS 7.6.1810
WordPress version en cours d'exécution: 4.9.8
En fonction de l'objectif recherché, je partage l'avis de Tom , selon lequel une commande WP-CLI pourrait être meilleure. L'avantage est que la commande est exécutée à partir de php directement sur le serveur (généralement, elle n'a pas de délai d'exécution maximal, charge un fichier php.ini différent, etc.) et vous n'avez pas besoin d'impliquer le serveur Web.
Si cela n’est pas possible, la meilleure solution consiste probablement à créer un point de terminaison REST personnalisé . WordPress a une classe WP_REST_Controller
, d'habitude j'écris des classes qui extend
this et travaillent à partir de là. Pour des raisons de simplicité, l'exemple suivant n'utilise pas l'héritage, mais j'essaie de garder le même jargon.
1. Enregistrer un nouvel itinéraire
Les nouveaux itinéraires/itinéraires personnalisés sont enregistrés via register_rest_route()
comme suit
$version = 1;
$namespace = sprintf('acme/v%u', $version);
$base = '/import';
\register_rest_route(
$namespace,
$base,
[
[
'methods' => \WP_REST_Server::CREATABLE,
// equals ['POST','PUT','PATCH']
'callback' => [$this, 'import_csv'],
'permission_callback' => [$this, 'get_import_permissions_check'],
'args' => [],
// used for OPTIONS calls, left out for simplicity's sake
],
]
);
Cela créera un nouvel itinéraire que vous pourrez appeler via
http://www.example.com/wp-json/acme/v1/import/
default REST start-^ ^ ^
namespace with version-| |-base
2. Définir le contrôle des autorisations
Peut-être avez-vous besoin d'une authentification? Utiliser des nonces?
public function get_import_permissions_check($request)
{
//TODO: implement
return true;
}
3. Créez votre rappel de point final réel
La méthode précédemment définie reçoit un objet WP_REST_Request
, elle sert à accéder au corps de la demande, etc. Pour rester cohérent, il est généralement préférable de renvoyer un WP_REST_Response
au lieu d’une impression personnalisée de JSON ou similaire.
public function import_csv($request)
{
$data = [];
// do stuff
return new \WP_REST_Response($data, 200);
}
Si vous faites tout cela dans le style OOP, vous aurez la classe suivante
class Import_CSV
{
/**
* register routes for this controller
*/
public function register_routes()
{
$version = 1;
$namespace = sprintf('acme/v%u', $version);
$base = '/import';
\register_rest_route(
$namespace,
$base,
[
[
'methods' => \WP_REST_Server::CREATABLE,
'callback' => [$this, 'import_csv'],
'permission_callback' => [$this, 'get_import_permissions_check'],
'args' => [],
],
]
);
}
/**
* endpoint for POST|PUT|PATCH /acme/v1/import
*/
public function import_csv($request)
{
$data = [];
// do stuff
return new \WP_REST_Response($data, 200);
}
/**
* check if user is permitted to access the import route
*/
public function get_import_permissions_check($request)
{
//TODO: implement
return true;
}
}
Mais .. encore 404? Oui, simplement définir la classe ne fonctionne malheureusement pas (pas de chargement automatique par défaut :(), nous devons donc exécuter register_routes()
comme ceci (dans votre fichier de plugin)
require_once 'Import_CSV.php';
add_action('rest_api_init', function(){
$import_csv = new \Import_CSV;
$import_csv->register_routes();
});
Dans votre fichier de fonctions, faites quelque chose comme ça:
add_action('wp_ajax_import_parts_abc', 'import_parts_abc');
function import_parts_abc() {
include_once('/admin/inc/scripts/import-parts.php');
exit();
}
Ensuite, dans votre fichier js, quelque chose comme ceci:
jQuery(document).ready(function() {
jQuery('.my-button').click(function() {
jQuery.ajax({
type: 'GET',
url: ajaxurl,
data: {
action : 'import_parts_abc'
},
success: function(textStatus){
alert('processing complete')
},
error: function(MLHttpRequest, textStatus, errorThrown){
console.log(errorThrown);
}
});
});
});