all07

Всего понемногу ...

Вселенский опыт говорит, что погибают царства не оттого, что тяжек быт или страшны мытарства.
А погибают оттого (и тем больней, чем дольше), что люди царства своего не уважают больше. (Булат Окуджава)

Те, кто готовы пожертвовать насущной свободой в обмен на то, чтобы получить временную безопасность, — недостойны ни свободы ни безопасности. (Бенджамин Франклин)

Война — это мир! Свобода — это рабство! Незнание — сила! (Джордж Оруэлл)

Использование протокола XML-RPC WordPress для публикации статей

В связи с официальным выходом стабильной версии WordPress 4.3 мне стало интересно изучить возможности удаленной публикации статей с использованием собственного интерфейса WordPress XML-RPC. До выхода последней версии я использовал интерфейс MetaWeblog. Один из возможных способов его применения с примером описан в этой статье.

На изучение вопроса удаленной публикации меня побудило удобство использования локального текстового редактора по сравнению с веб интерфейсом редактора WordPress. Я использую Notepad++ для редактирования статей, и делаю это исключительно в HTML коде, без какой-либо "визуализации" с последствиями применения которой мне приходилось не раз бороться. Кроме того, в моей недолгой практике работы с WordPress случалось пару раз потерять последние изменения при дисконнекте от Интернет моего радиоканала. Последняя копия черновика автоматически сохранилась полчаса назад, когда соединение еще было, нажимаю кнопку "сохранить" и... Все последние изменения пропадают, если только не успеешь за время попытки неудачного соединения с сервером выполнить в веб редакторе комбинацию Ctrl+A и Ctrl+C. Кроме того, практически все статьи пишутся офф-лайн и нет особой необходимости использовать именно веб редактор.

Главным для меня неудобством при работе с локальным HTML редактором было постоянное копирование готовых, отформатированных текстов в веб-форму для предварительного просмотра, при необходимости внесения изменений.

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

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

Как я уже сказал выше, за основу берется скрипт, описанный этой статье. Для простоты понимания и уменьшения объема из скрипта были убраны все дополнительные проверки, и код, отвечающий за вывод логов и отладочной информации. В начале рассмотрим основную функцию blog():

  • function blog( $filename ) {
  •   list( $meta, $postid ) = process( $filename ); // обработать файл статьи
  •  
  •   if( $postid == '' ) {       // Если в файле статьи не задан postid, то это  
  •     $id = create( $meta );    // новая статья, необходимо использовать wp.newPost
  •     $file = fopen( $filename, 'a'); // после создания статьи, полученный postid
  •     fwrite( $file, $id );           // дописать в конец файла статьи
  •     fclose( $file );
  •   } else {                    // В файле статье найден параметр с заданным postid
  •     update( $postid, $meta ); // вызвать wp.editPost для внесения измений в существующую статью
  •   }
  • }
  •  
  • $url = 'http://Имя_Вашего_Блога_WordPress';
  • $username = 'username';
  • $password = 'password';
  • $filename = $argv[1];         // Имя файла статьи передается, как 1-й аргумент
  •  
  • blog( $filename );

