Деинсталляция, активация, деактивация плагина: типичные функции и инструкции

100

Я делаю плагин для WordPress. Какие типичные вещи я должен включить в функцию удаления?

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

Очистить ли мои записи опций?

Что-нибудь еще?

redconservatory
источник
Я потратил столько времени, пытаясь заставить его работать. Проблема в том, что инициализация ловушки не работает внутри регистрации ловушек. Я полагаю, что ни один хук (действие или фильтр) не будет работать так рано. Прочитайте заметки по ссылке ниже. codex.wordpress.org/Function_Reference/register_activation_hook В нем говорится: «Регистрация хука внутри хука plugins_loaded слишком поздняя, ​​и она не запустится! (Даже если кажется, что она работает для register_deactivation_hook до хука wp_loaded.)»
Антон,
Я тот, кто обновил кодекс до того, что вы упомянули, так что это рассматривается в ответе выше. :)
kaiser

Ответы:

150

Есть три разных крючка. Они запускаются в следующих случаях:

  • Удалить
  • дезактивация
  • активация

Как безопасно запускать функции во время сценариев

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

Как вы могли бы использовать этот код в плагине, который использует

  • простые функции,
  • класс или
  • внешний класс,

Я покажу три различных демо-плагина, которые вы можете просмотреть, а затем внедрит код в свои собственные плагины.

Важное примечание заранее!

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

(1) Активировать / деактивировать / удалить плагины.

Обратные вызовы установки плагина запускаются ядром, и вы не имеете никакого влияния на то, как ядро ​​делает это. Есть несколько вещей, которые нужно иметь в виду:

  • Никогда , echo/printничего (!) Во время обратных вызовов. Это приведет к headers already sentсообщению, и ядро ​​порекомендует деактивировать и удалить ваш плагин ... не спрашивайте: я знаю ...
  • Вы не увидите никакого визуального вывода. Но я добавил exit()утверждения ко всем различным обратным вызовам, чтобы вы могли получить представление о том, что на самом деле происходит. Просто раскомментируйте их, чтобы увидеть, как все работает.
  • Чрезвычайно важно проверить, если __FILE__ != WP_PLUGIN_INSTALLи (если нет: прервать!), Чтобы увидеть, действительно ли кто-то удаляет плагин. Я бы порекомендовал просто вызывать on_deactivation()обратные вызовы во время разработки, чтобы вы сэкономили время, которое понадобится, чтобы вернуть все обратно. По крайней мере, это то, что я делаю.
  • Я также занимаюсь вопросами безопасности. Некоторые сделаны ядром, но эй! Береженого Бог бережет! ,
    • Сначала я запрещаю прямой доступ к файлам, когда ядро ​​не загружено: defined( 'ABSPATH' ) OR exit;
    • Затем я проверяю, разрешено ли текущему пользователю выполнять эту задачу.
    • В качестве последнего задания я проверяю реферера. Примечание. При появлении ошибки могут появиться непредвиденные результаты на wp_die()экране с запросом соответствующих разрешений (и если вы хотите повторить попытку ... да, конечно ). Это происходит, когда ядро ​​перенаправляет вас, устанавливает текущее $GLOBALS['wp_list_table']->current_action();значение error_scrapeи затем проверяет реферер check_admin_referer('plugin-activation-error_' . $plugin);, где $pluginнаходится $_REQUEST['plugin']. Таким образом, перенаправление происходит при половине загрузки страницы, и вы получаете эту проводную полосу прокрутки и экран, на котором отображается желтое окно уведомления / сообщения администратора. Если это произойдет: сохраняйте спокойствие и просто найдите ошибку с exit()помощью пошаговой отладки.

(A) Плагин простых функций

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

<?php
defined( 'ABSPATH' ) OR exit;
/**
 * Plugin Name: (WCM) Activate/Deactivate/Uninstall - Functions
 * Description: Example Plugin to show activation/deactivation/uninstall callbacks for plain functions.
 * Author:      Franz Josef Kaiser/wecodemore
 * Author URL:  http://unserkaiser.com
 * Plugin URL:  http://wordpress.stackexchange.com/questions/25910/uninstall-activate-deactivate-a-plugin-typical-features-how-to/25979#25979
 */

function WCM_Setup_Demo_on_activation()
{
    if ( ! current_user_can( 'activate_plugins' ) )
        return;
    $plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';
    check_admin_referer( "activate-plugin_{$plugin}" );

    # Uncomment the following line to see the function in action
    # exit( var_dump( $_GET ) );
}

