it-swarm.com.de

Das Hochladen von Bildern in die Medienbibliothek schlägt fehl, wenn der Speicher voll ist

Ich versuche, täglich mehrere hundert Bilder aus einem Ordner auf dem Server mithilfe des folgenden Skripts, das über CRON geplant ist, in die Medienbibliothek hochzuladen:

<?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();

Das Problem ist, dass egal was ich tue, das Skript nach nur zwei Bildern mit diesem Fehler fehlschlägt:

Schwerwiegender Fehler: Die zulässige Speichergröße von 1073741824 Byte ist erschöpft (es wurde versucht, 28672 Byte zuzuweisen) in /home/forge/morselandcompany.com/public/wordpress/wp-includes/wp-db.php in Zeile 1841

Ich habe das Speicherlimit auf 1024M in WordPress sowie PHP eingestellt. Ich verstehe nicht, warum dieses Skript wirklich mehr als 128 MB benötigt. Wie kann dieses Skript optimiert werden, um richtig zu funktionieren? (Die durchschnittliche Bildgröße beträgt 800 KB.)

Einige anfängliche Fehlerbehebungen mit BlackFire.io schlagen die folgenden Speicherprobleme vor: - wpdb-> query: 3.2MB, 31-mal aufgerufen - mysqli_fetch_object: 1.89MB, 595-mal aufgerufen - run_init () in wp-settings.php: 5.4MB, einmal aufgerufen Insgesamt bedeutet Blackfire, dass mehr als 8 MB erforderlich sind, um dieses Skript auszuführen!

Ich habe auch mit allen deaktivierten Plugins getestet, und das endete mit dem gleichen Ergebnis.

Ich arbeite mit - PHP 7.1
- Ubuntu 16.04
- DigitalOcean VPS (1 CPU, 1 GB RAM)
- Wordpress 4.8
- NGINX 1.11.5

Danke für jede Hilfe!

Update: Der Vollständigkeit halber habe ich den finalisierten Code veröffentlicht, mit dem das oben beschriebene Problem behoben wurde, damit andere die Lösung für Speicherlecks in Verbindung mit get_post und wp_update_post verwenden können. Wie Sie sehen, bestand die Lösung darin, meine eigenen Abfragen mit $ wpdb zu schreiben, anstatt sich auf die beiden WP -Methoden zu verlassen, die zu Speicherlecks führten:

<?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();
2
mike.bronner

Ein paar Dinge

  • Verwenden Sie media_handle_sideload, damit WordPress die Dateien an den richtigen Ort verschiebt und sie für Sie validiert, den Anhang erstellt usw. Keines dieser manuellen Dinge
  • Führen Sie dies nicht einmal aus und erwarten Sie, dass es alles tut. Sie werden nur auf dasselbe Problem stoßen, aber weiter in den Import hinein. Wenn Sie unendlich viel Speicher zur Verfügung stellen, besteht ein Zeitlimit-Ausführungsproblem, bei dem das Skript einfach nicht mehr genügend Zeit hat
  • Verarbeiten Sie jeweils 5 Dateien und rufen Sie sie wiederholt auf, bis nichts mehr zu verarbeiten ist
  • Verwenden Sie einen WP CLI-Befehl, booten Sie WordPress nicht und rufen Sie es über die GUI auf. Rufen Sie es direkt von Cron aus auf und überspringen Sie das Pingen eines URL-Geschäfts. CLI-Befehle haben eine unbegrenzte Zeit, um ihre Arbeit zu erledigen, und Sie können sie nicht vom Browser aus aufrufen. Die GET-Variable mit dem Schlüssel wird komplett überflüssig.
  • Escape Escape Escape, Sie geben diese Arrays und Werte wieder, vorausgesetzt, was sie enthalten, ist sicher, aber was ist, wenn ich ein Skript-Tag hineinschleiche? Unable to delete file <script>...</script> after import.. Der größte Sicherheitsschritt, den Sie unternehmen können, der den größten Unterschied macht, aber am wenigsten genutzt wird