Переменная $postid определяет индивидуальный номер статьи WordPress, автоматически присваивается каждой статье при ее создании, и в дальнейшем используется для ее идентификации, при обращении к ней. Функции process() служит для создания необходимой структуры данных, в виде массива $meta, которая будет преобразовываться в XML формат и передаваться на сервер:

  • function process( $filename ) {
  •  
  •   $lines = file( $filename );             // читаем построчно файл с текстом статьи
  •   $meta = array( "post_content" => "" );  // задаем необходимые начальные значения
  •   $postid = '';
  •   $text = '';
  •  
  •   foreach( $lines as $line ) {
  •    
  •     if( strpos( $line, '[text_begin]' ) === 0 ) { // найдено начало блока с текстом статьи
  •       $process_text = 1;                  // установить признак обработки блока с текстом
  • /* здесь может находится код оформления начала блока текста */
  •       continue;                           // пропустить строку с тегом
  •     }
  •     if( strpos( $line, '[text_end]' ) === 0 ) { // найден конец блока с текстом статьи
  •       $process_text = 0; // завершить обработку
  • /* здесь может находится код оформления конца блока текста */     
  •     }
  •    
  •     if(( strpos( $line, '#' ) === 0 ) && ( ! $process_text )) {   
  •                                           // строки параметров начинается c "#" и
  •                                           // располагаются вне блока текста статьи
  •       $kv = trim( substr( $line, 1 ));    // выделяем имя параметра и его значение
  •       $key = strstr( $kv, ':', true );
  •       $value = trim( substr( strstr( $kv, ':' ), 1 ));
  •              
  •       if( $key == 'category' ) {          // категория - список названий категорий
  •         $meta['terms_names'] = array( $key => explode( ",", $value ));
  •       }                                   // к которой относится пост
  •       else if ( $key == 'post_id' ) {     // идентификатор поста
  •         $postid = $value;
  •       }
  •       else if ( $key == 'meta_keywords' ) {   // ключевые слова
  •         // при использовании "All in One SEO Pack используем название его ключей"
  •         $meta['custom_fields'][] = array( 'key' => '_aioseop_keywords', 'value' => $value );
  •       }
  •       else if ( $key == 'meta_description' ) { // описание поста
  •         $meta['custom_fields'][] = array( 'key' => '_aioseop_description', 'value' => $value );
  •       }
  •       else {                              // во всех прочих случаях, просто присваиваем
  •         $meta[$key] = $value;             // значение параметра элементу массива с ключем
  •       }                                   // названия параметра
  •     }
  •  
  •     if( $process_text ) {                 // внутри блока [text_begin] - [text_end]
  •       $text .= $line;                     // добавлять содержимое строк
  •     }
  •   }                                       // после завершения обработки всех строк файла
  •   $meta['post_content'] = $text;          // сохранить в массиве
  •  
  •   return array( $meta, $postid );         // возвратить структуру
  • }

Перейдем к рассмотрению функций, для преобразования созданного массива $meta в XML форму и интерпретации ответа сервера.

  • function create( $meta ) {
  •     global $url, $username, $password;     // глобальные переменные уже инициализированы
  •                                            // в начале скрипта
  •     $params = array( 0, $username, $password, $meta ); // окончательный вид структуры для преобразования
  •                                            // в XML форму
  •     $request = xmlrpc_encode_request( 'wp.newPost', $params, array('encoding'=>'utf-8', 'escaping'=>'cdata'));
  •     $xmlresponse = get_response( $request );  
  •     $response = xmlrpc_decode( $xmlresponse ); // преобразование полученного ответа от сервера
  •                                                // в форму именованного массива
  •     return "\n# post_id:$response";            // возвратить строку для добавления в файл статьи
  • }
  •  
  • function update( $postid, $meta ) {
  •     global $url, $username, $password;
  •   
  •     $params = array( 0, $username, $password, $postid, $meta );
  •      
  •     $request = xmlrpc_encode_request( 'wp.editPost', $params, array('encoding'=>'utf-8', 'escaping'=>'cdata'));
  •     $xmlresponse = get_response($request);
  •     $response = xmlrpc_decode($xmlresponse);
  •  
  •     print_r( $response );                 // для вывода возвращаемых сервером ошибок
  • }

Функция xmlrpc_encode_request(), входит в библиотеку XML-RPC, включенную дистрибутив PHP, преобразует массив $params в XML форму. Обратите внимание на дополнительные параметры задания кодировки структуры и отключение преобразования символов, без этого символы кириллицы в посте будут неправильно отображаться. Кодировка WordPress для моего примера настроена на использование UTF-8. Функция xmlrpc_decode() производит обратное преобразование XML структуры в массив PHP. Массив $params имеет структуру, соответствующую указанной в официальном описании для методов wp.newPost и wp.editPost соответственно. В случае моего примера, для метода wp.editPost она имеет такой вид:

  • Array
  • (
  •     [0] => 0          // идентификатор блога, задается равным "0"
  •     [1] => username
  •     [2] => password
  •     [3] => хххх       // postid
  •     [4] => Array
  •         (
  •             [post_content] => здесь находится текст или HTML код поста
  •             [post_title] => заголовок поста
  •             [post_type] => post // тип поста: страница, заметка, линк и т.д.
  •             [terms_names] => Array
  •                 (
  •                     [category] => Array
  •                         (
  •                             [0] => Категория1
  •                             [1] => Категория2
  •                         )
  •                 )
  •  
  •             [post_status] => draft      // статус - черновик
  •             [comment_status] => closed  // запретить комментарии к посту
  •             [post_thumbnail] => хххх    // идентификатор миниатюры поста
  •             [custom_fields] => Array    // метаданные
  •                 (
  •                     [0] => Array
  •                         (
  •                             [key] => _aioseop_keywords // ключевые слова
  •                             [value] => ключевое_слово_1,ключевое_слово_2
  •                         )
  •  
  •                     [1] => Array
  •                         (
  •                             [key] => _aioseop_description // мето описание
  •                             [value] => Описание поста
  •                         )
  •  
  •                 )
  •  
  •         )
  • )

