Intereting Posts
Не удается найти wp-cron.php, но я вижу его в общей папке Как предотвратить добавление кэшированных сообщений в кеш? Как удалить пули из виджета Избранное изображение сообщения исчезло после перехода сайта на другой сервер Добавить классы Bootstrap для последних сообщений Widget Как отсортировать столбец администратора настраиваемого столбца, используя два мета-ключа? Отключите ms-files.php после настройки сети Как сначала показать должности какой-то категории, а затем остальные? Не могу определить кодировку Добавление условного содержимого в один шаблон post-type Использовать базу данных пользователей phpbb для WordPress Две полосы прокрутки во всплывающем окне Комментарии WordPress – разделитель "/" не найден Мои условные теги не работают Как префикс блога post urls как mysite.com/blog/%postname%/, но позволяют авторам по-прежнему находиться на mysite.com/authors/%nicename%? Как предотвратить привязку страницы в меню / перекладине

Загрузка изображений в медиабиблиотеку не выполняется с исчерпанной памятью

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

<?php require_once('../../../../public/wordpress/wp-load.php'); require_once('../../../../public/wordpress/wp-admin/includes/image.php'); function importImage($imagePath, $postId) { $succeededFileCount = 0; $failedFileCount = 0; $files = scandir($imagePath); foreach ($files as $file) { if (in_array($file, ['.', '..'])) { continue; } $newPath = $imagePath . "/" . $file; $filePath = realpath($newPath); if (is_dir($newPath) && $item != '.' && $item != '..' && $item != 'failed_files') { importImage($newPath, $postId); } elseif ($item != '.' && $item != '..' && $item != 'failed_files') { $filename = basename($file); $uploadFile = wp_upload_bits($filename, null, file_get_contents($filePath)); $wp_upload_dir = wp_upload_dir(); if (! $uploadFile['error']) { $fileType = wp_check_filetype($filename, null); $attachment = [ 'guid' => $wp_upload_dir['url'] . '/' . basename( $filename ), 'post_mime_type' => $fileType['type'], 'post_parent' => $postId, 'post_title' => preg_replace('/\.[^.]+$/', '', $filename), 'post_content' => '', 'post_status' => 'inherit' ]; $attachmentId = wp_insert_attachment($attachment, $uploadFile['file'], $postId); if (! is_wp_error($attachmentId)) { $attachmentData = wp_generate_attachment_metadata($attachmentId, $uploadFile['file']); wp_update_attachment_metadata($attachmentId, $attachmentData); } } else { echo '<span style="color: red; font-weight: bold;">Error: ' . $uploadFile['error'] . '</span>'; } if ($attachmentId > 0) { $succeededFileCount++; echo '<span style="color: green; font-weight: normal;">File import succeeded: ' . $filePath . "</span><br />"; if (! unlink($filePath)) { echo '<span style="color: red; font-weight: bold;">Unable to delete file ' . $filePath . " after import.</span><br />"; } $page = get_post($postId); if ($page->post_content) { $content = $page->post_content; $start = strpos($content, ""); $shortcode = substr($content, $start, $end); $attrs = shortcode_parse_atts($shortcode); $attrs["ids"] .= "," . $attachmentId; $tempIds = explode(",", $attrs["ids"]); $tempIds = array_filter($tempIds); rsort($tempIds); $attrs["ids"] = implode(",", $tempIds); $shortcode = ""; foreach ($attrs as $key => $value) { if (strlen($shortcode) > 0) { $shortcode .= " "; } $shortcode .= $key . "=\"" . $value . "\""; } $newContent = substr($content, 0, $start); $newContent .= $shortcode; $newContent .= substr($content, $start + $end, strlen($content)); $page->post_content = $newContent; wp_update_post($page); } } } } echo $succeededFileCount . " files uploaded and imported successfully. <br />"; echo $failedFileCount . " files failed to uploaded or import successfully."; } get_header(); if (get_option('rmm_image_importer_key') != urldecode($_GET['key'])) { echo '<div id="message" class="error">'; echo "<p><strong>Incorrect authentication key: you are not allowed to import images into this site.</strong></p></div>"; } else { echo '<br /><br />'; $mtime = microtime(); $mtime = explode(" ", $mtime); $mtime = $mtime[1] + $mtime[0]; $starttime = $mtime; $dataset = get_option('rmm_image_importer_settings'); if (is_array($dataset)) { foreach ($dataset as $data) { if (isset($data['folder']) || isset($data['page'])) { ?> <h2>Import from folder: <?php echo $data['folder']; ?></h2> <p> <?php importImage(realpath(str_replace('//', '/', ABSPATH . '../../' . $data['folder'])), $data['page']); ?> </p> <?php } } } $mtime = microtime(); $mtime = explode(" ", $mtime); $mtime = $mtime[1] + $mtime[0]; $endtime = $mtime; $totaltime = ($endtime - $starttime); echo 'Files imported to media library over ' . $totaltime . ' seconds.<br /><br />'; } get_footer(); 

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

