Создание продвинутой системы уведомлений для WordPress
Как поступить, если вам ближе второй вариант? В таком случае вы можете задать поле с метаданными пользователей, которое будет хранить информацию для идентификации требуемых подписчиков. Ту же самую информацию можно использовать для пометки записей при публикации. В зависимости от архитектуры вашего сайта, вы можете хранить метаданные в рубриках, метках, произвольных таксономиях или произвольных полях. В этой статье мы покажем вам, как реализовать возможность почтовых уведомлений для подписчиков, и как связать их с определенной географической локацией.
Могу ли я использовать плагин для этого
Если WordPress – ваша CMS, вы всегда можете воспользоваться любым подходящим плагином: к примеру, более универсальным Jetpack или более специализированным Subscribe 2.
Jetpack – простой в использовании плагин, в то время как Subscribe 2 – более специализированный и полнофункциональный плагин. Оба плагина позволяют вам отправлять почтовые уведомления подписчикам всякий раз, когда запись была опубликована. К сожалению, ни один из этих плагинов не позволяет уведомлять определенных пользователей об определенном контенте. Мы же хотим выбирать записи на основе произвольных полей и отправлять эти записи определенным группам пользователей. Увы, но плагин не поможет нам с этим.
Что мы будем делать
Мы добавим некоторую функциональность к ядру WordPress, и CMS позволяет нам объявить наши собственные функции в основном файле плагина. Мы не будем сильно погружаться в разработку плагинов. Для этого вы всегда можете получить соответствующую информацию в кодексе.
Нам нужно будет выполнить несколько следующих задач:
- Мы добавим два мета поля к пользовательскому профилю, первое из которых будет хранить название локации, а второе – будет определять, хочет или нет пользователь получать уведомления на почту.
- Мы добавим произвольную мета панель к странице редактирования записи, которая будет содержать произвольное поле с локацией.
- Мы выберем пользователей, которые должны получить уведомление на почту, и отправим им письма.
Добавляем мета-поля к пользовательским профилям
WordPress хранит данные пользователей в таблицах wp_users и wp_usermeta.
Таблица wp_users хранит список всех пользователей сайта, в то время как таблица wp_usermeta содержит все мета-данные, связанные с профилем каждого пользователя. Метаданные регистрируются в виде пар «ключ-значение» (key-value) с полями meta_key и meta_value.
WordPress генерирует набор метаданных, среди которых nickname, first_name, last_name, description и wp_capabilities. Большая часть этих данных автоматически связывается с профилем каждого пользователя, и пользователь может отредактировать эти данные позже на своей странице профиля в консоли.
Чтобы выполнить нашу первую задачу, мы должны добавить два мета поля к странице профиля пользователей. Эти мета-поля будут хранить в себе названия географической локации, а также дадут возможность пользователям активировать (или деактивировать) уведомления.
В основном файле плагина давайте зададим глобальный ассоциативный массив, который будет состоять из названий штатов США:
$smashing_notification_states = array( 'AL' => 'Alabama', 'AK' => 'Alaska', 'AZ' => 'Arizona', 'AR' => 'Arkansas', 'CA' => 'California', 'CO' => 'Colorado', 'CT' => 'Connecticut', 'DE' => 'Delaware', 'FL' => 'Florida', 'GA' => 'Georgia', 'HI' => 'Hawaii', 'ID' => 'Idaho', 'IL' => 'Illinois', 'IN' => 'Indiana', 'IA' => 'Iowa', 'KS' => 'Kansas', 'KY' => 'Kentucky', 'LA' => 'Louisiana', 'ME' => 'Maine', 'MD' => 'Maryland', 'MA' => 'Massachusetts', 'MI' => 'Michigan', 'MN' => 'Minnesota', 'MS' => 'Mississippi', 'MO' => 'Missouri', 'MT' => 'Montana', 'NE' => 'Nebraska', 'NV' => 'Nevada', 'NH' => 'New Hampshire', 'NJ' => 'New Jersey', 'NM' => 'New Mexico', 'NY' => 'New York', 'NC' => 'North Carolina', 'ND' => 'North Dakota', 'OH' => 'Ohio', 'OK' => 'Oklahoma', 'OR' => 'Oregon', 'PA' => 'Pennsylvania', 'RI' => 'Rhode Island', 'SC' => 'South Carolina', 'SD' => 'South Dakota', 'TN' => 'Tennessee', 'TX' => 'Texas', 'UT' => 'Utah', 'VT' => 'Vermont', 'VA' => 'Virginia', 'WA' => 'Washington', 'WV' => 'West Virginia', 'WI' => 'Wisconsin', 'WY' => 'Wyoming' );
Благодаря этому массиву мы можем сгенерировать выводимое меню, чтобы избежать ошибок ввода со стороны пользователей. Далее нам нужно будет добавить два поля формы к странице пользовательского профиля. Чтобы сделать это, мы воспользуемся следующими хуками:
add_action( 'show_user_profile', 'smashing_show_user_meta_fields' ); add_action( 'edit_user_profile', 'smashing_show_user_meta_fields' );
Здесь show_user_profile инициируется в том случае, если пользователь просматривает свой собственный профиль; edit_user_profile инициируется в том случае, если пользователь просматривает профиль другого пользователя.
Функция обратного вызова печатает разметку:
/** * Show custom user profile fields. * * @param obj $user The user object. */ function smashing_show_user_meta_fields( $user ) { global $smashing_notification_states; ?> <h3><?php _e( 'Smashing profile information', 'smashing' ); ?></h3> <table class="form-table"> <tr> <th scope="row"><?php _e( 'State', 'smashing' ); ?></th> <td> <label for="state"> <select name="state"> <option value="" <?php selected( get_user_meta( $user->ID, 'state', true ), "" ); ?>>Select</option> <?php foreach ($smashing_notification_states as $key => $value) { ?> <option value="<?php echo $key; ?>" <?php selected( esc_attr( get_user_meta( $user->ID, 'state', true ) ), $key ); ?>><?php echo $value; ?></option> <?php } ?> </select> <?php _e( 'Select state', 'smashing' ); ?> </label> </td> </tr> <tr> <th scope="row"><?php _e( 'Notifications', 'smashing' ); ?></th> <td> <label for="notification"> <input id="notification" type="checkbox" name="notification" value="true" <?php checked( esc_attr( get_user_meta( $user->ID, 'notification', true ) ), 'true' ); ?> /> <?php _e( 'Subscribe to email notifications', 'smashing' ); ?> </label> </td> </tr> </table> <?php }
Таблица содержит два произвольных мета-поля. Первое из них – это меню, пункты которого генерируются в цикле foreach, проходящего по глобальному массиву $smashing_notification_states. Таким образом, пользователю не нужно вводить название своего штата, он может просто выбрать его из выпадающего списка.
Как вы можете видеть, мы вызываем функцию selected() дважды внутри тегов option; selected() – это WordPress функция для сравнения двух строк. Если строки имеют одно и то же значение, то в таком случае функция печатает selected=’selected’; иначе она выводит пустую строку.
Во время первого вызова selected() мы сравниваем текущее значение опции (‘state’) с пустой строкой (это означает, что ни один штат не был выбран). В процессе прохода по массиву $smashing_notification_states мы сравниваем значение каждого элемента с текущим значением мета-поля ‘state’. Таким образом, мы можем автоматически выбрать опцию, соответствующую существующему значению ‘state’.
Второе мета-поле, добавленное к профилям пользователей – это чекбокс. Его значение может быть ‘true’ или ‘false’, что зависит от того, захотел ли пользователь получать уведомления или нет. По аналогии с selected(), функция checked() печатает строку checked=’checked’, если ее два аргумента имеют одинаковое значение. Естественно, checked() применяется к чекбоксам и радиокнопкам.
Теперь, когда у нас есть поля, мы можем сохранить пользовательский ввод. Нам понадобятся два хука, чтобы сохранить пользовательские данные:
add_action( 'personal_options_update', 'smashing_save_user_meta_fields' ); add_action( 'edit_user_profile_update', 'smashing_save_user_meta_fields' );
Здесь personal_options_update инициируется в том случае, когда пользователь просматривает свою собственную страницу профиля; edit_user_profile_update инициируется в том случае, когда пользователь имеет достаточные права для просмотра страницы профиля другого пользователя. У нас есть два хука, но только одна функция обратного вызова:
/** * Store data in wp_usermeta table. * * @param int $user_id the user unique ID. */ function smashing_save_user_meta_fields( $user_id ) { if ( !current_user_can( 'edit_user', $user_id ) ) return false; if( isset($_POST['state']) ) update_user_meta( $user_id, 'state', sanitize_text_field( $_POST['state'] ) ); if( !isset($_POST['notification']) ) $_POST['notification'] = 'false'; update_user_meta( $user_id, 'notification', sanitize_text_field( $_POST['notification'] ) ); }
Эта функция проверяет, разрешено ли для пользователя edit_user, и если current_user_can установлен в true, то в таком случае данные проверяются и сохраняются в таблицу wp_usermeta.
Произвольная мета-панель и произвольные поля
Мы должны решить, какой контент будет включен в уведомления подписчиков. Это решение будет зависеть от архитектуры сайта. В данном случае мы ограничимся обычными записями, однако вы могли бы взять вместо них произвольный тип записей. Выбор зависит от ваших потребностей.
Мы создадим произвольную мета-панель, содержащую набор произвольных полей. Эти поля будут использоваться для хранения адреса, города, штата, а также некоторых других данных, связанных с расположением. Два других произвольных поля позволят включать и отключать уведомления для каждой конкретной записи, а также они зарегистрируют количество писем, отправленных пользователям, как только новая запись была опубликована. Давайте воспользуемся еще одним хуком:
add_action( 'add_meta_boxes', 'smashing_add_meta_box' ); function smashing_add_meta_box(){ $screens = array( 'post' ); // possible values: 'post', 'page', 'dashboard', 'link', 'attachment', 'custom_post_type' foreach ($screens as $screen) { add_meta_box( 'smashing_metabox', // $id - meta_box ID __( 'Venue information', 'smashing' ), // $title - a title for the meta_box container 'smashing_meta_box_callback', // $callback - the callback that outputs the html for the meta_box $screen, // $post_type - where to show the meta_box. Possible values: 'post', 'page', 'dashboard', 'link', 'attachment', 'custom_post_type' 'normal', // $context - possible values: 'normal', 'advanced', 'side' 'high' // $priority - possible values: 'high', 'core', 'default', 'low' ); } }
Здесь add_meta_box принимает 7 аргументов: уникальный ID для мета-панели, заголовок, функцию обратного вызова, значение для screen, контекст (т.е. участок страницы, где будет выводиться мета-панель), а также приоритет и callback-аргумент. Поскольку мы не задаем значение для callback-аргумента, объект $post будет единственным аргументом, переданным в smashing_meta_box_callback. Наконец, давайте зададим функцию обратного вызова для вывода нашей мета-панели:
/* * Print the meta_box * * @param obj $post The object for the current post */ function smashing_meta_box_callback( $post ){ global $smashing_notification_states; // Add a nonce field wp_nonce_field( 'smashing_meta_box', 'smashing_meta_box_nonce' ); $address = esc_attr( get_post_meta( get_the_ID(), 'address', true ) ); $city = esc_attr( get_post_meta( get_the_ID(), 'city', true ) ); $state = esc_attr( get_post_meta( get_the_ID(), 'state', true ) ); $zip = esc_attr( get_post_meta( get_the_ID(), 'zip', true ) ); $phone = esc_attr( get_post_meta( get_the_ID(), 'phone', true ) ); $website = esc_attr( get_post_meta( get_the_ID(), 'website', true ) ); $disable = esc_attr( get_post_meta( get_the_ID(), 'disable', true ) ); ?> <table id="venue"> <tbody> <tr> <td class="label"><?php _e( 'Address', 'smashing' ); ?></td> <td><input type="text" id="address" name="venue[address]" value="<?php echo $address; ?>" size="30" /></td> </tr> <tr> <td><?php _e( 'City', 'smashing' ); ?></td> <td><input type="text" id="city" name="venue[city]" value="<?php echo $city; ?>" size="30" /></td> </tr> <tr> <td><?php _e( 'State', 'smashing' ); ?></td> <td> <select name="venue[state]"> <option value="" <?php selected( $state, "" ); ?>>Select</option> <?php foreach ($smashing_notification_states as $key => $value) { ?> <option value="<?php echo $key; ?>" <?php selected( $state, $key ); ?>><?php echo $value; ?></option> <?php } ?> </select> </td> </tr> <tr> <td><?php _e( 'Disable notification', 'smashing' ); ?></td> <td><input id="disable" type="checkbox" name="venue[disable]" value="true" <?php checked( $disable, 'true' ); ?> /></td> </tr> </tbody> </table> <?php }
Сначала мы инициализируем глобальный массив и регистрируем поле со случайными данными. Затем мы добавляем два простых текстовых поля. Атрибут name задан в виде элемента массива, в то время как значение вносится в соответствующее произвольное поле. Наконец, добавляются основные произвольные поля.
Как и в случае с метаданными пользователей, мы добавляем меню, пункты которого являются элементами глобального массива $smashing_notification_states. Как только мы создадим меню, давайте перейдем к чекбоксам, связанным с активацией и деактивацией уведомлений к отдельной записи.
Теперь мы должны сохранить данные: наш хук в данном случае – это save_post. Мы выполняем массу задач с помощью функции обратного вызова. Взгляните во встроенную документацию для получения дополнительной информации.
add_action( 'save_post', 'smashing_save_custom_fields' ); /* * Save the custom field values * * @param int $post_id the current post ID */ function smashing_save_custom_fields( $post_id ){ // Check WP nonce if ( !isset( $_POST['smashing_meta_box_nonce'] ) || ! wp_verify_nonce( $_POST['smashing_meta_box_nonce'], 'smashing_meta_box' ) ) return; // Return if this is an autosave if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return; // check the post_type and set the correspondig capability value $capability = ( isset( $_POST['post_type'] ) && 'page' == $_POST['post_type'] ) ? 'edit_page' : 'edit_post'; // Return if the user lacks the required capability if ( !current_user_can( $capability, $post_id ) ) return; if( !isset($_POST['venue']['disable']) ) $_POST['venue']['disable'] = 'false'; // validate custom field values $fields = ( isset( $_POST['venue'] ) ) ? (array) $_POST['venue'] : array(); $fields = array_map( 'sanitize_text_field', $fields ); foreach ($fields as $key => $value) { // store data update_post_meta( $post_id, $key, $value ); } }
Наша мета панель готова. Выглядит она следующим образом:
Создаем систему уведомлений
Если вы работаете с произвольными типами записей, вам понадобится хук publish_{$post_type} (т.е. publish_recipes, publish_events и т.д.). Однако, поскольку мы работаем с записями, мы воспользуемся хуком publish_post:
add_action( 'publish_post', 'smashing_notify_new_post' ); /* * Notify users sending them an email * * @param int $post_ID the current post ID */ function smashing_notify_new_post( $post_ID ){ global $smashing_notification_states; $url = get_permalink( $post_ID ); $state = get_post_meta( $post_ID, 'state', true ); if( 'true' == get_post_meta( $post_ID, 'disable', true ) ) return; // build the meta query to retrieve subscribers $args = array( 'meta_query' => array( array( 'key' => 'state', 'value' => $state, 'compare' => '=' ), array( 'key' => 'notification', 'value' => 'true', 'compare' => '=' ) ), 'fields' => array( 'display_name', 'user_email' ) ); // retrieve users to notify about the new post $users = get_users( $args ); $num = 0; foreach ($users as $user) { $to = $user->display_name . ' <' . $user->user_email . '>'; $subject = sprintf( __( 'Hei! We have news for you from %s', 'smashing' ), $smashing_notification_states[$state] ); $message = sprintf( __( 'Hi %s!', 'smashing' ), $user->display_name ) . "rn" . sprintf( __( 'We have a new post from %s', 'smashing' ), $smashing_notification_states[$state] ) . "rn" . sprintf( __( 'Read more on %s', 'smashing' ), $url ) . '.' . "rn"; $headers[] = 'From: Yourname <you@yourdomain.com>'; $headers[] = 'Reply-To: you@yourdomain.com'; if( wp_mail( $to, $subject, $message, $headers ) ) $num++; } // a hidden custom field update_post_meta( $post_ID, '_notified_users', $num ); return $post_ID; }
Опять же, мы объявляем глобальный массив $smashing_notification_states. Две переменные $url и $state будут хранить постоянную ссылку на запись и штат соответственно. В следующем условии мы проверяем значение произвольного поля disable. Если оно true, то в таком случае мы выходим из функции. Нам нужно вытащить из базы данных всех пользователей, у которых мета-поле state имеет то же самое значение, то и произвольное поле state текущей записи, и для этого мы используем функцию get_users().
Функция wp_mail принимает пять аргументов: получатель (получатели), тема, сообщение, заголовки, вложения. Получателей можно вполне передать в виде массива либо в виде адресов, разделенных запятыми. Мы могли бы передать в функцию все адреса сразу, однако такая реализация сделает адреса публично видимыми (так работает wp_mail()).
Таким образом, мы пойдем по массиву $users и будем постоянно вызывать wp_mail (что невозможно реализовать для большого количества писем, о чем мы расскажем далее). В случае успеха wp_mail вернет true. Счетчик увеличится на 1, и цикл продолжится для следующего пользователя.
Когда цикл foreach закончит свою работу, текущее значение $num будет зарегистрировано в скрытом произвольном поле _notified_users (обратите внимание на подчеркивание, стоящее в названии поля).
Увы, но цикл, выполняющийся снова и снова сотни раз, может значительно замедлить скрипт, что указано в описании функции mail():
Стоит отметить, что функция mail() не подходит для обработки больших объемов писем в цикле. Эта функция открывает и закрывает SMTP-сокет для каждого email’а, что не слишком эффективно.
Для отправки большого количества писем рассмотрите пакеты PEAR::Mail и PEAR::Mail_Queue.
Мы можем обойти это, передав в функцию почтовые адреса в виде BCC, задав их в $headers, как показано далее:
function smashing_notify_new_post( $post_ID ){ global $smashing_notification_states; $url = get_permalink( $post_ID ); $state = get_post_meta( $post_ID, 'state', true ); if( 'true' == get_post_meta( $post_ID, 'disable', true ) ) return; // build the meta query to retrieve subscribers $args = array( 'meta_query' => array( array( 'key' => 'state', 'value' => $state, 'compare' => '=' ), array( 'key' => 'notification', 'value' => 'true', 'compare' => '=' ) ), 'fields' => array( 'display_name', 'user_email' ) ); // retrieve users to notify about the new post $users = get_users( $args ); $num = 0; $to = 'Yourname <you@yourdomain.com>'; $subject = sprintf( __( 'Hei! We have news for you from %s', 'smashing' ), $smashing_notification_states[$state] ); $message = __( 'Hi ', 'smashing' ) . "rn" . sprintf( __( 'We have a new post from %s', 'smashing' ), $smashing_notification_states[$state] ) . "rn" . sprintf( __( 'Read more on %s', 'smashing' ), $url ) . '.' . "rn"; $headers[] = 'From: Yourname <you@yourdomain.com>'; $headers[] = 'Reply-To: you@yourdomain.com'; foreach ($users as $user) { $headers[] = 'Bcc: ' . $user->user_email; $num++; } if( wp_mail( $to, $subject, $message, $headers ) ) update_post_meta( $post_ID, '_notified_users', $num ); return $post_ID; }
Как вы можете видеть, в случае успешного выполнения wp_mail() мы обновляем произвольное поле _notified_user, внося в него значение $num. Однако в коде выше $num хранит количество полученных пользователей, а не количество вызовов wp_mail().
Наконец, если ни одно из решений не отвечает вашим требованиям, вы можете рассмотреть использование сторонних систем почтовых уведомлений, таких как, к примеру, MailChimp или FeedBurner.
Замечание по поводу переключения статусов
Мы подцепляли функцию обратного вызова smashing_notify_new_post к хуку publish_post. Этот хук инициируется всякий раз, когда статус существующей записи меняется на publish. К несчастью, publish_post не выполняется, когда публикуется новый пост. Поэтому, чтобы отправить уведомление, сначала сохраните запись как draft или pending. Если вы хотите отправлять письма подписчикам всякий раз, когда запись опубликована, рассмотрите использование хука save_post:
add_action( 'save_post', 'smashing_notify_new_post' ); /* * Save the custom field values * * @param int $post_id the current post ID */ function smashing_notify_new_post( $post_ID ){ global $smashing_notification_states; if( 'publish' != get_post_status( $post_ID ) ) return; ... }
Изучите кодекс, чтобы узнать больше информации о переключении статусов, а также о хуке save_post.
Сообщение с подтверждением
Если вы работаете с хуком publish_post, вы в скором времени заметите, что тестирование ваших скриптов становится достаточно сложным процессом. Когда новый пост публикуется, WordPress загружает скрипт, сохраняющий данные, и, когда он закончит свою работу, перенаправляет пользователя на страницу редактирования записи. Такой двойной редирект не позволяет вывести на экран значения переменных.
Сообщение с подтверждением – хороший выход из этой ситуации. Это решение позволяет нам проверять значения переменных и выводить полезную информацию: в частности, сколько раз wp_mail была вызвана (или сколько пользователей было уведомлено).
Помните переменную $num? Ее значение хранится в скрытом поле _notified_users. Теперь мы можем получить это значение и вывести его на экран в сообщении с помощью фильтра.
Благодаря фильтру post_updated_messages мы можем настроить сообщения с подтверждением в WordPress и вывести их на экран вне зависимости от того, сохраняется новый пост или публикуется (в кодексе нет описания этого фильтра, есть только пример его использования). Вот функция обратного вызова, которая позволяет нам настроить сообщение при публикации записи:
add_filter( 'post_updated_messages', 'smashing_updated_messages' ); /** * Post update messages. * * See /wp-admin/edit-form-advanced.php * * @param array $messages Existing post update messages. * * @return array Amended post update messages with new update messages. */ function smashing_updated_messages( $msgs ){ $post = get_post(); $post_type = get_post_type( $post ); $post_type_object = get_post_type_object( $post_type ); $num = get_post_meta( $post->ID, '_notified_users', true ); if ( $post_type_object->publicly_queryable ) { $msgs[$post_type][6] .= ' - ' . $num . __( ' notifications sent.', 'smashing' ); } return $msgs; }
wp_mail и SMTP
Функция wp_mail() в WordPress работает аналогично функции mail() в PHP. Будет ли письмо успешно отправлено, зависит от параметров php.ini, однако большинство хостингов включает SMTP в свои услуги. Если вы не в состоянии настроить SMTP, вы всегда можете воспользоваться внешним SMTP-сервисом, и использовать его в тандеме с плагином WP Mail SMTP, который направляет ваши письма через SMTP-сервис.
Будьте осторожны при сохранении данных: поле from должно иметь то же самое значение, что и почтовый адрес в вашем аккаунте; иначе сервер может выдать сообщение об ошибке.
Плагин не является обязательным: WordPress позволяет переписывать параметры php.ini с помощью скрипта через хук phpmailer_init. Этот хук позволяет нам передать наши собственные параметры объекту PHPMailer. За дополнительной информацией обратитесь к кодексу.
Дизайн писем
Как и в случае с функцией mail() в PHP, функция wp_mail() имеет стандартный Content Type как text/plain. И, как и в случае с mail(), функция wp_mail() позволяет нам задавать Content Type в text/html. Вы можете задать другой Content Type, используя wp_mail_content_type или установив следующие заголовки:
$headers[] = "MIME-Version: 1.0"; $headers[] = "Content-Type: text/html; charset=ISO-8859-1";
Естественно, есть много разных плагинов, которые позволят вам управлять вашим Content Type через панель администратора. WP Better Emails – один из таких плагинов. Этот плагин заставляет WordPress отправлять HTML-письма, однако это не его единственная возможность. Он также позволяет администраторам создать свои собственные шаблоны для писем и отправить тестовое письмо.
Следующее изображение показывает, что будет прислано в почтовый ящик пользователя Gmail.
Заключение
Наша система уведомлений готова к использованию. Создавая ее, мы раскрыли массу базовых возможностей WordPress: пользовательские метаданные, произвольные мета поля, произвольные запросы, рассылка писем через скрипт и т.д. Если вам интересен весь код целиком, вы можете загрузить его по следующей ссылке. Не забудьте заменить все вхождения строки you@yourdomain.com на ваш собственный адрес.
Вы могли бы расширить это гораздо больше. Вы можете интегрировать эту систему с помощью стороннего приложения для управления письмами, такого как MailChimp и Mad Mimi. Вы можете создать роскошные шаблоны писем. Или вы можете создать более персонализированные уведомления. Все зависит от вас и ваших потребностей.
Источник: smashingmagazine.com