32

The Problem

We all have been in a situation like this, and a lot of questions on this site need a solution like this. You either have to update a database, insert a lot of data automatically, convert meta_keys, or something similar.

Of course, in a running system based on best practises this should not happen.

But as it does, I would love to hear your personal solution to this problem, and why you chose yours.

The Question

How do you implement one-time scripts in your (running) WordPress install?

The issue here is mainly because of the following reasons:

  • Scripts that insert data should not run more than one time
  • Scripts that require a lot of ressources should not run at a time when they can not be monitored
  • They should not be run by accident

The Reason I ask

I have got my own practise, I am going to post it in the answers. As I do not know if it is the best solution out there, I'd like to know about yours. Also, this is a question that is asked a lot of times in context of other questions, and it would be great to have a ressource collecting the ideas.

looking forward to learning from you :)

  • 2
    If it's truly a one-time only deal, then I write the script, I run it, then I delete it. Can't nobody run it again after that. Like all things, code is fleeting. ;) – Otto Jan 29 '14 at 18:44
  • 1
    The thing is I am worried that a script might be called a second time, per coincidence. but I did your approach countless times ;) – fischi Jan 29 '14 at 20:31
  • Run it on an admin page of a plugin, always worked for me. You can add auth checks at the top of the page to make sure it's you if necessary. – Andrew Bartel Jan 29 '14 at 21:14
  • but you are not talking about scheduled one time execution, only manual? – birgire Feb 3 '14 at 11:08
  • 1
    Yes, I am only talking about manual on-time operations, like migrating-scripts etc., not wp-cron scheduled events. – fischi Feb 3 '14 at 12:10

11 Answers 11

17

I for myself use a combination of:

  • one file dedicated to the one-time script
  • using a transient to stop the script from accidentally running more than once
  • using capability-management or user-control to ensure the script is just run by me.

Structure

I use a file (onetime.php) in my include-folder inc, which is included in the functions.php, and deleted from there after the use.

include( 'inc/onetime.php' );

The file for the script itself

In my onetime.php my function f711_my_onetime_function() is placed. As it could be any function. I assume your script is tested and works correctly.

To achieve the control over the execution of the script, I use both

Capability control

To stop other users from accidentally executioning my script:

if ( current_user_can( 'manage_options' ) ) // check for administrator rights

or

if ( get_current_user_id() == 711 ) // check if it is me - I prefer restricting the execution to me, not to all admins.

a transient

to stop myself from accidentally executing the script more than once.

$transient = 'f711_my_onetime_check';
if ( !get_transient( $transient ) ) // check if the function was not executed.

The file for executing the script in my function f711_my_onetime_function() would look like that:

$transient = 'f711_my_onetime_check';
if ( get_current_user_id() == 711 && !get_transient( $transient ) ) {

    set_transient( $transient, 'locked', 600 ); // lock function for 10 Minutes
    add_action( 'wp_footer', 'f711_my_onetime_function' ); // execute my function on the desired hook.

}

function f711_my_onetime_function() {
    // all my glorious one-time-magic.
}

The reason I set the transient immediately after the check if it exists is that I want the function to be executed after the script has been locked from beeing used twice.

If I need any output from my function, I either print it out as a comment in the footer, or sometimes I even filter the content.

The lockouttime is set to 10 Minutes, but can be adjusted to your needs.

Cleanup

After the successful execution of my script I delete the include from the functions.php, and remove the onetime.php from the server. As I used a timeout for the transient, I do not need to clean the database, but of course you could also delete the transient after you removed the file.

  • I thought about adding my answer to this, but after having read your listing right at the top of this answer ... I won't anymore as my approach looks almost exactly the same. So +1 for this - also for the detailed thoughts on this. – tfrommen Feb 5 '14 at 20:58
14
+50

You can also do this:

run onetime.php and rename it after execution.

if ( current_user_can( 'manage_options' ) ) {

    if( ! file_exists( '/path/to/onetime.php' ) )
      return;
    add_action( 'wp_footer', 'ravs_my_onetime_function' ); // execute my function on the desired hook.

}

function ravs_my_onetime_function() {

    // all my glorious one-time-magic.
    include( '/path/to/onetime.php' );

   // after all execution rename your file;
   rename( '/path/to/onetime.php', '/path/to/onetime-backup.php');
}
  • This is what we do; it's pretty much guaranteed to be fool-proof. – Qix Jan 6 '15 at 21:07
7

I created a command line Phing script for this, it's nothing special other than loading an external script to run. The reason I used it via the CLI is because:

  • I don't want it to load by mistake (need to type a command)
  • It's secure since it can be run outside the web root, in other words it can effect WP but WP cannot reach the script in any way.
  • It does not add any code to WP or the DB itself.

require('..path to ../wp-blog-header.php');
//bunch of WP globals
define('WP_USE_THEMES', false);
//custom code

So you can use Phing, or the PHP CLI and sleep at night. The WP-CLI is also a good alternative though I forget if you can use it outside the web root.

Since this is a popular post here is an example of the script: https://github.com/wycks/WordPhing (run.php)

  • This looks nice and simple, as well as secure. You also covered one of my main concerns (me executing twice by accident) to quite an extend by using the command line. Nice idea! – fischi Feb 6 '14 at 7:18
5

Another pretty simple way of running a one-time script is to do this by means of an MU plugin.

Put the code in some PHP file (e.g., one-time.php) that you upload to your MU plugins' folder (by default /wp-content/mu-plugins), adjust the file permissions, run the plugin (i.e., according to your chosen hook, you basically just have to visit the frontend/backend), and you're all done.

Here is a boilerplate:

/**
* Main (and only) class.
*/
class OneTimeScript {