Неустранимая ошибка: допустимый объем памяти 1073741824 байт исчерпан (пытался выделить 28672 байта) в /home/forge/morselandcompany.com/public/wordpress/wp-includes/wp-db.php в строке 1841

У меня есть ограничение памяти, равное 1024 М в wordpress, а также PHP. Я не понимаю, почему этот скрипт должен нуждаться в более чем 128M, действительно. Как можно оптимизировать этот сценарий для правильной работы? (Средний размер изображения – 800 КБ).

Некоторая начальная отладка с BlackFire.io предлагает следующие типы памяти: – wpdb-> query: 3.2MB, называемый 31 раз – mysqli_fetch_object: 1.89MB, называемый 595 раз – run_init () в wp-settings.php: 5.4MB, вызываемый один раз В общем, blackfire предполагает, что для запуска этого скрипта требуется более 8 МБ!

Я также тестировал все отключенные плагины, и это закончилось тем же результатом.

Я запускаю – PHP 7.1
– Ubuntu 16.04
– DigitalOcean VPS (1 процессор, 1 ГБ оперативной памяти)
– WordPress 4.8
– NGINX 1.11.5

Спасибо за любую помощь!

Обновление: в интересах полноты, чтобы другие могли использовать решение для утечек памяти, связанных с get_post и wp_update_post, я опубликовал окончательный код, который решил проблему выше. Как вы можете видеть, решение заключалось в том, чтобы написать собственные запросы с помощью $ wpdb вместо того, чтобы полагаться на два метода WP, вызывающих утечки памяти:

 <?php require_once('../../../../public/wordpress/wp-load.php'); require_once(ABSPATH . 'wp-admin/includes/media.php'); require_once(ABSPATH . 'wp-admin/includes/file.php'); require_once(ABSPATH . 'wp-admin/includes/image.php'); function importImage($imagePath, $postId) { $succeededFileCount = 0; $failedFileCount = 0; $files = scandir($imagePath); foreach ($files as $file) { if (in_array($file, ['.', '..'])) { continue; } $newPath = $imagePath . "/" . $file; $filePath = realpath($newPath); if (is_dir($newPath) && $file != 'failed_files') { importImage($newPath, $postId); } elseif ($file != 'failed_files') { $webPath = str_replace($_SERVER['DOCUMENT_ROOT'], '', $imagePath); $imageUrl = str_replace('/wordpress', '', get_site_url(null, "{$webPath}/" . urlencode($file))); $imageUrl = str_replace(':8000', '', $imageUrl); $attachmentId = media_sideload_image($imageUrl, 0, '', 'id'); if ($attachmentId > 0) { $succeededFileCount++; echo '<span style="color: green; font-weight: normal;">File import succeeded: ' . $filePath . "</span><br />"; if (! unlink($filePath)) { echo '<span style="color: red; font-weight: bold;">Unable to delete file ' . $filePath . " after import.</span><br />"; } global $wpdb; $page = $wpdb->get_results("SELECT * FROM wp_posts WHERE ID = {$postId}")[0]; if (is_array($page)) { $page = $page[0]; } if ($page->post_content) { $content = $page->post_content; $start = strpos($content, ""); $shortcode = substr($content, $start, $end); $attrs = shortcode_parse_atts($shortcode); $attrs["ids"] .= "," . $attachmentId; $tempIds = explode(",", $attrs["ids"]); $tempIds = array_filter($tempIds); rsort($tempIds); $attrs["ids"] = implode(",", $tempIds); $shortcode = ""; foreach ($attrs as $key => $value) { if (strlen($shortcode) > 0) { $shortcode .= " "; } $shortcode .= $key . "=\"" . $value . "\""; } $newContent = substr($content, 0, $start); $newContent .= $shortcode; $newContent .= substr($content, $start + $end, strlen($content)); $wpdb->update( 'post_content', ['post_content' => $newContent], ['ID' => $postId] ); } } } } echo $succeededFileCount . " files uploaded and imported successfully. <br />"; echo $failedFileCount . " files failed to uploaded or import successfully."; } get_header(); if (get_option('rmm_image_importer_key') != urldecode($_GET['key'])) { echo '<div id="message" class="error">'; echo "<p><strong>Incorrect authentication key: you are not allowed to import images into this site.</strong></p></div>"; } else { echo '<br /><br />'; $mtime = microtime(); $mtime = explode(" ", $mtime); $mtime = $mtime[1] + $mtime[0]; $starttime = $mtime; $dataset = get_option('rmm_image_importer_settings'); if (is_array($dataset)) { foreach ($dataset as $data) { if (isset($data['folder']) || isset($data['page'])) { ?> <h2>Import from folder: <?php echo $data['folder']; ?></h2> <p> <?php importImage(realpath(str_replace('//', '/', ABSPATH . '../../' . $data['folder'])), $data['page']); ?> </p> <?php } } } $mtime = microtime(); $mtime = explode(" ", $mtime); $mtime = $mtime[1] + $mtime[0]; $endtime = $mtime; $totaltime = ($endtime - $starttime); echo 'Files imported to media library over ' . $totaltime . ' seconds.<br /><br />'; } get_footer(); 

