<?php
declare(strict_types = 1);

/**
 * Gestion des archives Zip.
 *
 * @license https://www.gnu.org/licenses/gpl-3.0.html
 * @link https://www.igalerie.org/
 */
class Zip extends ZipArchive
{
	/**
	 * Crée et envoi une archive Zip.
	 *
	 * @param string $zip_filename
	 *   Nom de fichier de l'archive.
	 * @param array $files
	 *   Fichiers à inclure dans l'archive.
	 *
	 * @return void
	 */
	public static function archive(string $zip_filename, array &$files): void
	{
		try
		{
			// L'extension ZipArchive doit être chargée.
			if (!extension_loaded('zip'))
			{
				throw new Exception('ZipArchive is required.');
			}

			// Fichier temporaire.
			$zip_temp_file = tempnam(App::getTempDirSys(), 'zip');
			if ($zip_temp_file === FALSE)
			{
				throw new Exception('ZipArchive tempnam error.');
			}

			// Création d'un objet ZipArchive.
			$zip_archive = new ZipArchive();
			$r = $zip_archive->open($zip_temp_file, ZipArchive::OVERWRITE);
			if ($r !== TRUE)
			{
				throw new Exception('ZipArchive error : ' . $r . '.');
			}

			// On vérifie que tous les fichiers existent bien
			// et on gére les noms de fichiers doublons.
			$files_temp = [];
			foreach ($files as &$f)
			{
				// On vérifie que le fichier existe bien sur le disque.
				if (!file_exists($f))
				{
					continue;
				}

				// On renomme le fichier si un fichier de même nom a déjà été ajouté à l'archive.
				$filename = basename($f);
				$test = $filename;
				$n = 2;
				while (in_array($test, $files_temp))
				{
					$test = preg_replace('`^(.+)\.([^\.]+)$`', '\1_' . $n . '.\2', $filename);
					$n++;
				}
				$filename = $test;
				if (!in_array($filename, $files_temp))
				{
					$files_temp[] = $filename;
				}

				// On ajoute le fichier à l'archive.
				if (!$zip_archive->addFile($f, $filename))
				{
					throw new Exception('ZipArchive addFile error.');
				}
			}
			unset($files_temp);

			// Fermeture de l'archive.
			if (!$zip_archive->close())
			{
				throw new Exception('ZipArchive close error.');
			}

			// Options de configuration.
			if (function_exists('apache_setenv'))
			{
				apache_setenv('no-gzip', '1');
			}
			if (function_exists('ini_set'))
			{
				ini_set('zlib.output_compression', '0');
			}
			if (function_exists('set_time_limit'))
			{
				set_time_limit(300);
			}

			// Paramètres du fichier.
			$mtime = ($mtime = filemtime($zip_temp_file)) ? $mtime : time();
			$filesize = intval(sprintf('%u', filesize($zip_temp_file)));

			// En-têtes.
			header('Content-Type: application/octet-stream');
			header('Content-Disposition: attachment; filename="' . $zip_filename
				. '"; modification-date="' . date('r', $mtime) . '";');

			// Envoi de l'archive.
			$chunksize = 1 * (1024 * 1024);
			if ($filesize > $chunksize)
			{
				$handle = fopen($zip_temp_file, 'rb');
				if ($handle === FALSE)
				{
					throw new Exception('ZipArchive fopen error.');
				}
				while (!feof($handle))
				{
					$r = fread($handle, $chunksize);
					if ($r === FALSE)
					{
						throw new Exception('ZipArchive fread error.');
					}
					echo $r;
					ob_flush();
					flush();
				}
				fclose($handle);
			}
			else if (readfile($zip_temp_file) === FALSE)
			{
				throw new Exception('ZipArchive readfile error.');
			}
		}
		catch (Exception $e)
		{
			trigger_error($e->getMessage(), E_USER_WARNING);
			Report::error($e->getMessage());
		}

		// Suppression du fichier temporaire.
		if (isset($zip_temp_file) && file_exists($zip_temp_file))
		{
			unlink($zip_temp_file);
		}
	}

	/**
	 * Extrait le contenu d'un sous-répertoire de l'archive.
	 * Source : https://www.php.net/manual/fr/ziparchive.extractto.php#116353
	 *
	 * @param string $destination
	 * @param string $subdir
	 *
	 * @return array
	 */
	public function extractSubdirTo(string $destination, string $subdir): array
    {
		$errors = [];

		// Préparation des répertoires.
		$destination = str_replace(['/', "\\"], DIRECTORY_SEPARATOR, $destination);
		$subdir = str_replace(['/', "\\"], '/', $subdir);

		if (substr($destination,
		mb_strlen(DIRECTORY_SEPARATOR, 'UTF-8') * -1) != DIRECTORY_SEPARATOR)
		{
			$destination .= DIRECTORY_SEPARATOR;
		}

		if (substr($subdir, -1) != '/')
		{
			$subdir .= '/';
		}

		// Extraction des fichiers.
		for ($i = 0; $i < $this->numFiles; $i++)
		{
			$filename = str_replace(
				$subdir . 'admin/',
				$subdir . CONF_ADMIN_DIR . '/',
				$this->getNameIndex($i)
			);

			if (substr($filename, 0, mb_strlen($subdir, 'UTF-8')) == $subdir)
			{
				$relative_path = substr($filename, mb_strlen($subdir, 'UTF-8'));
				$relative_path = str_replace(['/', "\\"], DIRECTORY_SEPARATOR, $relative_path);

				if (mb_strlen($relative_path, 'UTF-8') > 0)
				{
					// Répertoire.
					if (substr($filename, -1) == '/')
					{
						// Nouveau répertoire.
						if (!is_dir($destination . $relative_path))
						{
							if (!File::mkdir($destination . $relative_path))
							{
								$errors[$i] = $filename;
							}
						}
					}

					// Fichier.
					else
					{
						// Nouveau répertoire.
						if (dirname($relative_path) != '.')
						{
							if (!is_dir($destination . dirname($relative_path)))
							{
								if (!File::mkdir($destination . dirname($relative_path)))
								{
									$errors[$i] = $filename;
									continue;
								}
							}
						}

						// Nouveau fichier.
						if (File::putContents($destination . $relative_path,
						$this->getFromIndex($i)) === FALSE)
						{
							$errors[$i] = $filename;
						}
					}
				}
			}
		}

		return $errors;
    }
}
?>