    /**
     * Plugin function hook.
     *
     * @type    string
     */
    public static $hook = 'init';


    /**
     * Plugin function priority.
     *
     * @type    int
     */
    public static $priority = 0;


    /**
     * Run the one-time script.
     *
     * @hook    self::$hook
     * @return  void
     */
    public static function run() {
        // one-time action goes here...

        // clean up
        add_action('shutdown', array(__CLASS__, 'unlink'), PHP_INT_MAX);
    } // function run


    /**
     * Remove the file.
     *
     * @hook    shutdown
     * @return  void
     */
    public static function unlink() {
        unlink(__FILE__);
    } // function unlink

} // class OneTimeScript

add_action(OneTimeScript::$hook, array('OneTimeScript', 'run'), OneTimeScript::$priority);

Without the comments and stuff, it just looks like this:

class OneTimeScript {
    public static $hook = 'init';
    public static $priority = 0;

    public static function run() {
        // one-time action goes here...
        add_action('shutdown', array(__CLASS__, 'unlink'), PHP_INT_MAX);
    } // function run

    public static function unlink() {
        unlink(__FILE__);
    } // function unlink
} // class OneTimeScript
add_action(OneTimeScript::$hook, array('OneTimeScript', 'run'), OneTimeScript::$priority);
4

Under ideal conditions I'd ssh into the server and execute the function myself using wp-cli.

This often isn't possible, though, so I tend to set a $_GET variable and hook it onto 'init', for instance:

add_action( 'init', function() {
    if( isset( $_GET['one_time'] ) && $_GET['one_time'] == 'an_unlikely_string' ) {
        do_the_one_time_thing();
    }
});

then hit

http://my_blog.com/?one_time=an_unlikely_string

and disable the hook when it's done.

4

Sometimes I used a function hooked on plugin deactivation.

See here Update Old Links To Pretty Permalinks Custom Post Type

Once only admins can activate plugins, there a capability check as a side effect.

There is no need to delete file once deactivated it will not included by wordress. In addiction if you want to run again it you can. Activating and deactivating again.

And sometimes I used transient used like in @fischi answer. E.g. here query to create woocommerce products from images or here Delete/replace img tags in post content for auto published posts

A combination of both can be an alternative.

  • This is also a really good Idea. If it becomes annoying always having to activate to deactivate it again, you could also hook the same function to the activation of the plugin, right? – fischi Feb 6 '14 at 7:20
  • Yes, if you want. However, I think that 2 clicks is not a great effort to run a one-time script. Any other solution that involves CLI command or file handling (renaming, deleting) need more "work". In addition, everytime you rely on hooks, you are relying on global variables, adding an additional layer of potential issues regarding security /predictability of the code. @fischi – gmazzap Feb 6 '14 at 11:00
  • I don't think that two clicks is too much either, just wanted to ask :) – fischi Feb 6 '14 at 12:44
3

Definitely you can, Just create your one time code as a plugin.

add_action('admin_init', 'one_time_call');
function one_time_call()
{
    /* YOUR SCRIPTS */
    deactivate_plugins('onetime/index.php'); //deactivate current plugin
}

Problem how do I activate this plugin without clicking Activate link?

just add activate_plugins('onetime/index.php'); in functions.php

or Use must use plugins, http://codex.wordpress.org/Must_Use_Plugins

Try with different actions as when you want to execute onetime plugin,

  1. admin_init - after admin init

  2. init - wordpress init

  3. wp - when wordpress loaded

2

Another way is to set a global wp_option when the work is done and check for that option every time the init hook is executed.

function my_one_time_function() {
    // Exit if the work has already been done.
    if ( get_option( 'my_one_time_function', '0' ) == '1' ) {
        return;
    }

    /***** DO YOUR ONE TIME WORK *****/

    // Add or update the wp_option
    update_option( 'my_one_time_function', '1' );
}
add_action( 'init', 'my_one_time_function' );

Naturally you don't need to have this code forever (even if it is a simple read from the database), so you can probably remove the code when the work is done. Also you can manually change this option value to 0 if you need to re-execute the code.

1

My approach is a little different on this. I like to add my one-time script as a function in my theme's function.php and run it on a specific GET query.

if ( isset($_GET['linkupdate']) ) {
    add_action('init', 'link_update', 10);
}
function link_update() {
  // One Time Script
   die;
}

To run this, just visit the URL "www.sitename.com/?linkupdate"

This is working fine for me till now...

Does this method have any drawbacks? Just wondering...

1

I just use a single custom product template page I'm not using and isn't connected to anything on the public server.

Like if I have a testimonial page that isn't live (in draft mode, or whatever), but connected to a single page template, for example single-testimonial.php - I can place functions in there, load the page via a preview and the function or whatever is launched once. It's also very easy to make modifications to the function in case of debugging.

Is really easy and I prefer it over using init because I have more control over when and how its launched. Just my preference.

0

Just in case it helps, this is what I did and it works well:

add_action( 'init', 'upsubscriptions_setup');

function upsubscriptions_setup()
{
    $version = get_option('upsubscriptions_setup_version');

    // If no version is recorded yet in the DB
    if (!$version) {
        add_option('upsubscriptions_setup_version', '0.1');
        $version = get_option('upsubscriptions_setup_version');
    }

    if (version_compare($version, "0.1") <= 0) {
        // do stuff
        update_option('upsubscriptions_setup_version', '0.2');
    }

    if (version_compare($version, "0.2") <= 0) {
        // do stuff
        update_option('upsubscriptions_setup_version', '0.3');
    }

    if (version_compare($version, "0.3") <= 0) {
        // do stuff
        update_option('upsubscriptions_setup_version', '0.4');
    }

    // etc
}

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.