Какова ваша лучшая практика для выполнения одноразовых скриптов?

29 янв. 2014 г., 18:24:47
Просмотры: 21.4K
Голосов: 42

Проблема

Мы все сталкивались с подобной ситуацией, и многие вопросы на этом сайте требуют подобного решения. Вам нужно либо обновить базу данных, автоматически вставить много данных, конвертировать meta_keys или что-то подобное.

Конечно, в работающей системе, основанной на лучших практиках, такого происходить не должно.

Но поскольку это случается, я хотел бы услышать ваше личное решение этой проблемы и почему вы выбрали именно его.

Вопрос

Как вы реализуете одноразовые скрипты в вашей (работающей) установке WordPress?

Проблема здесь в основном из-за следующих причин:

  • Скрипты, которые вставляют данные, не должны выполняться более одного раза
  • Скрипты, требующие много ресурсов, не должны выполняться в то время, когда за ними нельзя наблюдать
  • Они не должны запускаться случайно

Причина, по которой я спрашиваю

У меня есть своя практика, я опубликую ее в ответах. Поскольку я не знаю, является ли это лучшим решением из существующих, я хотел бы узнать о вашем. Кроме того, этот вопрос часто задается в контексте других вопросов, и было бы здорово иметь ресурс, собирающий все идеи.

с нетерпением жду, чтобы научиться у вас :)

8
Комментарии

Если это действительно разовая операция, то я пишу скрипт, запускаю его, а затем удаляю. Никто больше не сможет его запустить. Как и всё в этом мире, код не вечен. ;)

Otto Otto
29 янв. 2014 г. 20:44:20

Дело в том, что я беспокоюсь, что скрипт может быть случайно вызван повторно. Но я бесчисленное количество раз использовал ваш подход ;)

fischi fischi
29 янв. 2014 г. 22:31:21

Запускайте его на странице админки плагина — у меня всегда работало. При необходимости можно добавить проверки авторизации в начале страницы, чтобы убедиться, что это вы.

Andrew Bartel Andrew Bartel
29 янв. 2014 г. 23:14:15

но вы говорите только о ручном одноразовом выполнении, а не о запланированном?

birgire birgire
3 февр. 2014 г. 13:08:42

Да, я говорю только о ручных одноразовых операциях, таких как миграционные скрипты и т.п., а не о запланированных событиях через wp-cron.

fischi fischi
3 февр. 2014 г. 14:10:32

Пожалуй, я не совсем понимаю, зачем это нужно. Если вы хотите изменить имя meta_key, используемое и в базе данных, и в вашем коде, разве существует скрипт, который сделает всё за один запуск? Даже если бы не требовалось изменять базу данных, а только код, разве скрипт, подключённый через include, не оставался бы статичным до вызова страницы? Кажется, я упускаю суть :/

Howdy_McGee Howdy_McGee
5 февр. 2014 г. 23:00:55

Суть в том, чтобы, например, преобразовать SEO-информацию из одного плагина в другой без потери данных или вставки большого количества контента — что должно выполняться только один раз. И с встроенными функциями WordPress вы работаете гораздо быстрее и безопаснее, чем при выполнении запросов в базе данных.

fischi fischi
6 февр. 2014 г. 09:11:51

Это не было моей целью, но после голосования я выберу свой ответ исключительно на основе результатов.

fischi fischi
11 февр. 2014 г. 08:22:55
Показать остальные 3 комментариев
Все ответы на вопрос 12
1
26

Лично я использую комбинацию следующих подходов:

  • отдельный файл для одноразового скрипта
  • использование транзиента (transient) для предотвращения случайного повторного запуска скрипта
  • управление правами пользователей (capability-management или user-control) для гарантии, что скрипт запускается только мной

Структура

Я использую файл (onetime.php) в папке inc, который подключается в functions.php и удаляется оттуда после использования.

include( 'inc/onetime.php' );

Файл для самого скрипта

В моем onetime.php размещена функция f711_my_onetime_function(). Это может быть любая функция. Предполагается, что ваш скрипт уже протестирован и работает корректно.

