プラグインなしでuploadsフォルダをZIPダウンロードする方法

Cocoonカスタマイズ集
この記事は約13分で読めます。
記事内に広告が含まれています。

はじめに

WordPressでアップロードされたファイルは通常uploadsフォルダに保存されます。
大量のメディアファイルをバックアップ目的で、これらのファイルをまとめてZIP形式で取得したい場合があります。

そこで、プラグイン「WP File Manager」などを使わずに、uploadsフォルダをZIPファイルにしてダウンロードする機能を追加します。

この記事では、この方法について説明します。

uploadsフォルダのファイル数が多い場合、ZIP化やダウンロード処理には時間がかかり、サーバーに負荷がかかることがあります。実行中はブラウザを閉じず、サーバーのタイムアウトやメモリ制限にも注意してください。

目安:約3,600ファイル、zipファイルサイズ約380MB、所要時間約12秒

完成イメージ

実装手順

以下のコードをfunctions.phpに追加します。

// ダッシュボードウィジェットを追加
add_action('wp_dashboard_setup', function () {
  // 権限チェック
  if (!current_user_can('manage_options')) {
    return;
  }

  wp_add_dashboard_widget(
    'download-uploads-zip',
    'Uploadsフォルダ ZIPダウンロード',
    'render_download_uploads_zip'
  );
});


/**
 * ダッシュボードにウィジェットを出力
 *
 * @return void
 */
if (!function_exists('render_download_uploads_zip')) :
function render_download_uploads_zip() {
  // ZIPダウンロードURLを生成
  $download_url = add_query_arg(
    [
      'download_uploads_zip'       => 'download_uploads_zip',
      'download_uploads_zip_nonce' => wp_create_nonce('download_uploads_zip_action'),
    ],
    admin_url()
  );
  ?>

  <div class="download-uploads-zip">
    <div class="download-uploads-zip__description">
      <p>
        全てのwp-content/uploadsフォルダを階層構造を維持したままZIP圧縮してダウンロードします。
      </p>

      <p class="download-uploads-zip__notice">
        <span class="fas fa-info-circle" aria-hidden="true"></span>
        大容量の場合、サーバーに負荷がかかります。
      </p>
    </div>

    <div class="download-uploads-zip__action">
      <a href="<?= esc_url($download_url); ?>" class="download-uploads-zip__button button button-primary"
        onclick="
          if (!confirm('本当に uploads フォルダのZIPダウンロードを開始しますか?処理中はブラウザを閉じないでください。')) {
            return false;
          }
          this.style.pointerEvents = 'none';
          return true;
        "
      >
        ZIPダウンロードを開始
      </a>
    </div>
  </div>

  <?php
}
endif;


// 対象の処理を実行
add_action('admin_init', function () {
  if (!isset($_GET['download_uploads_zip']) || $_GET['download_uploads_zip'] !== 'download_uploads_zip') {
    return;
  }

  // nonceチェック
  if (!isset($_GET['download_uploads_zip_nonce']) || !wp_verify_nonce($_GET['download_uploads_zip_nonce'], 'download_uploads_zip_action')) {
    return;
  }

  // 権限チェック
  if (!current_user_can('manage_options')) {
    set_transient('my_admin_notice', [
      'message' => 'エラー: この操作を実行する権限がありません。',
      'type'    => 'error',
    ], 30);

    wp_safe_redirect(admin_url());
    exit;
  }

  // タイムアウトを無制限に設定
  // 0 = 無制限(ホスティング側のポリシーで上書きされる場合あり)
  @set_time_limit(0);

  // メモリ上限を1GBに設定
  @ini_set('memory_limit', '1024M');

  // ZIP圧縮とダウンロードを実行
  download_uploads_zip();
});


/**
 * uploadsフォルダをZIP圧縮してダウンロード
 *
 * @return void
 */
