Статьи

Программное создание сообщений WordPress из данных CSV

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

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

Чтобы получить все данные, которые нам нужны, из CSV-файлов, мы будем использовать несколько изящных функций PHP, таких как: glob() , который «обрабатывает» каталог и возвращает в нем массив имен файлов; fopen() , который открывает файл, чтобы мы могли прочитать его содержимое, и, наконец, fgetcsv() , который анализирует CSV-файл в красивый ассоциативный массив, содержащий все наши данные.

В действительности, большинство данных, которые мы будем использовать для этой статьи, вероятно, будут находиться внутри одного CSV, в отличие от того, как мы собираемся делать это сегодня, когда данные разбросаны по нескольким файлам. Это сделано для того, чтобы используемые здесь методы могли быть реализованы с использованием других типов данных, таких как JSON, Yaml или даже обычные текстовые файлы. Идея всей этой статьи возникла из-за острой нехватки учебников и статей по этой теме, особенно когда вы используете пользовательские типы записей и настраиваемые поля.

Знакомство с нашими данными

Если вы хотите следовать, вы можете получить необходимые CSV-файлы (и весь код, используемый в этой статье, тоже) из этого репозитория . Хорошо, обо всем по порядку, давайте посмотрим на данные CSV, с которыми мы будем иметь дело (обратите внимание, что столбец «Файл» находится там, чтобы показать вам, что я распределяю все эти данные по нескольким файлам CSV).

файл заглавие содержание прикрепление
dummy.csv какой-то заголовок некоторый контент для поста attachment1.txt
dummy2.csv какой-то заголовок 2 некоторый контент для поста 2 attachment2.txt
dummy3.csv какой-то заголовок для поста 3 некоторый контент для третьего поста attachment3.txt
dummy3.csv какой-то заголовок 4 некоторый контент для поста 4 attachment4.txt

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

Настройки пользовательского интерфейса типа записей

Наконец, давайте посмотрим на настраиваемое поле, которое мы будем использовать. Он создан с прекрасными полями Advanced Custom . Вот еще один быстрый скриншот настроек, которые мы будем использовать.

Обратите внимание, что идентификатор вашего настраиваемого поля, скорее всего, будет отличаться от того, который использовался в этой статье, поэтому обязательно обновите массив $sitepoint правильный идентификатор. Это может быть либо фактический хеш-ключ для поля, либо просто имя поля. Я просто собираюсь придерживаться имени, для ясности.

Расширенные настройки пользовательских полей

Пара предпосылок

Стоит отметить, что код, используемый в этой статье, требует как минимум PHP 5.3 . Мы будем использовать анонимные функции , а также fgetcsv() , обе из которых требуют 5.3, поэтому перед тем, как вы уйдете и будете использовать это на старом шатком производственном сервере (пожалуйста, не делайте этого) , вы можете захотеть обновить .

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

Nitty-песчаный

Чтобы начать это, давайте создадим простую кнопку, которая выполняет наш скрипт в серверной части нашего сайта. Это гарантирует, что наш код выполняется только нами, администратором. Для этого мы просто воспользуемся хуком admin_notices WordPress. По сути, все, что он собирается сделать, — это создать переменную $_POST которую мы будем использовать, чтобы определить, следует ли нам вставлять записи в базу данных.

 /** * Show 'insert posts' button on backend */ add_action( "admin_notices", function() { echo "<div class='updated'>"; echo "<p>"; echo "To insert the posts into the database, click the button to the right."; echo "<a class='button button-primary' style='margin:0.25em 1em' href='{$_SERVER["REQUEST_URI"]}&insert_sitepoint_posts'>Insert Posts</a>"; echo "</p>"; echo "</div>"; }); 

Ранее я упоминал, что мы будем использовать анонимные функции (для простоты я буду называть их замыканиями ) в этой статье, и причина этого в том, что не стоит загрязнять глобальное пространство имен кучей функций, которые по существу одноразовые функции. Замыкания — это здорово, и если вы с ними не знакомы, я бы настоятельно рекомендовал прочитать их. Если вы пришли из JavaScript или Ruby, вы будете чувствовать себя как дома.

