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, "[gallery ") + strlen("[gallery ");
$end = strpos(substr($content, $start), "]");
$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();
問題は、私が何をしても、このエラーが発生した2つの画像の後でスクリプトが失敗することです。
致命的なエラー:1841行目の/home/forge/morselandcompany.com/public/wordpress/wp-includes/wp-db.phpで許容メモリサイズ1073741824バイトを使い果たしました(28672バイトを割り当てようとしました)
PHPと同様に、ワードプレスでもメモリ制限を1024Mに設定しています。このスクリプトが128M以上必要な理由はわかりません。このスクリプトを正しく機能させるためにどのように最適化できますか? (平均画像サイズは800kBです。)
BlackFire.ioを使った初期デバッグでは、以下のメモリ消費量が示唆されています。 - wpdb-> query:3.2MB、31回呼び出されました - mysqli_fetch_object:1.89MB、595回呼び出されました - 全体として、blackfireはこのスクリプトを実行するために8MB以上が必要であることを示唆しています。
私はすべてのプラグインを無効にしてテストしたところ、同じ結果になりました。
実行しています - PHP 7.1
- Ubuntu 16.04
- DigitalOcean VPS(1 CPU、1 GB RAM)
- ワードプレス4.8
- NGINX 1.11.5
助けてくれてありがとう!
更新: / get_postとwp_update_postに関連するメモリリークの解決策を他の人が利用できるように完全性のために、上記の問題を解決したコードを完成させました。ご覧のとおり、解決策は、メモリリークの原因となる2つのWPメソッドに頼るのではなく、$ wpdbを使用して自分のクエリを作成することでした。
<?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, "[gallery ") + strlen("[gallery ");
$end = strpos(substr($content, $start), "]");
$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
を使用します。Unable to delete file <script>...</script> after import.
。あなたが取ることができる最大のセキュリティステップはそれが最も差があるが最も使用されないものにするここにトリックをするべきである簡単なWP CLIコマンドがあります。私はテストしていませんが、すべての重要な部分があり、PHPに関して完全な初心者ではないと確信しています。
たとえば、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を使用するか、またはGET変数をチェックするadmin_init
をフックしてください。 run
コマンド内のコードを変更して使用します。
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;
}
あなたの外部サービスはこれを繰り返し呼び出すことができないかもしれません。私が言うところ:
それでもやることがある場合は、ノンブロッキングリクエストを使用して、タスクを自分自身で呼び出します。このようにして、終了するまで新しいインスタンスを生成し続けます。
if ( $count === 5 ) {
wp_remote_get( home_url('?mbimport=true&key=abc'), [ 'blocking' => false ]);
exit;
)
ダッシュボードのUIの進行状況メーターが必要な場合は、インポートするフォルダーに残っているjpegファイルの数を数えるだけです。設定する必要がある場合は、UIを構築してoptionsに設定を保存してから、CLIスクリプトのオプションから取得します。
プロセス全体を回避し、REST APIを介してファイルを追加します。 jpegsをアップロードするためにexample.com/wp-json/wp/v2/media
へのPOSTリクエストを行うことができます。あなたのサイトにコードは必要ありません
https://stackoverflow.com/questions/37432114/wp-rest-api-upload-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');
}
必要なものはこれだけです。画像は投稿に添付する必要がありますが、後で削除することができます。