In the last tutorial we looked at exporting data from a custom table. Of course this is only half the story - we obviously need to provide a way of importing that data. Naturally, WordPress doesn't handle this - so once more we need to roll our own.
In the previous article in this series we noted that if our data contains references to one of the native WordPress tables (a post ID for example) then we rather quickly hit difficulties. The reason is that when importing such data, the post ID being referenced may not exist and if it does, may not be the correct ID. This is because when posts are imported, their ID may change to avoid collisions in the database (IDs must be unique).
Normally this is ok: linked data is imported together and the references are updated during the import routine to ensure that any changes propagate throughout the entire dataset. However, as discussed in the previous article - it's actually very difficult (except in particular cases) to import our custom data alongside the native data. So the caveats mentioned in that article carry forward to this one - and as before, although the example below references WordPress tables, it is used simply for consistency with the rest of the series.
The Mark-Up
We want to allow our users to import data from the export file generated in the last article in this series - so let's start with adding a form that allows the user to upload that file. We'll do this by modifying the class we defined in last tutorial.
class WPTuts_Log_Export_Admin_Page { /** * The page hook suffix */ static $hook_suffix=''; static function load() { add_action( 'admin_menu', array( __CLASS__, 'add_submenu' ) ); add_action( 'admin_init', array( __CLASS__, 'maybe_download' ) ); add_action( 'admin_init', array( __CLASS__, 'maybe_upload' ) ); add_action( 'admin_notices',array( __CLASS__, 'admin_notices' ) ); } static function add_submenu() { /* Defined in previous article */ } static function maybe_download() { /* Defined in previous article */ } static function display() { /* Defined in previous article - but we'll make some modifications */ } static function maybe_upload() { } static function admin_notices() { } static function import() { } static function parse() { } } WPTuts_Log_Export_Admin_Page::load();
Above we've added the following methods
-
maybe_upload()
- which will act as a listener for a file being submitted for importing. -
admin_notices()
- which will display a success or error notice after attempting to import a file. -
import()
- which will receive an uploaded file and import the data. -
parse()
- a helper function called byimport()
to parse the uploaded file, and extract the logs it contains.
But first, we'll add a form by which we can upload a file. We'll add this below the export button we created in the previous article. To do this we'll need make some alterations to the display()
method, responsible for producing the mark-up of our admin page. Since this second form will submit a file, we need to set the encoding type to 'multipart/form-data
'.
<?php static function display() { echo '<div class="wrap">'; screen_icon(); echo '<h2>' . __( 'Export Activity Logs', 'wptuts-log' ) . '</h2>'; ?> <form id="wptuts-export-log-form" method="post" action=""> <p> <label><?php _e( 'Click to export the activity logs','wptuts-log' ); ?></label> <input type="hidden" name="action" value="export-logs" /> </p> <?php wp_nonce_field( 'wptuts-export-logs', '_wplnonce' ); ?> <?php submit_button( __( 'Download Activity Logs', 'wptuts-log' ), 'button' ); ?> </form> <form method="post" action="" enctype="multipart/form-data"> <p> <label for="wptuts_import_logs"><?php _e( 'Import an .xml file.', 'wptuts-log' ); ?></label> <input type="file" id="wptuts_import_logs" name="wptuts_import" /> </p> <input type="hidden" name="action" value="import-logs" /> <?php wp_nonce_field( 'wptuts-import-logs', '_wplnonce' ); ?> <?php submit_button( __( 'Upload Activity Logs', 'wptuts-log' ), 'secondary' ); ?> </form> <?php } ?>
Handling the Form Submission
Next, we want to listen for when the above form is submitted and trigger the import routine. Before doing that it's important to run a few checks:
- Does the user have permission to upload files? Here we've granted only those who can
manage_options
the ability to upload. - Did the user intend to upload the files (we check this by verify the nonce)
- Was a file actually uploaded? And was it of the correct type
- Were there any errors in the upload?
Optionally you can place a limit on the size of the uploaded file as a sort of 'sanity check'. In this example I've capped it at 2MB. (A useful function for formatting file sizes in a 'human readable way' is the function size_format
).
static function maybe_upload() { /* Listen for form submission */ if ( empty( $_POST['action'] ) || 'import-logs' !== $_POST['action'] ) return; /* Check permissions and nonces */ if ( ! current_user_can( 'manage_options' ) ) wp_die(''); check_admin_referer( 'wptuts-import-logs', '_wplnonce' ); /* Perform checks on file: */ // Sanity check if ( empty( $_FILES["wptuts_import"] ) ) wp_die( 'No file found' ); $file = $_FILES["wptuts_import"]; // Is it of the expected type? if ( $file["type"] != "text/xml" ) wp_die( sprintf( __( "There was an error importing the logs. File type detected: '%s'. 'text/xml' expected", 'wptuts-log' ), $file['type'] ) ); // Impose a limit on the size of the uploaded file. Max 2097152 bytes = 2MB if ( $file["size"] > 2097152 ) { $size = size_format( $file['size'], 2 ); wp_die( sprintf( __( 'File size too large (%s). Maximum 2MB', 'import-logs' ), $size ) ); } if( $file["error"] > 0 ) wp_die( sprintf( __( "Error encountered: %d", 'wptuts-import' ), $file["error"] ) ); /* If we've made it this far then we can import the data */ $imported = self::import( $file['tmp_name'] ); /* Everything is complete, now redirect back to the page */ wp_redirect( add_query_arg( 'imported', $imported ) ); exit(); }
Importing the File
Next we need to import the file. First we'll need to extract the logs from the uploaded file - and we'll delegate that job to the parse()
method (we'll get to that method in a bit).
Once we have these logs we'll first check if they already exist (to avoid any accidental duplication), before inserting them. When checking the logs we'll just check the user ID and the activity date. We could, if we wanted, be more strict (checking activity, object ID and type etc.) but we would need to go back and extend our API (specifically wptuts_get_logs()
).
Once we've imported the logs we redirect the user back to our admin page. We'll add a query variable to the URL (imported
) to store how many logs were imported (if any). This way we can display an appropriate admin message.
function import( $file ) { // Parse file $logs = self::parse( $file ); // No logs found ? - then aborted. if ( ! $logs ) return 0; // Initialises a variable storing the number of logs successfully imported. $imported = 0; // Go through each log foreach ( $logs as $log_id => $log ) { /* * Check if the log already exists: * We'll just check the date and the user ID, but we could check other details * if we extended our wptuts_get_logs() API */ $exists = wptuts_get_logs( array( 'user_id' => $log['user_id'], 'since' => mysql2date( 'G', $log['activity_date'], false ), 'until' => mysql2date( 'G', $log['activity_date'], false ), )); // If it exists, don't import it if ( $exists ) continue; // Insert the log $successful = wptuts_insert_log( array( 'user_id'=> $log['user_id'], 'date' => mysql2date( 'G', $log['activity_date'], false ), 'object_id' => $log['object_id'], 'object_type' => $log['object_type'], 'activity' => $log['activity'], 'activity_date' => $log['activity_date'], )); if ( $successful ) $imported++; } return $imported; }
Parsing the File
We still need to define the parse()
method which, given the uploaded file, should extract the data and return it as an array of logs. Fortunately, with PHP's built-in XML handler this is a fairly simple task.
function parse( $file ) { // Load the xml file $xml = simplexml_load_file( $file ); // halt if loading produces an error if ( ! $xml ) return false; // Initial logs array $logs = array(); foreach ( $xml->xpath( '/logs/item' ) as $log_obj ) { $log = $log_obj->children( ); $log_id = (int) $log->log_id; $logs[$log_id] = array( 'user_id' => (int) $log->user_id, 'object_id' => (int) $log->object_id, 'object_type' => (string) $log->object_type, 'activty' => (string) $log->activity, 'activity_date' =>(string) $log->activity_date, ); } return $logs; }
Displaying the Admin Notice
Finally, we want to define our admin_notices()
method to display an appropriate message after the file has been uploaded. Recall that once the import routine has finished, we redirect the user back to our admin page, with the query variable imported
added, storing the number of imported logs. We use this to determine whether we should display an error or success message.
We also check the screen ID so that we only display the notice on our admin page. If you're not sure what the screen ID of your admin page is, see this article.
function admin_notices() { // Was an import attempted and are we on the correct admin page? if ( ! isset( $_GET['imported'] ) || 'tools_page_wptuts-export' !== get_current_screen()->id ) return; $imported = intval( $_GET['imported'] ); if ( 1 == $imported ) { printf( '<div class="updated"><p>%s</p></div>', __( '1 log successfully imported', 'wptuts-import' ) ); } elseif ( intval( $_GET['imported'] ) ) { printf( '<div class="updated"><p>%s</p></div>', sprintf( __( '%d logs successfully imported', 'wptuts-import' ), $imported ) ); } else { printf( '<div class="error"><p>%s</p></div>', __( ' No logs were imported', 'wptuts-import' ) ); } }
Comments