Несколько вещей

  • Используйте media_handle_sideload чтобы WordPress перемещал файлы в нужное место и проверял их для вас, создавал сообщение вложения и т. Д., Ни один из этих материалов руководства
  • Не запускайте это один раз и ожидайте, что он сделает все. Вы просто столкнетесь с одной и той же проблемой, но в дальнейшем импортируете. Если вы дадите ему бесконечную память, у вас будет проблема с исполнением ограничения времени, когда сценарий просто заканчивается
  • Обработайте 5 файлов за раз и повторно вызывайте его, пока ничего не осталось для обработки
  • Используйте команду WP CLI, не загружайте WordPress и не вызывайте ее из графического интерфейса. Вызовите его непосредственно из Cron и пропустите pinging URL-бизнес. Команды CLI получают неограниченное время для выполнения своей работы, и вы не можете вызывать их из браузера. Переменная GET с ключом становится совершенно ненужной.
  • Escape escape escape, вы эхом отдаете эти массивы и значения, считая, что они содержат, безопасно, но что, если я продержу там тег скрипта? Unable to delete file <script>...</script> after import. , Самый большой шаг в безопасности, который вы можете предпринять, делает наибольшую разницу, но наименее используемую

Команда прототипа WP CLI

Вот простая команда WP CLI, которая должна сделать трюк. Я не тестировал его, но все важные части там, я надеюсь, вы не полный новичок, когда дело доходит до PHP, и можете затянуть любые свободные винты или незначительные ошибки, никаких дополнительных знаний API не требуется.

Вы хотите включить его только в контексте WP CLI, например:

 if ( defined( 'WP_CLI' ) && WP_CLI ) { require_once dirname( __FILE__ ) . '/inc/class-plugin-cli-command.php'; } 

Измените, соответственно, сброс команды в functions.php темы и ожидание ее работы приведет к ошибкам, поскольку классы WP CLI загружаются только в командной строке, никогда не обрабатывая запрос браузера.

Применение:

wp mbimport run

