Веб-дизайн и веб-разработка для компаний и частных лиц в Чешской Республике.
+420 608 713 199

10 распространенных ошибок при разработке плагинов для WordPress

10 распространенных ошибок при разработке плагинов для WordPress

Как я и обещал, я собираюсь поделиться с вами списком наиболее часто распространенных ошибок, недопонимания, плохих привычек и неправильных решений, которые я встретил, когда проводил аудит 43 плагинов для WordPress. Были и реально критичные уязвимости (я связался с тремя авторами плагинов после обнаружения серьезных уязвимостей в их разработках). Некоторые проблемы были скорее разражающими факторами нежели реальными багами. Иногда плохо написанный код приводил к бесполезному расходованию серверных ресурсов, которого можно было избежать. Но все эти проблемы имеют кое-что общее: их легко устранить.

10 распространенных примеров плохого кода

1. Это не плагин – это помойка

Я был реально шокирован количеством кодеров, которые поставляют свои разработки без единого коментария, без отступов или с абсолютно хаотичной индентацией (серьезно!! просто *никаких* отступов!! вы можете в это поверить??). Будучи судьёй я выплеснул свой гнев на их конечную оценку. Будучи пользователем, я бы никогда не установил плагин, который выглядит как помойка просто потому, что если оно выглядит как помойка, оно и есть помойка.

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

Я не буду развивать эту тему далее, потому что это просто обычный здравый смысл, но если вы один из тех любителей помоек, кто реально не видит здесь проблемы, пожалуйста, прочитайте вот эту статью Make clean and readable sources: why and how (возможно, позднее мы добавим перевод и этой статьи на наш сайт – artprima.cz). Возможно, вы поменяете свое мнение и начнете соблюдать WordPress Coding Standards (англ.).

2. Слишком общие названия функций

Опять приходится говорить о банальных вещах, однако около 40% плагинов, которые я аудировал, используют слииишком общие названия функций. Основная задача здесь убедиться в том, что ваш плагин никогда не вызовет ошибку php “Fatal error: Cannot redeclare your poorly named function”.

Названия функций должны быть описательными (говорящими за себя) и уникальными. Я нашел плагины с функциями, названными весьма незатейливо pretty_title() или pages(), или update_options(). Какая ирония, кодер опубликовал несколько плагинов, которые не будут работать на одном и том же блоге просто потому, что используют одни и те же названия функций.

Более правильными названиями функций были бы, например, joeplugin_post_pretty_title(), joeplugin_list_pages() или joeplugin_update_options(). Альтернативой для сохранения короткого имени функции является использование классов (которые тоже должны иметь уникальное и говорящее за себя имя)

3. Что? 87 новых записей в таблице настроек?

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

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

Из этого правила есть одно исключение: если ваш плагин содержит туеву хучу настроек и только некоторые из них будут использоваться при каждой загрузке плагина в то время, как остальные будут использоваться только на странице администрирования, в этом случае имеет смысл хранить их в двух или более записях и устанавливать малоизвестный параметр autoload как ‘no’.

WordPress работает следующим образом: при инициализации он загружает все необходимые файлы и потом выполняет SQL-запрос: SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes'. Это загружает в память *все* опции, для которых autoload установлено в ‘yes’ (значение по умолчанию).

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

// 'no' = не загружать автоматически
add_option('option_name', 'option_value', '', 'no');

4. Для чего вы создаете новые таблицы?

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

Конечно же, возможно, что ваш плагин нуждается в создании одной или нескольких дополнительных таблиц. Однако перд тем как создавать свои, убедитесь, что существующие таблицы не подходят для решения ваших задач. По возможности старайтесь использовать таблицы wp_options и wp_*meta.

Если вам *действительно* нужно создать таблицы, дайте им название, которое имеет смысл. Всегда начинайте с $wpdb->prefix, добавьте также префикс-идентификатор своего плагина, и добавьте слово(-а) для описания назначения таблицы. В итоге у вас получится что-то типа такого: $wpdb->prefix.’myplugintitle_custom_logs’

5. Отсутствует функция деинсталляции (uninstall)

Начиная с версии 2.7, WordPress предлагает функционал для деинсталляции плагинов. Существует два вариант использования этого функционала: с помощью деинсталляционного хука (uninstall hook), что может показаться несколько сложноватым (но это не так на самом деле), и с использованием деинсталляционного файла, проще которого не может быть.

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

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

6. Пользовательский javascript или CSS на каждой странице в панели администрирования

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

На самом деле вставить ваш скрипт или CSS только на ваших страницах весьма просто.

7. Небезопасные формы без использования nonce или неправильное понимание nonce

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

Проблема, которую я встречал много раз в плагинах, которые я рассматривал, в том, что данные форм принимаются и обрабатываются даже без проверки is_admin() или current_user_can(). В сущности, это значит, что кто угодно (даже пользователь без прав) сможет передать данные на ваш сайт и поиграться с плагином.