function WCM_Setup_Demo_on_deactivation()
{
    if ( ! current_user_can( 'activate_plugins' ) )
        return;
    $plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';
    check_admin_referer( "deactivate-plugin_{$plugin}" );

    # Uncomment the following line to see the function in action
    # exit( var_dump( $_GET ) );
}

function WCM_Setup_Demo_on_uninstall()
{
    if ( ! current_user_can( 'activate_plugins' ) )
        return;
    check_admin_referer( 'bulk-plugins' );

    // Important: Check if the file is the one
    // that was registered during the uninstall hook.
    if ( __FILE__ != WP_UNINSTALL_PLUGIN )
        return;

    # Uncomment the following line to see the function in action
    # exit( var_dump( $_GET ) );
}

register_activation_hook(   __FILE__, 'WCM_Setup_Demo_on_activation' );
register_deactivation_hook( __FILE__, 'WCM_Setup_Demo_on_deactivation' );
register_uninstall_hook(    __FILE__, 'WCM_Setup_Demo_on_uninstall' );

(B) Архитектура на основе классов / ООП

Это самый распространенный пример современных плагинов.

<?php
defined( 'ABSPATH' ) OR exit;
/**
 * Plugin Name: (WCM) Activate/Deactivate/Uninstall - CLASS
 * Description: Example Plugin to show activation/deactivation/uninstall callbacks for classes/objects.
 * Author:      Franz Josef Kaiser/wecodemore
 * Author URL:  http://unserkaiser.com
 * Plugin URL:  http://wordpress.stackexchange.com/questions/25910/uninstall-activate-deactivate-a-plugin-typical-features-how-to/25979#25979
 */


register_activation_hook(   __FILE__, array( 'WCM_Setup_Demo_Class', 'on_activation' ) );
register_deactivation_hook( __FILE__, array( 'WCM_Setup_Demo_Class', 'on_deactivation' ) );
register_uninstall_hook(    __FILE__, array( 'WCM_Setup_Demo_Class', 'on_uninstall' ) );

add_action( 'plugins_loaded', array( 'WCM_Setup_Demo_Class', 'init' ) );
class WCM_Setup_Demo_Class
{
    protected static $instance;

    public static function init()
    {
        is_null( self::$instance ) AND self::$instance = new self;
        return self::$instance;
    }

    public static function on_activation()
    {
        if ( ! current_user_can( 'activate_plugins' ) )
            return;
        $plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';
        check_admin_referer( "activate-plugin_{$plugin}" );

        # Uncomment the following line to see the function in action
        # exit( var_dump( $_GET ) );
    }

    public static function on_deactivation()
    {
        if ( ! current_user_can( 'activate_plugins' ) )
            return;
        $plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';
        check_admin_referer( "deactivate-plugin_{$plugin}" );

        # Uncomment the following line to see the function in action
        # exit( var_dump( $_GET ) );
    }

    public static function on_uninstall()
    {
        if ( ! current_user_can( 'activate_plugins' ) )
            return;
        check_admin_referer( 'bulk-plugins' );

        // Important: Check if the file is the one
        // that was registered during the uninstall hook.
        if ( __FILE__ != WP_UNINSTALL_PLUGIN )
            return;

        # Uncomment the following line to see the function in action
        # exit( var_dump( $_GET ) );
    }

    public function __construct()
    {
        # INIT the plugin: Hook your callbacks
    }
}

(C) Архитектура на основе классов / ООП с внешним объектом настройки

Этот сценарий предполагает , что вы получили главный файл плагина , и второй файл с именем setup.phpв подкаталоге плагина под названием inc: ~/wp-content/plugins/your_plugin/inc/setup.php. Это будет работать и тогда, когда папка плагина находится за пределами структуры папок WP по умолчанию, а также когда папка содержимого переименована или если ваш установочный файл назван по-другому. Только incпапка должна иметь такое же имя и расположение относительно корневого каталога плагинов.

Примечание. Вы можете просто взять три register_*_hook()*функции и классы и поместить их в свой плагин.

Основной файл плагина:

<?php
defined( 'ABSPATH' ) OR exit;
/**
 * Plugin Name: (WCM) Activate/Deactivate/Uninstall - FILE/CLASS
 * Description: Example Plugin
 * Author:      Franz Josef Kaiser/wecodemore
 * Author URL:  http://unserkaiser.com
 * Plugin URL:  http://wordpress.stackexchange.com/questions/25910/uninstall-activate-deactivate-a-plugin-typical-features-how-to/25979#25979
 */