Для контроля выполнения скрипта я использую:

Контроль прав доступа

Чтобы предотвратить случайный запуск скрипта другими пользователями:

if ( current_user_can( 'manage_options' ) ) // проверка прав администратора

или

if ( get_current_user_id() == 711 ) // проверка, что это я - я предпочитаю ограничивать выполнение только для себя, а не для всех админов.

Транзиент

чтобы предотвратить случайный повторный запуск скрипта мной же.

$transient = 'f711_my_onetime_check';
if ( !get_transient( $transient ) ) // проверка, что функция еще не выполнялась.

Файл для выполнения скрипта в моей функции f711_my_onetime_function() будет выглядеть так:

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

    set_transient( $transient, 'locked', 600 ); // блокировка функции на 10 минут
    add_action( 'wp_footer', 'f711_my_onetime_function' ); // выполнение моей функции на нужном хуке.

}

function f711_my_onetime_function() {
    // вся моя великолепная одноразовая магия.
}

Я устанавливаю транзиент сразу после проверки его существования, потому что хочу, чтобы функция выполнялась после того, как скрипт будет защищен от повторного запуска.

Если мне нужен вывод из функции, я либо вывожу его в виде комментария в подвале, либо иногда фильтрую контент.

Время блокировки установлено на 10 минут, но может быть изменено по вашему усмотрению.

Очистка

После успешного выполнения скрипта я удаляю include из functions.php и файл onetime.php с сервера. Так как я использовал временный транзиент, мне не нужно чистить базу данных, но вы также можете удалить транзиент после удаления файла.

29 янв. 2014 г. 18:24:47
Комментарии

Я подумал о том, чтобы добавить свой ответ, но после прочтения вашего списка в самом начале этого ответа... я больше не буду, так как мой подход выглядит практически идентично. Так что +1 за это — также за подробные размышления по этому поводу.

tfrommen tfrommen
5 февр. 2014 г. 22:58:38
2
20

Вы также можете сделать следующее:

запустите onetime.php и переименуйте его после выполнения.

if ( current_user_can( 'manage_options' ) ) {

    if( ! file_exists( '/path/to/onetime.php' ) )
      return;
    add_action( 'wp_footer', 'ravs_my_onetime_function' ); // выполнить мою функцию на нужном хуке.

}

function ravs_my_onetime_function() {

    // вся моя великолепная одноразовая магия.
    include( '/path/to/onetime.php' );

   // после выполнения переименуйте файл;
   rename( '/path/to/onetime.php', '/path/to/onetime-backup.php');
}
5 февр. 2014 г. 21:58:56
Комментарии

Это то, чем мы занимаемся; это практически гарантированно безошибочно.

Qix - MONICA WAS MISTREATED Qix - MONICA WAS MISTREATED
6 янв. 2015 г. 23:07:52

Идеально, не знаю, почему эта идея не пришла мне в голову, Спасибо :)

Shahin Shahin
11 февр. 2020 г. 09:01:46
1

Я создал командный скрипт Phing для этого, в нём нет ничего особенного, кроме загрузки внешнего скрипта для выполнения. Причина, по которой я использовал его через CLI:

  • Я не хочу, чтобы он загружался случайно (нужно ввести команду)
  • Это безопасно, так как может выполняться вне корневой веб-директории, то есть он может влиять на WP, но WP не может получить доступ к скрипту никаким образом.
  • Он не добавляет никакого кода в WP или саму базу данных.

require('..путь к ../wp-blog-header.php');
//куча глобальных переменных WP
define('WP_USE_THEMES', false);
//пользовательский код

Таким образом, вы можете использовать Phing или PHP CLI и спать спокойно. WP-CLI также является хорошей альтернативой, хотя я не помню, можно ли использовать его вне корневой веб-директории.

Поскольку это популярный пост, вот пример скрипта: https://github.com/wycks/WordPhing (run.php)

6 февр. 2014 г. 04:54:58
Комментарии