Для отправки полученной XML структуры на сервер используем функции библиотеки cURL PHP:

  • function get_response( $context ) {
  •     global $url;
  •      
  •     $curlHandle = curl_init();
  •     curl_setopt( $curlHandle, CURLOPT_URL, $url."/xmlrpc.php" );
  •     curl_setopt( $curlHandle, CURLOPT_HEADER, false );
  •     curl_setopt( $curlHandle, CURLOPT_RETURNTRANSFER, true );
  •     curl_setopt( $curlHandle, CURLOPT_HTTPHEADER, array( "Content-Type: text/xml; charset=utf-8" ));
  •     curl_setopt( $curlHandle, CURLOPT_POSTFIELDS, $context );
  •     $response = curl_exec( $curlHandle );
  •      
  •     return $response;
  • }

Теперь приведу пример формы текстового файла поста для обработки скриптом:

  • -[text_begin]
  • ... здесь расположен текст Вашего поста c HTML форматированием или без него ...
  • -[text_end]
  •  
  • # post_title:         Заголовок поста
  • # post_type:          post
  •  post, page, link, nav_menu_item
  • # category:           Категория1,Категория2,...
  • # post_status:        draft
  •  draft, publish, pending, future, private
  • # comment_status:     closed
  •  closed, open
  • # post_thumbnail:     хххх
  • # meta_keywords:      ключевое_слово_1,ключевое_слово_2
  • # meta_description:   Описание поста

В моем примере, я придерживался концепции автора этой идеи, хотя ничто не мешает задавать и анализировать параметры по другому. Обратите внимание, что в примере перед тегами [text_begin] и [text_end] я намеренно добавил символы "-", иначе они ошибочно воспримутся за символы разметки при публикации этой статьи. После успешной публикации в конец файла запишется идентификатор статьи в виде строки: # post_id:хххх, который будет впоследствии использоваться скриптом при обновлении поста. С этим номером надо быть внимательным, если он ошибочно будет заменен на номер другой существующей статьи, то она полностью будет замещена текстом текущей, и (если не окажется копии) будет утеряна, поэтому делать копии файла с заданным параметром post_id надо особенно осторожно.

Для корректного обновления миниатюры необходимо внести изменения в файл ./wp-includes/class-wp-xmlrpc-server.php WordPress, в котором находятся функции, отвечающие за обработку сообщений, полученных сервером по протоколу XML-RPC. В частности, код:

  • if ( isset( $post_data['post_thumbnail'] ) ) {
  • // empty value deletes, non-empty value adds/updates
  •  if ( ! $post_data['post_thumbnail'] )
  •    delete_post_thumbnail( $post_ID );
  •      elseif ( ! set_post_thumbnail( $post_ID, $post_data['post_thumbnail'] ) )
  •      return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
  •      unset( $content_struct['post_thumbnail'] );
  •  }

изменить на:

  •   if ( isset( $post_data['post_thumbnail'] ) ) { // если задано значение "post_thumbnail"
  •       // empty value deletes, non-empty value adds/updates
  •   if ( ! $post_data['post_thumbnail'] )  // полученное значение "post_thumbnail" пустое, то
  •           delete_post_thumbnail( $post_ID ); // удалить миниатюру из статьи
  • // если значение id заданной миниатюры в статье не совпадает с полученным при обновлении
  • elseif ( get_post_meta( $post_ID, '_thumbnail_id', true) != $post_data['post_thumbnail'] )
  • // то - произвести замену миниатюры на новое, иначе - оставить, как есть
  •   if ( ! set_post_thumbnail( $post_ID, $post_data['post_thumbnail'] ) )
  • // если не удалось - отправить клиенту код ошибки с описанием
  •               return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
  •       unset( $content_struct['post_thumbnail'] );
  •   }