Ein Prototype WP CLI-Befehl

Hier ist ein einfacher WP CLI-Befehl, der den Trick ausführen sollte. Ich habe es nicht getestet, aber alle wichtigen Teile sind vorhanden. Ich vertraue darauf, dass Sie in Bezug auf PHP kein Anfänger sind und lockere Schrauben oder kleinere Fehler festziehen können. Es sind keine zusätzlichen API-Kenntnisse erforderlich.

Sie sollten es nur in einem WP - CLI-Kontext einfügen, zum Beispiel:

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

Ändern Sie dies entsprechend. Wenn Sie den Befehl in functions.php eines Themas ablegen und erwarten, dass alles funktioniert, treten Fehler auf, da WP - CLI-Klassen nur in der Befehlszeile geladen werden, niemals bei der Verarbeitung einer Browseranforderung.

Verwendungszweck:

wp mbimport run

Klasse:

<?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' );

Rufen Sie wiederholt von einem echten Cron-Job aus an. Wenn dies nicht möglich ist, verwenden Sie entweder WP Cron oder setzen Sie einen Haken bei admin_init, der nach einer GET-Variablen sucht. Verwenden Sie den Code im Befehl run mit einigen Änderungen.

Wenn WP CLI keine Option ist

Die Verwendung einer eigenständigen PHP Datei, die WP bootet, ist ein Sicherheitsrisiko und ein hervorragendes Ziel für Angreifer, wenn sie Ihre Serverressourcen erschöpfen möchten (oder Duplikationsprobleme auslösen möchten, indem Sie die URL mehrmals gleichzeitig aufrufen).

Zum Beispiel:

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

Wiederholtes Anrufen

Möglicherweise kann Ihr externer Dienst dies nicht wiederholt aufrufen. Zu dem sage ich:

  • Verlassen Sie sich nicht auf den externen Dienst, sondern lassen Sie ihn von Ihrem eigenen Server aufrufen, auch wenn keine Arbeit zu erledigen ist
  • Eine Standard-Cron-Task WP würde ebenfalls funktionieren
  • Führen Sie es alle 5 Minuten aus
  • Führen Sie den Task-Aufruf selbst aus, wenn noch Aufgaben zu erledigen sind, und verwenden Sie dabei eine nicht blockierende Anforderung. Auf diese Weise werden so lange neue Instanzen erzeugt, bis sie fertig sind, z.

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

GUI?

Wenn Sie eine Fortschrittsanzeige für eine Benutzeroberfläche im Dashboard wünschen, zählen Sie einfach, wie viele JPEG-Dateien im zu importierenden Ordner verbleiben. Wenn Sie es konfigurieren müssen, erstellen Sie eine Benutzeroberfläche, speichern Sie die Einstellungen in den Optionen und rufen Sie die Optionen im CLI-Skript auf.

Haben Sie in Betracht gezogen, die API REST zu verwenden?

Umgehen Sie den gesamten Prozess und fügen Sie die Dateien über die API REST hinzu. Sie können eine POST - Anforderung an example.com/wp-json/wp/v2/media senden, um die JPEGs hochzuladen. Kein Code auf Ihrer Website erforderlich

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

3
Tom J Nowell

Zu diesem Zweck wurde bereits eine integrierte Funktion erstellt. Sie müssen keine Codewände schreiben, um Bilder von Ihrer Festplatte hochzuladen. Sie können stattdessen media_sideload_image verwenden.

Diese Funktion lädt Ihre Dateien hoch und kümmert sich um den Dateinamen, das Datum, die ID und den Rest des Materials.

Ich habe dies nicht mit absoluten Pfaden getestet (es benötigt die URL), aber es wäre einfach, absolute Pfade in URLs zu konvertieren, basierend auf Ihren Fähigkeiten beim Schreiben des obigen Skripts.

// 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');
}

Das ist alles was du brauchst. Bilder müssen an einen Beitrag angehängt werden, aber Sie können sie anschließend entfernen.

3
Jack Johansson