I am trying to upload many hundreds of images daily from a folder on the server to the media library using the following script that is scheduled via 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();
The problem is that no matter what I do, the script fails after just two images with this error:
Fatal error: Allowed memory size of 1073741824 bytes exhausted (tried to allocate 28672 bytes) in /home/forge/morselandcompany.com/public/wordpress/wp-includes/wp-db.php on line 1841
I have the memory limit set to 1024M in wordpress, as well as PHP. I don't see why this script should need more than 128M, really. How can this script be optimized to work properly? (Average image size is 800kB.)
Some initial debugging with BlackFire.io suggests the following memory hogs: - wpdb->query: 3.2MB, called 31 times - mysqli_fetch_object: 1.89MB, called 595 times - run_init() in wp-settings.php: 5.4MB, called once In total blackfire suggests that over 8MB is required to run this script!
I have also tested with all plugins disabled, and that ended with the same result.
I am running on
- PHP 7.1
- Ubuntu 16.04
- DigitalOcean VPS (1 CPU, 1GB RAM)
- Wordpress 4.8
- NGINX 1.11.5
Thanks for any help!
Update: in the interest of completeness so that others may utilize the solution for memory leaks associated with get_post and wp_update_post, I have posted the finalized code that solved the problem above. As you can see, the solution was to write my own queries using $wpdb instead of relying on the two WP methods causing the memory leaks:
<?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();