Добавив проверку совпадения существующего идентификатора миниатюры у поста с идентификатором, полученным при обновлении, удасться избежать ошибки, которая возникает в случае обновления статьи уже содержащей миниатюру с тем же самым идентификатором, и статья не обновится. Это объясняется тем, что функция ядра WordPress set_post_thumbnail() выдает ошибку при попытке повторного задания той же самой миниатюры для статьи. Я не стал изучать код этой функции и тратить время на выяснение причины такого поведения, а просто добавил проверку дополнительного условия.

Для возможности добавления метаданных к посту, с использованием защищенных тегов _aioseop_keywords и _aioseop_description необходимо внести еще одно изменение в файл ./wp-includes/class-wp-xmlrpc-server.php, как указано здесь, заменив в функции set_custom_fields() код:

  • // ...
  •     } elseif (current_user_can( 'add_post_meta', $post_id, stripslashes( $meta['key'] ))) {
  •         add_post_meta( $post_id, $meta['key'], $meta['value'] );
  •     }
  • // ...

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

  • // ...
  • // сделать исключение для _aioseop_keywords и _aioseop_description тегов и "обманув" функцию
  • // current_user_can( 'xxxx_post_meta', ... ) убрав из имени знаки подчеркивания
  • } elseif( $meta['key'] == '_aioseop_keywords' || $meta['key'] == '_aioseop_description' ) {
  •   if( $meta['value'] ) {                                 // не пустое переданное значение?
  •     $curr_val = get_post_meta( $post_id, $meta['key'] ); // запросить существующее значение тега
  •     if ( $curr_val ) {                                   // тег сушествует?
  •       if( $curr_val != $meta['value'] ) {                // значение тега, не равно переданному?
  •         if( current_user_can( 'edit_post_meta', $post_id, str_replace('_', '', stripslashes( $meta['key'] ))))
  •           update_post_meta( $post_id, $meta['key'], $meta['value'] ); // тогда, заменить на переданное
  •       }
  •     } else { // тег не существует - создать тег и задать ему переданное значение
  •       if( current_user_can( 'add_post_meta', $post_id, str_replace('_', '', stripslashes( $meta['key'] ))))
  •         add_post_meta( $post_id, $meta['key'], $meta['value'] );
  •     }
  •   } else { // передано пустое значение - удалить тег
  •     if( current_user_can( 'delete_post_meta', $post_id, str_replace('_', '', stripslashes( $meta['key'] ))))
  •       delete_post_meta( $post_id, $meta['key'] );
  •   }
  • }
  • // ...

Из главных минусов удаленной публикации с использованием описанной выше схемы - проблема синхронизация текста в локальном редакторе со своей копией на сервере WordPress, в случае изменения копии публикации на серверной стороне через веб редактор. Эту проблему можно решить, воспользовавшись функцией wp.getPost, но при этом возникнет другая проблема - автоматизация этого вызова для своевременного обновления локальной копии. Кроме того, функция выдаст текст статьи в том виде, в каком она хранится в базе данных на сервере, то есть текст, в моем случае, будет содержать элементы форматирования, которые придется отделять от самого текста. Другими словами, придется делать обратное преобразование, выполняемому локальным скриптом. Как вариант решения проблемы - не вносить изменения в публикации альтернативными способами, помимо скрипта.

Один из возможных вариантов решения указанной проблемы - создать дополнительное поле в таблице, где хранятся содержание постов (posts) и каждый раз при внесении изменений перезаписывать его текстом рассматриваемого шаблона. В таком случае "обратный парсинг" будет не нужен.

Не знаю, как для других, но для меня подобная форма публикации сообщений достаточно удобна. В редакторе - текст, в браузере страница в режиме предварительного просмотра. После окончательной выверки текста и форматирования - необходимо лишь поменять значение параметра "post_status" с "draft" на "publish", и сатья будет опубликована.

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

Еще нет комментариев к «Использование протокола XML-RPC WordPress для публикации статей»

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Captcha Обновить картинку Каптчи

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