register_activation_hook(   __FILE__, array( 'WCM_Setup_Demo_File_Inc', 'on_activation' ) );
register_deactivation_hook( __FILE__, array( 'WCM_Setup_Demo_File_Inc', 'on_deactivation' ) );
register_uninstall_hook(    __FILE__, array( 'WCM_Setup_Demo_File_Inc', 'on_uninstall' ) );

add_action( 'plugins_loaded', array( 'WCM_Setup_Demo_File', 'init' ) );
class WCM_Setup_Demo_File
{
    protected static $instance;

    public static function init()
    {
        is_null( self::$instance ) AND self::$instance = new self;
        return self::$instance;
    }

    public function __construct()
    {
        add_action( current_filter(), array( $this, 'load_files' ), 30 );
    }

    public function load_files()
    {
        foreach ( glob( plugin_dir_path( __FILE__ ).'inc/*.php' ) as $file )
            include_once $file;
    }
}

Файл установки:

<?php
defined( 'ABSPATH' ) OR exit;

class WCM_Setup_Demo_File_Inc
{
    public static function on_activation()
    {
        if ( ! current_user_can( 'activate_plugins' ) )
            return;
        $plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';
        check_admin_referer( "activate-plugin_{$plugin}" );

        # Uncomment the following line to see the function in action
        # exit( var_dump( $_GET ) );
    }

    public static function on_deactivation()
    {
        if ( ! current_user_can( 'activate_plugins' ) )
            return;
        $plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';
        check_admin_referer( "deactivate-plugin_{$plugin}" );

        # Uncomment the following line to see the function in action
        # exit( var_dump( $_GET ) );
    }

    public static function on_uninstall()
    {
        if ( ! current_user_can( 'activate_plugins' ) )
            return;
        check_admin_referer( 'bulk-plugins' );

        // Important: Check if the file is the one
        // that was registered during the uninstall hook.
        if ( __FILE__ != WP_UNINSTALL_PLUGIN )
            return;

        # Uncomment the following line to see the function in action
        # exit( var_dump( $_GET ) );
    }
}

(2) Обновления плагинов

Если вы напишите плагин, который имеет свою собственную таблицу или параметры БД, могут возникнуть ситуации, когда вам нужно что-то изменить или обновить.

К сожалению, пока нет возможности запустить что-либо при установке плагина / темы или обновлении / обновлении. Рад, что есть обходной путь: подключите пользовательскую функцию к пользовательской опции (да, это хромая - но она работает).

function prefix_upgrade_plugin() 
{
    $v = 'plugin_db_version';
    $update_option = null;
    // Upgrade to version 2
    if ( 2 !== get_option( $v ) ) 
    {
        if ( 2 < get_option( $v ) )
        {
            // Callback function must return true on success
            $update_option = custom_upgrade_cb_fn_v3();

            // Only update option if it was an success
            if ( $update_option )
                update_option( $v, 2 );
        }
    }

    // Upgrade to version 3, runs just after upgrade to version 2
    if ( 3 !== get_option( $v ) ) 
    {
        // re-run from beginning if previous update failed
        if ( 2 < get_option( $v ) )
            return prefix_upgrade_plugin();

        if ( 3 < get_option( $v ) )
        {
            // Callback function must return true on success
            $update_option = custom_upgrade_cb_fn_v3();

            // Only update option if it was an success
            if ( $update_option )
                update_option( $v, 3 );
        }
    }

    // Return the result from the update cb fn, so we can test for success/fail/error
    if ( $update_option )
        return $update_option;

return false;
}
add_action('admin_init', 'prefix_upgrade_plugin' );

Источник

Эта функция обновления - не очень хороший / хорошо написанный пример, но, как сказано: это пример, и техника работает хорошо. Улучшит это с более поздним обновлением.

