false, 'error' => __('This server has no ZipArchive extension, so packages cannot be built.', 'aumframe')]; } $kit = Exporter::build($meta); if (!class_exists('\ZipArchive')) { throw new \RuntimeException('This needs PHP\'s zip extension (ZipArchive), which is not installed on this server.'); } $zip = new \ZipArchive(); if ($zip->open($out, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) { /* translators: %s: path of the file that could not be written. */ return ['ok' => false, 'error' => sprintf(__('Could not write to %s', 'aumframe'), $out)]; } $uploads = wp_get_upload_dir(); $base = Media::uploads_base(); $n = 0; foreach ($kit['media'] as $i => $m) { $url = (string) ($m['url'] ?? ''); if ($url === '' || strpos($url, $base) !== 0) { continue; } $path = $uploads['basedir'] . substr($url, strlen($base)); if (!is_readable($path)) { // Never skip silently: one image missing from the package is a broken image on the // buyer's site $kit['media'][$i]['missing'] = true; continue; } $name = self::MEDIA_DIR . '/' . $m['id'] . '.' . pathinfo($path, PATHINFO_EXTENSION); $zip->addFile($path, $name); // `file` is added alongside `url`, never a replacement for it — see the class // comment $kit['media'][$i]['file'] = $name; $n++; } $zip->addFromString( self::KIT_FILE, (string) wp_json_encode($kit, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) ); $zip->close(); return ['ok' => true, 'path' => $out, 'size' => (int) filesize($out), 'media' => $n]; } /** * Import from a zip. * * @param array $opts passed through to `Importer::run` (siteIdentity / dryRun) */ public static function import(string $zipPath, array $opts = []): array { require_once ABSPATH . 'wp-admin/includes/file.php'; WP_Filesystem(); if (!is_readable($zipPath)) { /* translators: %s: path of the zip that could not be read. */ return ['ok' => false, 'error' => sprintf(__('Could not read %s', 'aumframe'), $zipPath)]; } $tmp = trailingslashit(get_temp_dir()) . 'aumframe-kit-' . wp_generate_password(8, false); $res = unzip_file($zipPath, $tmp); if (is_wp_error($res)) { /* translators: %s: the unzip error. */ return ['ok' => false, 'error' => sprintf(__('Could not unpack the zip: %s', 'aumframe'), $res->get_error_message())]; } $kitFile = $tmp . '/' . self::KIT_FILE; if (!is_readable($kitFile)) { self::rmdir($tmp); /* translators: %s: the file the kit zip is missing (kit.json). */ return ['ok' => false, 'error' => sprintf(__('The zip does not contain %s', 'aumframe'), self::KIT_FILE)]; } $report = Importer::run( (string) file_get_contents($kitFile), $opts + ['baseDir' => $tmp, 'mediaDir' => $tmp . '/' . self::MEDIA_DIR] ); self::rmdir($tmp); return $report; } /** * Remove the temp extraction directory. * * This used to be a hand-rolled recursive scandir/unlink/rmdir. `WP_Filesystem()` is already * initialised above (it has to be, for `unzip_file()`), and its `delete()` does the same job * recursively — but through the filesystem abstraction, so it also works on an install mounted * over FTP or SSH, where direct `unlink()` silently fails and left the temp directory behind. */ private static function rmdir(string $dir): void { global $wp_filesystem; if (!$wp_filesystem || !$wp_filesystem->is_dir($dir)) { return; } $wp_filesystem->delete($dir, true); } }