Но даже проверка авторизации может быть недостаточной. Представье плагин, который, скажем, может удалять посты. Достаточно просто сделать веб-страницу, которая будет передавать данные с помощью метода POST на страницу настроек плагина на чьем-нибудь сайте. Далее эта страница маскируется с помощью сервиса сокращению ссылок и кидается, например, в Твиттер. Один клик по этой ссылке и вы будете перенаправлены в свой собственный блог в админку, где у вас, разумеется, есть права доступа, что приведет к удалению постов. Получается, что у вас есть права, но не было намерения.

Это тот случай, когда нужно вспомнить (или узнать) о существовании технологии nonce. Nonce функции создают временные уникальные коды, которые невозможно угадать, и проверяют, что переданные данные исходят изнутри админки, а не откуда-то извне.

Самой распространенной ошибкой, которую я видел, было то, что многие плагины просто добавляют поле nonce в форму. Само по себе это не будет работать, этого недостаточно. Вам нужны не только nonce-поля в форме, но и проверки этих полей там, где форма обрабатывается. Иначе это все равно, что объявить: “для посещения моего бара нужно иметь при себе паспорт”, но никогда не проверять его наличие.

8. Действия на основании непроверенных данных из GET-запроса

Я неоднократно видел подобные несколько даже пугающие конструкции

    add_action('init', 'myplugin_init');
    function myplugin_init() {
        global $wpdb;
        call_user_func($_GET['action']);
        $wpdb->query('DELETE from sometable WHERE somefield = '. $_GET['value']);
    }

Этот случай очень похож на предыдущий: использование непроверенного запроса (yoursite.com?action=bleh&value=wot), но в этот раз для исполнения кода или даже SQL-запросов. В этом случае неиспользование обработчиков безопасности даже более критично, чем в предыдущем примере, так как здесь даже не нужно вынуждать владельца сайта посещать определенный урл: любой может передать эти (потенциально вредные) данные.

Еще раз, nonce-функции это то, что вам нужно. Если вы хотите использовать $_GET[‘action’] в качестве переключателя действия, пожалуйста, сделайте следующее:

  • Вместо перенаправления юзера на site.com/?action=something, используйте следующий код:
        $url = "site.com/?action=something";
        $action = "myplugin-update";
        $link = wp_nonce_url( $url, $action );
        echo "<a href='$link'>click here</a>"; // whatever you're doing to echo the nonced link
    
  • В функции, обрабатывающей значение $_GET[‘action’], сделайте следующее:
        if ( isset( $_GET['action'] ) && $_GET['action'] == 'something' ) {
            check_admin_referer( 'myplugin-update' ); // die if invalid or missing nonce
           
            // rest of the code ...
        }
    

Видите? Использовать nonce не так уж и трудно. Один вызов в форме или ссылке и один в обработчике.

Существуют также другие функции, которые могут быть необходимы или, как минимум, иметь смысл здесь: is_admin(), чтобы убедиться, что мы в админке, current_user_can(), чтобы убедиться, что пользователь имеет право совершать данное действие.

9. Доверие вводу пользователя и передача его в SQL-запрос

Это одна из наиболее важных дыр в безопасности, особенно в купе с пунктом 8. И обнаружил я это в двух плагинах из рассмотренных. Каждый раз, когда вы выполняете SQL-запросы, содержащие пользовательские данные, валидируйте их. Основной риск здесь – SQL-инъекция.

Если вы передаете параметр, который должен должен быть типа integer, используйте intval() перед сохранением его в базе данных. Если вы разрешаете HTML, используйте esc_attr(), и т.д.

Как только данные для запроса валидированы, прогоните их через $wpdb->prepare() перед исполнением запроса. Метод wpdb::prepare() похож в использовании на sprintf() и производит для вас экранирование, квотирование и подстановку типов, которые вам потребуются. Это просто:

    $calvin = "6 years";
    $hobbes = "stuffed";
     
    // "Prepare" the query
    $sql = $wpdb->prepare( "INSERT INTO $wpdb->joeplugin_table( id, field1, field2 ) VALUES ( %d, %s, %s )", $_POST['id'], $calvin, $hobbes );
     
    // Run it
    $wpdb->query( $sql );

Если ваш плагин собирается взаимодействовать с MySQL, убедитесь, что вы ознакомились с классом wpdb.

10. Неверная локализация

Эта проблема оказалась самой распространенной среди тех, которые я встретил: многие почему-то считают, что __(‘some string’) вполне достаточно для создания переводимого плагина.

Правильным синтаксисом для поддержки плагином перевода является __('some string', 'myplugin'), где ‘myplugin’ — уникальный идентификатор текстового домена (text domain), который инициализуется с помощью load_plugin_textdomain().

Полный пример с подкаталогом ‘translations/‘ в каталоге вашего плагина:

    add_action('init', 'myplugin_load_translation_file');
     
    function myplugin_load_translation_file() {
        // relative path to WP_PLUGIN_DIR where the translation files will sit:
        $plugin_path = plugin_basename( dirname( __FILE__ ) .'/translations' );
        load_plugin_textdomain( 'myplugin', '', $plugin_path );
    }

Полный туториал по переводу плагинов можно прочитать здесь (англ.). Также вам стоит прочитать Кодекс про интернационализацию (i18n) для разработчиков WordPress.

Интересуют наши услуги?
Наймите нас