将图像上载到媒体库失败,内存耗尽

时间:2017-07-10 作者:mike.bronner

我正在尝试使用通过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();
问题是,无论我做什么,只要两个图像出现以下错误,脚本就会失败:

致命错误:在/home/forge/morselandcompany中,允许的内存大小为1073741824字节(尝试分配28672字节)。com/public/wordpress/wp包括/wp数据库。php在线1841

我在wordpress和PHP中将内存限制设置为1024M。我真的不明白为什么这个脚本需要超过128M。如何优化此脚本以正常工作?(平均图像大小为800kB。)

使用BlackFire进行一些初始调试。io建议使用以下内存占用:-wpdb->查询:3.2MB,调用31次-mysqli\\u fetch\\u对象:1.89MB,调用595次-在wp设置中运行\\u init()。php:5.4MB,称为onceIn total blackfire,表明运行此脚本需要超过8MB!

我还测试了所有禁用的插件,结果也是一样的。

我正在运行-PHP 7.1-Ubuntu 16.04-DigitalOcean VPS(1个CPU,1GB RAM)
-Wordpress 4.8-NGINX 1.11.5

谢谢你的帮助!

Update: 为了确保完整性,以便其他人可以利用与get\\u post和wp\\u update\\u 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, "[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();

3 个回复
最合适的回答,由SO网友:Tom J Nowell 整理而成

几件事

使用media_handle_sideload 因此,WordPress会将文件移动到正确的位置,并为您验证文件,创建附件帖子等,所有这些手动操作都不会运行一次,希望它能完成所有操作。您只会遇到同样的问题,但会进一步进入导入。如果给它无限大的内存,则会出现执行时间限制问题,脚本只会耗尽时间,一次处理5个文件,并重复调用run it,直到没有剩余的文件可处理。请使用WP CLI命令,不要引导WordPress并从GUI调用它。直接从Cron调用它,跳过ping URL业务。CLI命令的工作时间是无限的,您无法从浏览器中调用它们。带有键的GET变量变得完全没有必要了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来触发复制问题),则WP是一个很好的攻击目标。

例如:

// 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

如果您想在仪表板中为UI设置进度表,只需计算文件夹中要导入的jpeg文件的数量。如果需要对其进行配置,请构建UI并将设置保存在options中,然后从CLI脚本中的options中提取。

您是否考虑过使用REST API

绕过整个过程,通过REST API添加文件。您可以向example.com/wp-json/wp/v2/media 上载JPEG。站点上无需任何代码

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

SO网友:Johansson

已经有一个专门为此目的创建的内置函数。从磁盘上载图像不需要编写大量代码。您可以使用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\');
}
这就是你所需要的。图像必须附加到帖子,但之后可以将其拆离。

SO网友:Debbie Kurth

您的服务器可能还限制了总上载容量,而不仅仅是您在代码中设置的容量。请咨询您的提供商,或者如果您有权访问WHM,请更改帐户的maxium php上载(通常为8GB),然后您必须更改php。ini。当我不得不做同样的事情时,以下URL对我很有帮助:

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

但是,如果您所在的服务器有限制,那么这并不是一个完整的解决方案。

结束

相关推荐

Get images from the post

使用Add Media 我在帖子中插入图片。我只想在单个贴子页面上检索这些图像。我正在使用get_attached_media 函数可检索这些图像。但它返回一个空数组。 <?php $media_image = get_attached_media( \'image\', $post->ID ); //print_r($media_image);?> <div class =\"single-post-flex-slider\">&#