кайзер
источник
1
Это здорово, НО я действительно хочу знать, что я должен включить в свой метод деактивации ... например, должен ли я удалить свои таблицы в базе данных или оставить их на случай, если пользователь передумает и повторно активирует плагин ?
красная консерватория
1
Объявление "НО": я упомянул, что есть 3 метода. Один для активации, один для временной деактивации и один для удаления. Imho «удалить» говорит «Удалить меня и все, что я сделал», в то время как «деактивировать» является временным состоянием и может быть переделано. Но: смотрите обновление. Я добавил комментарии о вашем Q +, добавил несколько рекомендаций по разработке.
Кайзер
3
Ах, теперь я понимаю. Просто вопрос, когда вызывается неустановленное? Когда файлы будут удалены ??
красная консерватория
1
@aendrew Они используются только в стороне check_admin_referer(). Они не нуждаются в дезинфекции, потому что ядро ​​не делает это само по себе и в любом случае будет сравнивать его с неанизированными $_REQUESTзначениями. Но если они начинают плакать как маленькие девочки из-за этого, просто используйте filter_var()или esc_attr()на это.
Кайзер
2
Вы не должны проверять WP_UNINSTALL_PLUGIN в функции обратного вызова при использовании wp_register_uninstall_hook, только если вы используете uninstall.php
paul
17

Чтобы проверить текущую систему на наличие необходимых функций, таких как версия PHP или установленные расширения, вы можете использовать что-то вроде этого:

<?php  # -*- coding: utf-8 -*-
/**
 * Plugin Name: T5 Check Plugin Requirements
 * Description: Test for PHP version and installed extensions
 * Plugin URI:
 * Version:     2013.03.31
 * Author:      Thomas Scholz
 * Author URI:  http://toscho.de
 * Licence:     MIT
 * License URI: http://opensource.org/licenses/MIT
 */

/*
 * Don't start on every page, the plugin page is enough.
 */
if ( ! empty ( $GLOBALS['pagenow'] ) && 'plugins.php' === $GLOBALS['pagenow'] )
    add_action( 'admin_notices', 't5_check_admin_notices', 0 );

/**
 * Test current system for the features the plugin needs.
 *
 * @return array Errors or empty array
 */
function t5_check_plugin_requirements()
{
    $php_min_version = '5.4';
    // see http://www.php.net/manual/en/extensions.alphabetical.php
    $extensions = array (
        'iconv',
        'mbstring',
        'id3'
    );
    $errors = array ();

    $php_current_version = phpversion();

    if ( version_compare( $php_min_version, $php_current_version, '>' ) )
        $errors[] = "Your server is running PHP version $php_current_version but
            this plugin requires at least PHP $php_min_version. Please run an upgrade.";

    foreach ( $extensions as $extension )
        if ( ! extension_loaded( $extension ) )
            $errors[] = "Please install the extension $extension to run this plugin.";

    return $errors;

}

/**
 * Call t5_check_plugin_requirements() and deactivate this plugin if there are error.
 *
 * @wp-hook admin_notices
 * @return  void
 */
function t5_check_admin_notices()
{
    $errors = t5_check_plugin_requirements();

    if ( empty ( $errors ) )
        return;

    // Suppress "Plugin activated" notice.
    unset( $_GET['activate'] );

    // this plugin's name
    $name = get_file_data( __FILE__, array ( 'Plugin Name' ), 'plugin' );

    printf(
        '<div class="error"><p>%1$s</p>
        <p><i>%2$s</i> has been deactivated.</p></div>',
        join( '</p><p>', $errors ),
        $name[0]
    );
    deactivate_plugins( plugin_basename( __FILE__ ) );
}

Тест с проверкой на PHP 5.5:

введите описание изображения здесь

Фуксия
источник
Прикосновение сбито с толку, поэтому в принципе здесь нет вызова register_activation_hook- почему бы не использовать его? Также будет ли этот огонь до или после register_activation_hookи сработает, register_activation_hookдаже если вышеперечисленное не пройдет?
orionrush
Он запускается только после активации на странице плагина.
fuxia
Я вижу - но если плагин активируется за пределами страницы плагина (скажем, как часть зависимости от темы), тогда ваши проверки будут пропущены, нет? Поэтому я попытался перейти add_action( 'admin_notices', 't5_check_admin_notices', 0 );в активационную зацепку, и плагин активируется без выполнения проверок. , ,
orionrush
@kaiser объяснил, как работает хук активации, я хотел показать альтернативу. Если плагин не активирован на странице плагина, может произойти фатальная ошибка, да. Этот подход не может работать на хуке активации без серьезной перезаписи, потому что этот хук срабатывает после admin_notices.
fuxia
На самом деле просто наткнулся на легкий путь: stackoverflow.com/a/13927297/362445
orionrush