Это выглядит красиво и просто, а также безопасно. Вы также в значительной степени учли одну из моих главных проблем (случайное двойное выполнение) за счёт использования командной строки. Отличная идея!

fischi fischi
6 февр. 2014 г. 09:18:15
0

В идеальных условиях я бы подключился к серверу через SSH и выполнил функцию самостоятельно, используя wp-cli.

Однако это не всегда возможно, поэтому я обычно устанавливаю переменную $_GET и подключаю её к хуку 'init', например:

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

затем перехожу по ссылке

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

и отключаю хук после выполнения.

5 февр. 2014 г. 21:38:38
0

Ещё один довольно простой способ запуска одноразового скрипта — использование MU-плагина.

Поместите код в PHP-файл (например, one-time.php), загрузите его в папку MU-плагинов (по умолчанию /wp-content/mu-plugins), настройте права доступа к файлу, запустите плагин (т.е., в зависимости от выбранного хука, вам достаточно просто посетить фронтенд/бэкенд сайта), и всё готово.

Вот шаблон для такого скрипта:

/**
* Главный (и единственный) класс.
*/
class OneTimeScript {

    /**
     * Хук для функции плагина.
     *
     * @type    string
     */
    public static $hook = 'init';


    /**
     * Приоритет функции плагина.
     *
     * @type    int
     */
    public static $priority = 0;


    /**
     * Запуск одноразового скрипта.
     *
     * @hook    self::$hook
     * @return  void
     */
    public static function run() {
        // здесь выполняем одноразовое действие...

        // очистка
        add_action('shutdown', array(__CLASS__, 'unlink'), PHP_INT_MAX);
    } // function run


    /**
     * Удаление файла.
     *
     * @hook    shutdown
     * @return  void
     */
    public static function unlink() {
        unlink(__FILE__);
    } // function unlink

} // class OneTimeScript

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

Без комментариев и дополнительных элементов код выглядит так:

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

    public static function run() {
        // здесь выполняем одноразовое действие...
        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);
6 февр. 2014 г. 00:01:47
0

Конечно, вы можете создать одноразовый код в виде плагина.

add_action('admin_init', 'one_time_call');
function one_time_call()
{
    /* ВАШИ СКРИПТЫ */
    deactivate_plugins('onetime/index.php'); //деактивировать текущий плагин
}

Вопрос: как активировать этот плагин без нажатия на ссылку "Активировать"?

Просто добавьте activate_plugins('onetime/index.php'); в functions.php

Или используйте must-use плагины: http://codex.wordpress.org/Must_Use_Plugins

Попробуйте разные действия для выполнения одноразового плагина:

  1. admin_init - после инициализации админки

  2. init - инициализация WordPress

  3. wp - когда WordPress загружен

8 февр. 2014 г. 18:28:52
3

Иногда я использовал функцию, подключенную к деактивации плагина.

Смотрите здесь Обновление старых ссылок на красивые постоянные ссылки для пользовательских типов записей

Поскольку только администраторы могут активировать плагины, проверка прав доступа является побочным эффектом.

Нет необходимости удалять файл при деактивации — WordPress не будет его включать. Кроме того, если вы захотите запустить его снова, вы можете это сделать. Просто активируйте и деактивируйте его снова.

А иногда я использовал временные данные (transient), как в ответе @fischi. Например, здесь запрос для создания товаров WooCommerce из изображений или здесь Удаление/замена тегов img в контенте записей для автоматически публикуемых постов

Комбинация обоих подходов может быть альтернативой.

5 февр. 2014 г. 22:14:57
Комментарии

Это тоже очень хорошая идея. Если станет раздражать постоянная необходимость активировать и деактивировать плагин, можно также привязать эту же функцию к активации плагина, верно?

fischi fischi
6 февр. 2014 г. 09:20:32