Если вы хотите поместить весь этот код в свой файл functions.php , это нормально, хотя также хорошо, если вы хотите создать отдельный шаблон страницы, скрытую страницу или что-то еще. В конце концов, это действительно не имеет значения. Для начала давайте воспользуемся другим хуком WordPress, admin_init . Мы также $wpdb глобальный $wpdb , чтобы позже мы могли выполнить пользовательский запрос к базе данных.

 /** * Create and insert posts from CSV files */ add_action( "admin_init", function() { global $wpdb; // ... code will go here }); 

Хорошо, что дальше? Давайте начнем с проверки, присутствует ли наша переменная $_POST , и если ее нет, мы можем выйти из функции. Бесполезно тратить память ни на что. Чтобы проверить, присутствует ли наша переменная, мы будем использовать переменную $_GET . Если вы не знакомы с этими типами переменных, вы можете прочитать о них здесь . В дополнение к вышеупомянутой проверке мы также определим наш массив $sitepoint о котором я упоминал ранее. Он будет содержать ваш собственный тип записи и идентификаторы пользовательских полей.

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

 // ... global $wpdb; // I'd recommend replacing this with your own code to make sure // the post creation _only_ happens when you want it to. if ( ! isset( $_GET["insert_sitepoint_posts"] ) ) { return; } // Change these to whatever you set $sitepoint = array( "custom-field" => "sitepoint_post_attachment", "custom-post-type" => "sitepoint_posts" ); // ... 

Далее, давайте создадим замыкание, которое будет извлекать наши CSV-данные и создавать хороший ассоциативный массив всех данных. Теперь было бы хорошо отметить, что в зависимости от того, какой тип данных вы используете (будь то CSV, JSON, Yaml и т. Д.), Это закрытие будет различным. Итак, я бы посоветовал вам отрегулировать это в соответствии со своими данными. Я прокомментировал код ниже, чтобы вы могли лучше следить за тем, что на самом деле происходит.

Несколько дополнительных заметок:
* Синтаксис $array[] = "value" — это сокращение от array_push , которое array_push назначенное значение в конец массива.
* Я храню свои данные CSV в моей теме, внутри директории data/ . Вы можете хранить его где угодно, но не забывайте настраивать путь glob() по своему усмотрению.

 // ... // Get the data from all those CSVs! $posts = function() { $data = array(); $errors = array(); // Get array of CSV files $files = glob( __DIR__ . "/data/*.csv" ); foreach ( $files as $file ) { // Attempt to change permissions if not readable if ( ! is_readable( $file ) ) { chmod( $file, 0744 ); } // Check if file is writable, then open it in 'read only' mode if ( is_readable( $file ) && $_file = fopen( $file, "r" ) ) { // To sum this part up, all it really does is go row by // row, column by column, saving all the data $post = array(); // Get first row in CSV, which is of course the headers $header = fgetcsv( $_file ); while ( $row = fgetcsv( $_file ) ) { foreach ( $header as $i => $key ) { $post[$key] = $row[$i]; } $data[] = $post; } fclose( $_file ); } else { $errors[] = "File '$file' could not be opened. Check the file's permissions to make sure it's readable by your server."; } } if ( ! empty( $errors ) ) { // ... do stuff with the errors } return $data; }; // ... 

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

 $data = array( 0 => array( "title" => "some title", "content" => "some content for the post", "attachment" => "attachment1.txt" ), 1 => array( "title" => "some title 2", "content" => "some content for post 2", "attachment" => "attachment2.txt" ), // ... ); 

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

 // ... // Simple check to see if the current post exists within the // database. This isn't very efficient, but it works. $post_exists = function( $title ) use ( $wpdb, $sitepoint ) { // Get an array of all posts within our custom post type $posts = $wpdb->get_col( "SELECT post_title FROM {$wpdb->posts} WHERE post_type = '{$sitepoint["custom-post-type"]}'" ); // Check if the passed title exists in array return in_array( $title, $posts ); }; // ... 

Вы, наверное, задаетесь вопросом, когда мы на самом деле собираемся вставить все эти данные в виде реальных сообщений, а? Что ж, как вы можете сказать, нужно проделать большую работу, чтобы убедиться, что все эти данные организованы аккуратно, и что у нас есть функции, настроенные для выполнения необходимых проверок. Для этого мы выполним наше закрытие $post() , чтобы мы могли перебрать возвращаемые данные. Далее мы выполним наше $post_exists() чтобы увидеть, существует ли текущий заголовок сообщения.

Итак, в приведенном ниже коде существует множество массивов и данных, которые передаются. Я продолжил и прокомментировал код, чтобы вы могли лучше понять все. По сути, мы вставляем сообщение в базу данных с помощью wp_insert_post и сохраняем возвращенный идентификатор сообщения для дальнейшего использования. Затем мы берем каталог загрузки и создаем необходимые метаданные вложения, создавая путь к загруженному файлу (который находится в uploads/sitepoint-attachments ); и, наконец, захватывает имя и расширение файла, которые мы будем использовать, чтобы вставить вложение в наш недавно созданный пост.

 // .. foreach ( $posts() as $post ) { // If the post exists, skip this post and go to the next one if ( $post_exists( $post["title"] ) ) { continue; } // Insert the post into the database $post["id"] = wp_insert_post( array( "post_title" => $post["title"], "post_content" => $post["content"], "post_type" => $sitepoint["custom-post-type"], "post_status" => "publish" )); // Get uploads dir $uploads_dir = wp_upload_dir(); // Set attachment meta $attachment = array(); $attachment["path"] = "{$uploads_dir["baseurl"]}/sitepoint-attachments/{$post["attachment"]}"; $attachment["file"] = wp_check_filetype( $attachment["path"] ); $attachment["name"] = basename( $attachment["path"], ".{$attachment["file"]["ext"]}" ); // Replace post attachment data $post["attachment"] = $attachment; // Insert attachment into media library $post["attachment"]["id"] = wp_insert_attachment( array( "guid" => $post["attachment"]["path"], "post_mime_type" => $post["attachment"]["file"]["type"], "post_title" => $post["attachment"]["name"], "post_content" => "", "post_status" => "inherit" )); // Update post's custom field with attachment update_field( $sitepoint["custom-field"], $post["attachment"]["id"], $post["id"] ); } // .. 

Ну и что дальше? Проще говоря, как я могу: мы нажимаем кнопку. Вся наша тяжелая работа собирается окупиться (надеюсь!). Когда мы нажимаем кнопку, наш код должен проверять переменную post, затем он запускается через наш скрипт и вставляет наши сообщения. Легко и приятно. Вот скриншот для всех нас, визуальных людей:

Выполнение нашего скрипта и вставка постов

Вот и все! Как я и обещал ранее, вот весь код, использованный в этой статье:

 /** * Show 'insert posts' button on backend */ add_action( "admin_notices", function() { echo "<div class='updated'>"; echo "<p>"; echo "To insert the posts into the database, click the button to the right."; echo "<a class='button button-primary' style='margin:0.25em 1em' href='{$_SERVER["REQUEST_URI"]}&insert_sitepoint_posts'>Insert Posts</a>"; echo "</p>"; echo "</div>"; }); /** * Create and insert posts from CSV files */ add_action( "admin_init", function() { global $wpdb; // I'd recommend replacing this with your own code to make sure // the post creation _only_ happens when you want it to. if ( ! isset( $_GET["insert_sitepoint_posts"] ) ) { return; } // Change these to whatever you set $sitepoint = array( "custom-field" => "sitepoint_post_attachment", "custom-post-type" => "sitepoint_posts" ); // Get the data from all those CSVs! $posts = function() { $data = array(); $errors = array(); // Get array of CSV files $files = glob( __DIR__ . "/data/*.csv" ); foreach ( $files as $file ) { // Attempt to change permissions if not readable if ( ! is_readable( $file ) ) { chmod( $file, 0744 ); } // Check if file is writable, then open it in 'read only' mode if ( is_readable( $file ) && $_file = fopen( $file, "r" ) ) { // To sum this part up, all it really does is go row by // row, column by column, saving all the data $post = array(); // Get first row in CSV, which is of course the headers $header = fgetcsv( $_file ); while ( $row = fgetcsv( $_file ) ) { foreach ( $header as $i => $key ) { $post[$key] = $row[$i]; } $data[] = $post; } fclose( $_file ); } else { $errors[] = "File '$file' could not be opened. Check the file's permissions to make sure it's readable by your server."; } } if ( ! empty( $errors ) ) { // ... do stuff with the errors } return $data; }; // Simple check to see if the current post exists within the // database. This isn't very efficient, but it works. $post_exists = function( $title ) use ( $wpdb, $sitepoint ) { // Get an array of all posts within our custom post type $posts = $wpdb->get_col( "SELECT post_title FROM {$wpdb->posts} WHERE post_type = '{$sitepoint["custom-post-type"]}'" ); // Check if the passed title exists in array return in_array( $title, $posts ); }; foreach ( $posts() as $post ) { // If the post exists, skip this post and go to the next one if ( $post_exists( $post["title"] ) ) { continue; } // Insert the post into the database $post["id"] = wp_insert_post( array( "post_title" => $post["title"], "post_content" => $post["content"], "post_type" => $sitepoint["custom-post-type"], "post_status" => "publish" )); // Get uploads dir $uploads_dir = wp_upload_dir(); // Set attachment meta $attachment = array(); $attachment["path"] = "{$uploads_dir["baseurl"]}/sitepoint-attachments/{$post["attachment"]}"; $attachment["file"] = wp_check_filetype( $attachment["path"] ); $attachment["name"] = basename( $attachment["path"], ".{$attachment["file"]["ext"]}" ); // Replace post attachment data $post["attachment"] = $attachment; // Insert attachment into media library $post["attachment"]["id"] = wp_insert_attachment( array( "guid" => $post["attachment"]["path"], "post_mime_type" => $post["attachment"]["file"]["type"], "post_title" => $post["attachment"]["name"], "post_content" => "", "post_status" => "inherit" )); // Update post's custom field with attachment update_field( $sitepoint["custom-field"], $post["attachment"]["id"], $post["id"] ); } }); 

Вывод

Программная вставка сообщений WordPress из данных CSV не так сложна, как мы думаем изначально. Надеемся, что это может послужить ресурсом для многих людей, когда им нужно перенести данные, которые используют как пользовательские типы записей, так и пользовательские поля. Как я уже говорил в начале статьи, большая часть кода, такая как наша кнопка бэкэнда, использующая переменные $_POST , не должна использоваться на рабочем сайте. Код, используемый в этой статье, следует использовать в качестве отправной точки, а не решения «включай и работай».

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