Класс:

 <?php /** * Implements image importer command. */ class MBronner_Import_Images extends WP_CLI_Command { /** * Runs the import script and imports several images * * ## EXAMPLES * * wp mbimport run * * @when after_wp_load */ function run( $args, $assoc_args ) { if ( !function_exists('media_handle_upload') ) { require_once(ABSPATH . "wp-admin" . '/includes/image.php'); require_once(ABSPATH . "wp-admin" . '/includes/file.php'); require_once(ABSPATH . "wp-admin" . '/includes/media.php'); } // Set the directory $dir = ABSPATH .'/wpse'; // Define the file type $images = glob( $dir . "*.jpg" ); if ( empty( $images ) { WP_CLI::success( 'no images to import' ); exit; } // Run a loop and transfer every file to media library // $count = 0; foreach ( $images as $image ) { $file_array = array(); $file_array['name'] = $image; $file_array['tmp_name'] = $image; $id = media_handle_sideload( $file_array, 0 ); if ( is_wp_error( $id ) ) { WP_CLI::error( "failed to sideload ".$image ); exit; } // only do 5 at a time, dont worry we can run this // several times till they're all done $count++; if ( $count === 5 ) { break; } } WP_CLI::success( "import ran" ); } } WP_CLI::add_command( 'mbimport', 'MBronner_Import_Images' ); 

Вызовите повторно из реального задания cron. Если вы не можете, то либо используйте WP Cron, либо имеете крючок на admin_init который проверяет переменную GET. Используйте код внутри команды run с некоторыми изменениями.

Когда WP CLI не является вариантом

Использование автономного файла PHP, который загружает WP, представляет собой угрозу безопасности и отличную цель для злоумышленников, если они хотят исчерпать ресурсы вашего сервера (или вызвать проблемы с дублированием, удалив URL-адрес несколько раз подряд).

Например:

 // example.com/?mbimport=true add_action( 'init', function() { if ( $_GET['action'] !== 'mbimport' ) { return; } if ( $_GET['key'] !== get_option('key thing' ) ) { return; } // the code from the run function in the CLI command, but with the WP_CLI::success bits swapped out // ... exit; } 

Повторный вызов

Возможно, ваше внешнее обслуживание не может повторять это повторно. На что я говорю:

  • Не полагайтесь на внешнюю службу, если ваш собственный сервер вызывает ее независимо, даже если нет работы
  • Также будет работать стандартная задача WP Cron
  • Запускать его каждые 5 минут
  • Сделать вызов задачи сам, если есть еще что-то делать, используя неблокирующий запрос. Таким образом, он будет продолжать создавать новые экземпляры до тех пор, пока они не будут завершены, например

      if ( $count === 5 ) { wp_remote_get( home_url('?mbimport=true&key=abc'), [ 'blocking' => false ]); exit; ) 

GUI?

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

Считаете ли вы использование API REST?

Пройдите весь процесс и добавьте файлы через REST API. Вы можете сделать запрос POST на example.com/wp-json/wp/v2/media чтобы загрузить jpeg. Не требуется код на вашем сайте

https://stackoverflow.com/questions/37432114/wp-rest-api-upload-image

Существует уже встроенная функция, созданная именно для этой цели. Вам не нужно писать стены кодов для загрузки изображений с вашего диска. media_sideload_image этого вы можете использовать media_sideload_image .

Эта функция загрузит ваши файлы, позаботится о имени файла, дате, ID и остальной части материала.

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

 // Set the directory $dir = ABSPATH .'/wpse'; // Define the file type $images = glob($directory . "*.jpg"); // Run a loop and upload every file to media library foreach($images as $image) { // Upload a single image media_sideload_image($image,'SOME POST ID HERE'); } 

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

Ваш сервер также может ограничивать общую загрузку, это не только то, что вы установили в коде. обратитесь к своему провайдеру или если у вас есть доступ к WHM, измените максимальную загрузку php для учетной записи (обычно это 8 ГБ), и вам придется изменить php.ini. Следующий URL-адрес был полезен для меня, когда мне приходилось делать то же самое:

http://www.wpbeginner.com/wp-tutorials/how-to-increase-the-maximum-file-upload-size-in-wordpress/

Однако это не полное решение, если у сервера, на котором вы находитесь, есть предел.