Да, если хотите. Однако я считаю, что два клика — это не такая уж большая нагрузка для выполнения одноразового скрипта. Любое другое решение, включающее команды CLI или работу с файлами (переименование, удаление), требует больше "усилий". Кроме того, каждый раз, когда вы полагаетесь на хуки, вы зависите от глобальных переменных, добавляя дополнительный слой потенциальных проблем с безопасностью/предсказуемостью кода. @fischi

gmazzap gmazzap
6 февр. 2014 г. 13:00:52

Я тоже не считаю, что два клика — это слишком много, просто хотел спросить :)

fischi fischi
6 февр. 2014 г. 14:44:25
0

Другой способ — установить глобальную wp_option, когда работа завершена, и проверять эту опцию каждый раз при выполнении хука init.

function my_one_time_function() {
    // Выходим, если работа уже выполнена.
    if ( get_option( 'my_one_time_function', '0' ) == '1' ) {
        return;
    }

    /***** ВЫПОЛНИТЕ ВАШУ ОДНОРАЗОВУЮ РАБОТУ *****/

    // Добавляем или обновляем wp_option
    update_option( 'my_one_time_function', '1' );
}
add_action( 'init', 'my_one_time_function' );

Естественно, вам не нужно хранить этот код вечно (даже если это просто чтение из базы данных), поэтому вы можете удалить его после завершения работы. Также вы можете вручную изменить значение этой опции на 0, если вам нужно повторно выполнить код.

29 дек. 2014 г. 13:22:11
0

Использование wp-cli eval-file — это отличный способ. Вы даже можете выполнять его на удаленной системе (используя ssh-алиас через '@') с локальным скриптом.

Если ваш код находится в файле one-time.php в корневой директории WordPress, и у вас есть доступ к командной строке на системе, против которой нужно выполнить команду, вы можете сделать так:

wp eval-file one-time.php

Если файл one-time.php находится локально, и вы хотите выполнить его на удаленном WordPress, используя @, команда будет выглядеть так:

wp @remote eval-file - < one-time.php
25 июн. 2020 г. 21:00:16
0

Мой подход немного отличается. Я предпочитаю добавлять одноразовые скрипты как функцию в файл function.php моей темы и запускать их по определенному GET-запросу.

if ( isset($_GET['linkupdate']) ) {
    add_action('init', 'link_update', 10);
}
function link_update() {
  // Одноразовый скрипт
   die;
}

Чтобы запустить этот скрипт, просто перейдите по URL "www.sitename.com/?linkupdate"

Пока что этот метод отлично работает для меня...

Есть ли у этого способа какие-либо недостатки? Просто интересно...

16 июн. 2017 г. 09:10:33
0

Я просто использую один пользовательский шаблон страницы товара, который не задействован и не подключен ни к чему на публичном сервере.

Например, если у меня есть страница с отзывами, которая не опубликована (в черновике или другом статусе), но привязана к шаблону отдельной страницы, например single-testimonial.php — я могу разместить функции внутри этого файла, загрузить страницу через preview, и функция (или что-то другое) будет запущена один раз. Также очень легко вносить изменения в функцию при отладке.

Это действительно просто, и я предпочитаю такой подход использованию хука init, потому что у меня больше контроля над тем, когда и как функция запускается. Просто мои личные предпочтения.

9 июл. 2017 г. 01:13:25
0

На всякий случай, если это поможет, вот что я сделал, и это хорошо работает:

add_action( 'init', 'upsubscriptions_setup');

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

    // Если версия еще не записана в БД
    if (!$version) {
        add_option('upsubscriptions_setup_version', '0.1');
        $version = get_option('upsubscriptions_setup_version');
    }

    if (version_compare($version, "0.1") <= 0) {
        // выполнить действия
        update_option('upsubscriptions_setup_version', '0.2');
    }

    if (version_compare($version, "0.2") <= 0) {
        // выполнить действия
        update_option('upsubscriptions_setup_version', '0.3');
    }

    if (version_compare($version, "0.3") <= 0) {
        // выполнить действия
        update_option('upsubscriptions_setup_version', '0.4');
    }

    // и т.д.
}
19 нояб. 2018 г. 17:25:30