if (!function_exists('download_uploads_zip')) :
function download_uploads_zip() {
  $zip_file_name = 'wordpress_uploads_' . date('Ymd_His') . '.zip';
  $temp_dir = download_uploads_zip_temp_dir();
  $zip_path = trailingslashit($temp_dir) . $zip_file_name;

  $upload_dir = wp_upload_dir();
  $source_dir = $upload_dir['basedir'];

  // ZipArchiveクラスの利用可否を確認
  if (!class_exists('ZipArchive')) {
    set_transient('my_admin_notice', [
      'message' => 'サーバーで ZipArchive クラスが利用できません。',
      'type'    => 'error',
    ], 30);

    wp_safe_redirect(admin_url());
    exit;
  }

  $zip = new ZipArchive();

  // ZIPファイルを作成・オープン
  if ($zip->open($zip_path, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
    set_transient('my_admin_notice', [
      'message' => 'ZIPファイルの作成に失敗しました。一時ディレクトリへの書き込み権限を確認してください。',
      'type'    => 'error',
    ], 30);

    wp_safe_redirect(admin_url());
    exit;
  }

  // uploadsフォルダ内を再帰的に走査
  $files = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator(
      $source_dir,
      RecursiveDirectoryIterator::SKIP_DOTS
    ),
    RecursiveIteratorIterator::SELF_FIRST
  );

  foreach ($files as $file) {
    if (!$file->isDir()) {
      // uploadsフォルダを基準とした相対パスを取得
      $relative_path = substr(
        $file->getPathname(),
        strlen($source_dir) + 1
      );

      // 一時ディレクトリ自体をZIPに含めない
      if (strpos($relative_path, 'download_uploads_zip_temp') === 0) {
        continue;
      }

      // ファイルをZIPに追加
      if (!$zip->addFile(
        $file->getPathname(),
        $relative_path
      )) {
        error_log('ZIP addFile failed for: ' . $file->getPathname());
      }
    }
  }

  // ZIPファイルをクローズ
  if (!$zip->close()) {
    set_transient('my_admin_notice', [
      'message' => 'ZIPファイルのクローズ処理が失敗しました。',
      'type'    => 'error',
    ], 30);

    wp_safe_redirect(admin_url());
    exit;
  }

  // ZIPファイルの存在とサイズを確認
  if (file_exists($zip_path) && filesize($zip_path) > 0) {

    // ブラウザ切断後も後片付けを実行
    ignore_user_abort(true);

    // 処理終了時にZIPファイルを削除
    register_shutdown_function(function () use ($zip_path) {
      if (file_exists($zip_path)) {
        @unlink($zip_path);
      }
    });

    // 出力バッファを全てクリア
    while (ob_get_level() > 0) {
      ob_end_clean();
    }

    // gzip等の出力圧縮を無効化
    // Content-Lengthとの不整合によるダウンロード破損を防止
    if (function_exists('apache_setenv')) {
      @apache_setenv('no-gzip', 1);
    }

    @ini_set('zlib.output_compression', 'Off');

    // ダウンロード用HTTPヘッダーを送信
    if (!headers_sent()) {
      header('Content-Type: application/zip');
      header(
        'Content-Disposition: attachment; filename="' .
        $zip_file_name .
        '"'
      );
      header('Content-Length: ' . filesize($zip_path));
      header('Content-Transfer-Encoding: binary');
      header(
        'Cache-Control: no-cache, must-revalidate, post-check=0, pre-check=0'
      );
      header('Pragma: public');
      header('Expires: 0');
    } else {
      // HTTPヘッダーが既に送信済みの場合はログに記録
      error_log('download_uploads_zip: headers already sent before download.');
    }

    // ZIPファイルを出力
    readfile($zip_path);

    // ダウンロード完了後にZIPファイルを削除
    // シャットダウン関数でも削除するため二重の安全策
    @unlink($zip_path);

    exit;
  }

  // ZIPファイルが存在しない、またはサイズが0の場合
  set_transient('my_admin_notice', [
    'message' => 'ZIPファイルが見つからないか、サイズ0バイトです。サーバーログを確認してください。',
    'type'    => 'error',
  ], 30);

  wp_safe_redirect(admin_url());
  exit;
}
endif;


/**
 * ZIPファイルの一時保存ディレクトリを作成・取得
 *
 * uploads/download_uploads_zip_temp/を使用し、外部アクセスを禁止
 *
 * @return string 一時ディレクトリのパス
 */
if (!function_exists('download_uploads_zip_temp_dir')) :
function download_uploads_zip_temp_dir() {
  $upload_dir = wp_upload_dir();
  $temp_dir   = trailingslashit($upload_dir['basedir']) . 'download_uploads_zip_temp';

  // 一時ディレクトリが存在しない場合は作成
  if (!is_dir($temp_dir)) {
    wp_mkdir_p($temp_dir);

    // 外部アクセスを禁止する.htaccessを作成
    // Apache 2.2と2.4の両方に対応
    $htaccess_content = "<IfModule mod_authz_core.c>\nRequire all denied\n</IfModule>\n<IfModule !mod_authz_core.c>\nDeny from all\n</IfModule>\n";

    @file_put_contents(
      $temp_dir . '/.htaccess',
      $htaccess_content
    );

    // ディレクトリ一覧表示を防止するindex.phpを作成
    @file_put_contents(
      $temp_dir . '/index.php',
      "<?php\n// Silence is golden.\n"
    );
  }

  return $temp_dir;
}
endif;


// 通知メッセージを出力
add_action('admin_notices', function () {
  if ($notice = get_transient('my_admin_notice')) {
    echo '<div class="download-uploads-zip__admin-notice notice notice-' . esc_attr($notice['type']) . ' is-dismissible"><p>' . esc_html($notice['message']) . '</p></div>';
    delete_transient('my_admin_notice');
  }
});

参考:uploadsフォルダーを一括でダウンロードする方法

ローカル環境へメディアをまとめて取得する際、FTPツールでuploadsフォルダーを直接ダウンロードすると、ファイル数が多い場合に時間がかかってしまいます。

そこで、プラグイン「WP File Manager」を利用すれば、uploadsフォルダーをZIP形式に圧縮してからダウンロードできるため、短時間で効率よく作業を行えます。

手順

WP File Manager

以下の手順で操作します(図1参照)。

  1. WP File Managerでuploadsフォルダーを選択する。
  2. 「ダウンロード」ボタンをクリックする。

さいごに

今回紹介した方法を使えば、プラグインを使わずに、uploadsフォルダにある大量のファイルをZIP形式でまとめてダウンロードできます。
処理時間やサーバー負荷に注意しながら、簡単にバックアップやファイル整理が可能です。

タイトルとURLをコピーしました