commit vendor

This commit is contained in:
2025-11-11 14:49:30 +01:00
parent f33121a308
commit 6d03080c00
2436 changed files with 483781 additions and 0 deletions

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Sergey S. V.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1 @@
theme: jekyll-theme-slate

View File

@ -0,0 +1,97 @@
#!/usr/bin/env php
<?php
use wapmorgan\UnifiedArchive\CamApplication;
use wapmorgan\UnifiedArchive\UnifiedArchive;
$paths = [
// as a root package or phar
__DIR__.'/../vendor/autoload.php',
// as a dependency from bin
__DIR__.'/../autoload.php',
// as a dependency from package folder
__DIR__.'/../../../autoload.php',
];
function init_composer(array $paths) {
foreach ($paths as $path) {
if (file_exists($path)) {
require_once $path;
return true;
}
}
return false;
}
if (!init_composer($paths)) die('Run `composer install` firstly.'.PHP_EOL);
if (!class_exists('\Docopt')) die('Install docopt firsly. Run `composer require docopt/docopt ~1.0`.'.PHP_EOL);
$version = UnifiedArchive::VERSION;
$doc = <<<DOC
Universal console archive manager for Windows/Linux (part of UnifiedArchive $version).
USAGE: cam (-l|--list) ARCHIVE
cam (-t|--table) ARCHIVE
cam (-i|--info) ARCHIVE
cam (-e|--extract) [--output=DIR] [--replace=(all|ask|none|time|size)] [--flat=(file|path)] [--exclude=PATTERN] ARCHIVE [FILES_IN_ARCHIVE...]
cam (-p|--print) ARCHIVE FILES_IN_ARCHIVE...
cam (-d|--details) ARCHIVE FILES_IN_ARCHIVE...
cam (-x|--delete) ARCHIVE FILES_IN_ARCHIVE...
cam (-a|--add) ARCHIVE FILES_ON_DISK...
cam (-c|--create) ARCHIVE FILES_ON_DISK...
cam (-f|--formats)
ACTIONS:
-l(--list) List files in archive
-t(--table) List files as table in archive
-i(--info) Summary about archive
-e(--extract) Extract from archive
-p(--print) Extract archive file content on terminal
-d(--details) Details about file in archive
-x(--delete) Delete files from archive
-a(--add) Pack files to archive
-c(--create) Create new archive
OPTIONS:
for --extract (-e):
--replace=all|ask|none|time|size
Set how should be resolved cases in that extracting files already exist.
all - replaces all files
ask - ask for manual resolution on every case
none - preserve all existing on disk files
time - selects file with later timestamp
size - selects file with bigger size
--flat=[file,path]
Removes all hierarchy and stores all files in one directory. With path option file name will be prepended with in-archive path (all "/" replaced by "-").
--exclude FILES... or
--exclude /PATTERN/
Excludes one or few files, directories by exact in-archive path or by regular expression pattern.
--output=DIRECTORY
Set output directory in that all files will be extracted.
DOC;
$args = Docopt::handle($doc, ['version' => UnifiedArchive::VERSION]);
$actions = array(
'l:list' => 'listArray',
't:table' => 'table',
'i:info' => 'info',
'e:extract' => 'extract',
'p:print' => 'printFile',
'd:details' => 'details',
'x:delete' => 'delete',
'a:add' => 'add',
'c:create' => 'create',
'f:formats' => 'checkFormats',
);
foreach ($actions as $arg => $v) {
$arg = explode(':', $arg);
if ($args['-'.$arg[0]] === true || $args['--'.$arg[1]] === true) {
$application = new CamApplication();
call_user_func(array($application, $v), $args);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace wapmorgan\UnifiedArchive;
/**
* Information class. Represent information about concrete file in archive.
*
* @package wapmorgan\UnifiedArchive
*/
class ArchiveEntry
{
/** @var string Path of archive entry */
public $path;
/** @var int Size of packed entry in bytes */
public $compressedSize;
/** @var int Size of unpacked entry in bytes */
public $uncompressedSize;
/** @var int Time of entry modification in unix timestamp format. */
public $modificationTime;
/** @var bool */
public $isCompressed;
/**
* @var string Path of archive entry
* @deprecated 0.1.0
* @see $path property
*/
public $filename;
/**
* ArchiveEntry constructor.
* @param $path
* @param $compressedSize
* @param $uncompressedSize
* @param $modificationTime
* @param $isCompressed
*/
public function __construct($path, $compressedSize, $uncompressedSize, $modificationTime, $isCompressed = null)
{
$this->path = $path;
$this->compressedSize = $compressedSize;
$this->uncompressedSize = $uncompressedSize;
$this->modificationTime = $modificationTime;
if ($isCompressed === null)
$isCompressed = $uncompressedSize !== $compressedSize;
$this->isCompressed = $isCompressed;
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace wapmorgan\UnifiedArchive;
/**
* Information class. Represent information about the whole archive.
*
* @package wapmorgan\UnifiedArchive
*/
class ArchiveInformation
{
/** @var array List of files inside archive */
public $files = [];
/** @var int Size of files inside archive in bytes */
public $compressedFilesSize = 0;
/** @var int Original size of files inside archive in bytes */
public $uncompressedFilesSize = 0;
}

View File

@ -0,0 +1,299 @@
<?php
namespace wapmorgan\UnifiedArchive;
use Exception;
use wapmorgan\UnifiedArchive\UnifiedArchive;
class CamApplication {
/**
* @param $file
* @return UnifiedArchive
* @throws Exception
* @throws \Archive7z\Exception
*/
protected function open($file)
{
if (!UnifiedArchive::canOpenArchive($file))
throw new Exception('Could not open archive '.$file.'. Try installing suggested packages or run `cam -f` to see formats support.');
$archive = UnifiedArchive::open($file);
if ($archive === null)
throw new Exception('Could not open archive '.$file);
return $archive;
}
/**
*
*/
public function checkFormats()
{
$types = [
'.zip' => [UnifiedArchive::canOpenType(UnifiedArchive::ZIP), 'install "zip" extension'],
'.rar' => [UnifiedArchive::canOpenType(UnifiedArchive::RAR), 'install "rar" extension'],
'.gz' => [UnifiedArchive::canOpenType(UnifiedArchive::GZIP), 'install "zlib" extension'],
'.bz2' => [UnifiedArchive::canOpenType(UnifiedArchive::BZIP), 'install "bz2" extension'],
'.xz' => [UnifiedArchive::canOpenType(UnifiedArchive::LZMA), 'install "xz" extension'],
'.7z' => [UnifiedArchive::canOpenType(UnifiedArchive::SEVEN_ZIP), 'install "gemorroj/archive7z" package'],
'.iso' => [UnifiedArchive::canOpenType(UnifiedArchive::ISO), 'install "phpclasses/php-iso-file" package'],
'.cab' => [UnifiedArchive::canOpenType(UnifiedArchive::CAB), 'install "wapmorgan/cab-archive" package'],
'.tar' => [UnifiedArchive::canOpenType(UnifiedArchive::TAR), 'install "phar" extension or "pear/archive_tar" package'],
'.tar.gz' => [UnifiedArchive::canOpenType(UnifiedArchive::TAR_GZIP), 'install "phar" extension or "pear/archive_tar" package and "zlib" extension'],
'.tar.bz2' => [UnifiedArchive::canOpenType(UnifiedArchive::TAR_BZIP), 'install "phar" extension or "pear/archive_tar" package and "bz2" extension'],
'.tar.xz' => [UnifiedArchive::canOpenType(UnifiedArchive::TAR_LZMA), 'install "pear/archive_tar" package and "xz" extension'],
'.tar.Z' => [UnifiedArchive::canOpenType(UnifiedArchive::TAR_LZW), 'install "pear/archive_tar" package and "compress" system utility'],
];
$installed = $not_installed = [];
foreach ($types as $extension => $configuration) {
if ($configuration[0]) {
$installed[] = $extension;
} else {
$not_installed[$extension] = $configuration[1];
}
}
if (!empty($installed)) {
echo 'Supported archive types: '.implode(', ', $installed).PHP_EOL;
}
if (!empty($not_installed)) {
echo 'Not supported archive types:'.PHP_EOL;
array_walk($not_installed, function ($instruction, $extension) {
echo '- '.$extension.': '.$instruction.PHP_EOL;
});
}
}
/**
* @param $args
* @throws Exception
* @throws \Archive7z\Exception
*/
public function listArray($args)
{
$archive = $this->open($args['ARCHIVE']);
foreach ($archive->getFileNames() as $file) {
echo $file.PHP_EOL;
}
}
/**
* @param $args
* @throws Exception
* @throws \Archive7z\Exception
*/
public function table($args)
{
$archive = $this->open($args['ARCHIVE']);
echo sprintf('%51s | %4s | %-18s'.PHP_EOL, 'File name', 'Size', 'Date');
echo str_repeat('-', 80).PHP_EOL;
foreach ($archive->getFileNames() as $file) {
$info = $archive->getFileData($file);
$size = $this->formatSize($info->uncompressedSize);
$file_name = strlen($file) > 51 ? substr($file, 0, 49).'..' : $file;
echo sprintf('%-51s | %1.1f%s | %18s'.PHP_EOL,
$file_name,
$size[0],
$size[1],
$this->formatDate($info->modificationTime)
);
}
$size = $this->formatSize($archive->countUncompressedFilesSize());
$packed_size = $this->formatSize($archive->countCompressedFilesSize());
echo str_repeat('-', 80).PHP_EOL;
echo sprintf('%51s | %1.1f%s | %1.1f%s'.PHP_EOL, 'Total '.$archive->countFiles().' file(s)', $size[0], $size[1], $packed_size[0], $packed_size[1]);
}
/**
* @param $bytes
* @param int $precision
* @return array
*/
public function formatSize($bytes, $precision = 1)
{
$units = array('b', 'k', 'm', 'g', 't');
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
$i = round($bytes, $precision);
if ($precision == 1 && $i >= 10) {
$i = round($i / 1024, 1);
$pow++;
}
return array($i, $units[$pow]);
}
/**
* @param $unixtime
*
* @return string
* @throws \Exception
*/
public function formatDate($unixtime)
{
if (strtotime('today') < $unixtime)
return 'Today, '.date('G:m', $unixtime);
else if (strtotime('yesterday') < $unixtime)
return 'Yesterday, '.date('G:m', $unixtime);
else {
$datetime = new \DateTime();
$datetime->setTimestamp($unixtime);
if ($datetime->format('Y') == date('Y'))
return $datetime->format('d M, G:m');
else
return $datetime->format('d M Y, G:m');
}
}
/**
* @param $args
* @throws Exception
* @throws \Archive7z\Exception
*/
public function info($args)
{
$archive = $this->open($args['ARCHIVE']);
echo 'Archive type: '.$archive->getArchiveType().PHP_EOL;
echo 'Archive changed: '.$this->formatDate(filemtime($args['ARCHIVE'])).PHP_EOL;
echo 'Archive contains: '.$archive->countFiles().' file'.($archive->countFiles() > 1 ? 's' : null).PHP_EOL;
echo 'Archive compressed size: '.implode(' ', $this->formatSize($archive->countCompressedFilesSize(), 2)).PHP_EOL;
echo 'Archive uncompressed size: '.implode(' ', $this->formatSize($archive->countUncompressedFilesSize(), 2)).PHP_EOL;
echo 'Archive compression ratio: '.round($archive->countUncompressedFilesSize() / $archive->countCompressedFilesSize(), 6).'/1 ('.floor($archive->countCompressedFilesSize() / $archive->countUncompressedFilesSize() * 100).'%)'.PHP_EOL;
}
/**
* @param $args
* @throws Exception
* @throws \Archive7z\Exception
*/
public function extract($args)
{
$archive = $this->open($args['ARCHIVE']);
$output = getcwd();
if (isset($args['--output'])) {
if (!is_dir($args['--output']))
mkdir($args['--output']);
$output = realpath($args['--output']);
}
if (empty($args['FILES_IN_ARCHIVE']) || $args['FILES_IN_ARCHIVE'] == array('/') || $args['FILES_IN_ARCHIVE'] == array('*')) {
$result = $archive->extractFiles($output);
if ($result === false) echo 'Error occured'.PHP_EOL;
else echo 'Extracted '.$result.' file(s) to '.$output.PHP_EOL;
} else {
$extracted = 0;
$errored = [];
foreach ($args['FILES_IN_ARCHIVE'] as $file) {
$result = $archive->extractFiles($output, $file);
if ($result === false) $errored[] = $file;
else $extracted += $result;
}
if (!empty($errored)) echo 'Errored: '.implode(', ', $errored).PHP_EOL;
if ($extracted > 0) echo 'Exctracted '.$extracted.' file(s) to '.$output.PHP_EOL;
}
}
/**
* @param $args
* @throws Exception
* @throws \Archive7z\Exception
*/
public function printFile($args)
{
$archive = $this->open($args['ARCHIVE']);
foreach ($args['FILES_IN_ARCHIVE'] as $file) {
$info = $archive->getFileData($file);
if ($info === false) {
echo 'File '.$file.' IS NOT PRESENT'.PHP_EOL;
continue;
}
echo 'File content: '.$file.' (size is '.implode('', $this->formatSize($info->uncompressedSize, 1)).')'.PHP_EOL;
echo $archive->getFileContent($file).PHP_EOL;
}
}
/**
* @param $args
* @throws Exception
* @throws \Archive7z\Exception
*/
public function details($args)
{
$archive = $this->open($args['ARCHIVE']);
foreach ($args['FILES_IN_ARCHIVE'] as $file) {
$info = $archive->getFileData($file);
if ($info === false) {
echo 'File '.$file.' IS NOT PRESENT'.PHP_EOL;
continue;
}
echo 'File name : '.$file.PHP_EOL;
echo 'Compressed size : '.implode('', $this->formatSize($info->compressedSize, 2)).PHP_EOL;
echo 'Uncompressed size: '.implode('', $this->formatSize($info->uncompressedSize, 2)).PHP_EOL;
echo 'Is compressed : '.($info->isCompressed ? 'yes' : 'no').PHP_EOL;
echo 'Date modification: '.$this->formatDate($info->modificationTime).PHP_EOL;
}
}
/**
* @param $args
* @throws Exception
* @throws \Archive7z\Exception
*/
public function delete($args)
{
$archive = $this->open($args['ARCHIVE']);
$files = $archive->getFileNames();
foreach ($args['FILES_IN_ARCHIVE'] as $file) {
if (!in_array($file, $files)) {
echo 'File '.$file.' is NOT in archive'.PHP_EOL;
continue;
}
if ($archive->deleteFiles($file) === false)
echo 'Error file '.$file.PHP_EOL;
}
}
/**
* @param $args
* @throws Exception
* @throws \Archive7z\Exception
*/
public function add($args)
{
$archive = $this->open($args['ARCHIVE']);
$added_files = $archive->addFiles($args['FILES_ON_DISK']);
if ($added_files === false)
echo 'Error'.PHP_EOL;
else
echo 'Added '.$added_files.' file(s)'.PHP_EOL;
}
/**
* @param $args
* @throws Exception
*/
public function create($args)
{
if (file_exists($args['ARCHIVE'])) {
if (is_dir($args['ARCHIVE']))
echo $args['ARCHIVE'].' is a directory!'.PHP_EOL;
else {
echo 'File '.$args['ARCHIVE'].' already exists!'.PHP_EOL;
}
} else {
$archived_files = UnifiedArchive::archiveFiles($args['FILES_ON_DISK'], $args['ARCHIVE']);
if ($archived_files === false)
echo 'Error'.PHP_EOL;
else
echo 'Created archive ' . $args['ARCHIVE'] . ' with ' . $archived_files . ' file(s) of total size ' . implode('', $this->formatSize(filesize($args['ARCHIVE']))) . PHP_EOL;
}
}
}

View File

@ -0,0 +1,6 @@
<?php
namespace wapmorgan\UnifiedArchive\Exceptions;
class ArchiveCreationException extends \Exception
{
}

View File

@ -0,0 +1,6 @@
<?php
namespace wapmorgan\UnifiedArchive\Exceptions;
class ArchiveExtractionException extends \Exception
{
}

View File

@ -0,0 +1,6 @@
<?php
namespace wapmorgan\UnifiedArchive\Exceptions;
class ArchiveModificationException extends \Exception
{
}

View File

@ -0,0 +1,6 @@
<?php
namespace wapmorgan\UnifiedArchive\Exceptions;
class EmptyFileListException extends \InvalidArgumentException
{
}

View File

@ -0,0 +1,6 @@
<?php
namespace wapmorgan\UnifiedArchive\Exceptions;
class FileAlreadyExistsException extends \Exception
{
}

View File

@ -0,0 +1,6 @@
<?php
namespace wapmorgan\UnifiedArchive\Exceptions;
class NonExistentArchiveFileException extends \Exception
{
}

View File

@ -0,0 +1,6 @@
<?php
namespace wapmorgan\UnifiedArchive\Exceptions;
class UnsupportedArchiveException extends \InvalidArgumentException
{
}

View File

@ -0,0 +1,6 @@
<?php
namespace wapmorgan\UnifiedArchive\Exceptions;
class UnsupportedOperationException extends \Exception
{
}

View File

@ -0,0 +1,29 @@
<?php
namespace wapmorgan\UnifiedArchive\Formats;
use Symfony\Component\Process\Process;
class Archive7z extends \Archive7z\Archive7z
{
/**
* @throws \Archive7z\Exception
*/
public static function getBinaryVersion()
{
if (method_exists(__CLASS__, 'makeBinary7z'))
$binary = static::makeBinary7z();
else {
// some hack for gemorroj/archive7z 4.x version
$seven_zip = new self(null);
$binary = $seven_zip->getAutoCli();
unset($seven_zip);
}
$process = new Process([str_replace('\\', '/', $binary)]);
$result = $process->mustRun()->getOutput();
if (!preg_match('~7-Zip (\[[\d]+\] )?(?<version>\d+\.\d+)~i', $result, $version))
return false;
return $version['version'];
}
}

View File

@ -0,0 +1,134 @@
<?php
namespace wapmorgan\UnifiedArchive\Formats;
use wapmorgan\UnifiedArchive\ArchiveEntry;
use wapmorgan\UnifiedArchive\ArchiveInformation;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveCreationException;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveModificationException;
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedArchiveException;
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException;
use wapmorgan\UnifiedArchive\PclzipZipInterface;
abstract class BasicFormat
{
/**
* BasicFormat constructor.
* @param string $archiveFileName
*/
abstract public function __construct($archiveFileName);
/**
* Returns summary about an archive.
* Called after
* - constructing
* - addFiles()
* - deleteFiles()
* @return ArchiveInformation
*/
abstract public function getArchiveInformation();
/**
* @return array
*/
abstract public function getFileNames();
/**
* @param string $fileName
* @return bool
*/
abstract public function isFileExists($fileName);
/**
* @param string $fileName
* @return ArchiveEntry|false
*/
abstract public function getFileData($fileName);
/**
* @param string $fileName
* @return string|false
*/
abstract public function getFileContent($fileName);
/**
* @param string $fileName
* @return bool|resource|string
*/
abstract public function getFileResource($fileName);
/**
* @param string $outputFolder
* @param array $files
* @return int Number of extracted files
* @throws ArchiveExtractionException
*/
abstract public function extractFiles($outputFolder, array $files);
/**
* @param string $outputFolder
* @return int Number of extracted files
* @throws ArchiveExtractionException
*/
abstract public function extractArchive($outputFolder);
/**
* @param array $files
* @return false|int Number of deleted files
* @throws UnsupportedOperationException
* @throws ArchiveModificationException
*/
abstract public function deleteFiles(array $files);
/**
* @param array $files
* @return int Number of added files
* @throws UnsupportedOperationException
* @throws ArchiveModificationException
*/
abstract public function addFiles(array $files);
/**
* @param array $files
* @param string $archiveFileName
* @return int Number of archived files
* @throws UnsupportedOperationException
* @throws ArchiveCreationException
*/
public static function createArchive(array $files, $archiveFileName) {
throw new UnsupportedOperationException();
}
/**
* @return bool
*/
public static function canCreateArchive()
{
return false;
}
/**
* @return bool
*/
public static function canAddFiles()
{
return false;
}
/**
* @return bool
*/
public static function canDeleteFiles()
{
return false;
}
/**
* @throws UnsupportedOperationException
* @return PclzipZipInterface
*/
public function getPclZip()
{
throw new UnsupportedOperationException('Format '.get_class($this).' does not support PclZip-interface');
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace wapmorgan\UnifiedArchive\Formats;
class Bzip extends OneFileFormat
{
const FORMAT_SUFFIX = 'bz2';
/**
* Bzip constructor.
*
* @param $archiveFileName
*
* @throws \Exception
*/
public function __construct($archiveFileName)
{
parent::__construct($archiveFileName);
$this->modificationTime = filemtime($this->fileName);
}
/**
* @param string $fileName
*
* @return string|false
*/
public function getFileContent($fileName = null)
{
return bzdecompress(file_get_contents($this->fileName));
}
/**
* @param string $fileName
*
* @return bool|resource|string
*/
public function getFileResource($fileName = null)
{
return bzopen($this->fileName, 'r');
}
/**
* @param $data
*
* @return mixed|string
*/
protected static function compressData($data)
{
return bzcompress($data);
}
}

View File

@ -0,0 +1,182 @@
<?php
namespace wapmorgan\UnifiedArchive\Formats;
use CabArchive;
use Exception;
use wapmorgan\UnifiedArchive\ArchiveEntry;
use wapmorgan\UnifiedArchive\ArchiveInformation;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException;
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException;
class Cab extends BasicFormat
{
/** @var CabArchive */
protected $cab;
/**
* BasicFormat constructor.
*
* @param string $archiveFileName
* @throws Exception
*/
public function __construct($archiveFileName)
{
$this->open($archiveFileName);
}
/**
* Iso format destructor
*/
public function __destruct()
{
$this->cab = null;
}
/**
* @param $archiveFileName
* @throws Exception
*/
protected function open($archiveFileName)
{
try {
$this->cab = new CabArchive($archiveFileName);
} catch (Exception $e) {
throw new Exception('Could not open Cab archive: '.$e->getMessage(), $e->getCode(), $e);
}
}
/**
* @return ArchiveInformation
*/
public function getArchiveInformation()
{
$information = new ArchiveInformation();
foreach ($this->cab->getFileNames() as $file) {
$information->files[] = $file;
$file_info = $this->cab->getFileData($file);
$information->uncompressedFilesSize += $file_info->size;
$information->compressedFilesSize += $file_info->packedSize;
}
return $information;
}
/**
* @return array
*/
public function getFileNames()
{
return $this->cab->getFileNames();
}
/**
* @param string $fileName
*
* @return bool
*/
public function isFileExists($fileName)
{
return in_array($fileName, $this->cab->getFileNames(), true);
}
/**
* @param string $fileName
*
* @return ArchiveEntry|false
*/
public function getFileData($fileName)
{
$data = $this->cab->getFileData($fileName);
return new ArchiveEntry($fileName, $data->packedSize, $data->size, $data->unixtime, $data->is_compressed);
}
/**
* @param string $fileName
*
* @return string|false
* @throws Exception
*/
public function getFileContent($fileName)
{
return $this->cab->getFileContent($fileName);
}
/**
* @param string $fileName
*
* @return bool|resource|string
* @throws Exception
*/
public function getFileResource($fileName)
{
$resource = fopen('php://temp', 'r+');
fwrite($resource, $this->cab->getFileContent($fileName));
rewind($resource);
return $resource;
}
/**
* @param string $outputFolder
* @param array $files
* @return int Number of extracted files
* @throws ArchiveExtractionException
*/
public function extractFiles($outputFolder, array $files)
{
try {
return $this->cab->extract($outputFolder, $files);
} catch (Exception $e) {
throw new ArchiveExtractionException($e->getMessage(),
$e->getCode(),
$e->getPrevious()
);
}
}
/**
* @param string $outputFolder
* @return int
* @throws ArchiveExtractionException
*/
public function extractArchive($outputFolder)
{
try {
return $this->cab->extract($outputFolder);
} catch (Exception $e) {
throw new ArchiveExtractionException($e->getMessage(),
$e->getCode(),
$e->getPrevious()
);
}
}
/**
* @param array $files
* @return void
* @throws UnsupportedOperationException
*/
public function deleteFiles(array $files)
{
throw new UnsupportedOperationException();
}
/**
* @param array $files
* @return void
* @throws UnsupportedOperationException
*/
public function addFiles(array $files)
{
throw new UnsupportedOperationException();
}
/**
* @param array $files
* @param string $archiveFileName
* @return void
* @throws UnsupportedOperationException
*/
public static function createArchive(array $files, $archiveFileName){
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace wapmorgan\UnifiedArchive\Formats;
use Exception;
class Gzip extends OneFileFormat
{
const FORMAT_SUFFIX = 'gz';
/**
* @param string $file GZipped file
* @return array|false Array with 'mtime' and 'size' items
*/
public static function gzipStat($file)
{
$fp = fopen($file, 'rb');
if (filesize($file) < 18 || strcmp(fread($fp, 2), "\x1f\x8b")) {
return false; // Not GZIP format (See RFC 1952)
}
$method = fread($fp, 1);
$flags = fread($fp, 1);
$stat = unpack('Vmtime', fread($fp, 4));
fseek($fp, -4, SEEK_END);
$stat += unpack('Vsize', fread($fp, 4));
fclose($fp);
return $stat;
}
/**
* Gzip constructor.
*
* @param $archiveFileName
*
* @throws \Exception
*/
public function __construct($archiveFileName)
{
parent::__construct($archiveFileName);
$stat = static::gzipStat($archiveFileName);
if ($stat === false) {
throw new Exception('Could not open Gzip file');
}
$this->uncompressedSize = $stat['size'];
$this->modificationTime = $stat['mtime'];
}
/**
* @param string $fileName
*
* @return string|false
*/
public function getFileContent($fileName = null)
{
return gzdecode(file_get_contents($this->fileName));
}
/**
* @param string $fileName
*
* @return bool|resource|string
*/
public function getFileResource($fileName = null)
{
return gzopen($this->fileName, 'rb');
}
/**
* @param $data
*
* @return mixed|string
*/
protected static function compressData($data)
{
return gzencode($data);
}
}

View File

@ -0,0 +1,229 @@
<?php
namespace wapmorgan\UnifiedArchive\Formats;
use wapmorgan\UnifiedArchive\ArchiveEntry;
use wapmorgan\UnifiedArchive\ArchiveInformation;
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException;
class Iso extends BasicFormat
{
/** @var \CISOFile */
protected $iso;
/** @var array List of files */
protected $files = [];
/** @var array */
protected $filesData = [];
/** @var int */
protected $filesSize = 0;
/** @var null|int Size of block in ISO. Used to find real position of file in ISO */
protected $blockSize;
/**
* BasicFormat constructor.
*
* @param string $archiveFileName
*/
public function __construct($archiveFileName)
{
$this->open($archiveFileName);
}
/**
* Iso format destructor
*/
public function __destruct()
{
$this->iso->close();
}
/**
* @param $archiveFileName
*/
protected function open($archiveFileName)
{
// load php-iso-files
$this->iso = new \CISOFile;
$this->iso->open($archiveFileName);
$this->iso->ISOInit();
/** @var \CVolumeDescriptor $usedDesc */
$usedDesc = $this->iso->GetDescriptor(SUPPLEMENTARY_VOLUME_DESC);
if (!$usedDesc)
$usedDesc = $this->iso->GetDescriptor(PRIMARY_VOLUME_DESC);
$this->blockSize = $usedDesc->iBlockSize;
$directories = $usedDesc->LoadMPathTable($this->iso);
// iterate over all directories
/** @var \CPathTableRecord $Directory */
foreach ($directories as $Directory) {
$directory = $Directory->GetFullPath($directories);
$directory = trim($directory, '/');
if ($directory != '') {
$directory .= '/';
// $this->files[$Directory->Location] = $directory;
}
// $this->isoCatalogsStructure[$Directory->Location]
// = $directory;
/** @var \CFileDirDescriptors[] $files */
$files = $Directory->LoadExtents($this->iso,
$usedDesc->iBlockSize, true);
if ($files) {
/** @var \CFileDirDescriptors $file */
foreach ($files as $file) {
if (in_array($file->strd_FileId, ['.', '..']) || $file->IsDirectory())
continue;
$this->files[$file->Location] = $directory.$file->strd_FileId;
$this->filesSize += $file->DataLen;
$this->filesData[$directory . $file->strd_FileId] =
[
'size' => $file->DataLen,
'mtime' =>
strtotime((string)$file->isoRecDate),
];
}
}
// break;
}
}
/**
* @return ArchiveInformation
*/
public function getArchiveInformation()
{
$information = new ArchiveInformation();
$information->files = array_values($this->files);
$information->compressedFilesSize = $information->uncompressedFilesSize = $this->filesSize;
return $information;
}
/**
* @return array
*/
public function getFileNames()
{
return array_values($this->files);
}
/**
* @param string $fileName
*
* @return bool
*/
public function isFileExists($fileName)
{
return array_key_exists($fileName, $this->filesData);
}
/**
* @param string $fileName
*
* @return ArchiveEntry|false
*/
public function getFileData($fileName)
{
if (!isset($this->filesData[$fileName]))
return false;
return new ArchiveEntry($fileName, $this->filesData[$fileName]['size'],
$this->filesData[$fileName]['size'], $this->filesData[$fileName]['mtime'],false);
}
/**
* @param string $fileName
*
* @return string|false
*/
public function getFileContent($fileName)
{
$data = $this->prepareForFileExtracting($fileName);
return $this->iso->Read($data['size']);
}
/**
* @param string $fileName
*
* @return bool|resource|string
*/
public function getFileResource($fileName)
{
$data = $this->prepareForFileExtracting($fileName);
$resource = fopen('php://temp', 'r+');
fwrite($resource, $this->iso->Read($data['size']));
rewind($resource);
return $resource;
}
/**
* @param string $fileName
* @return array
*/
protected function prepareForFileExtracting($fileName)
{
$Location = array_search($fileName, $this->files, true);
if (!isset($this->filesData[$fileName])) return false;
$data = $this->filesData[$fileName];
$Location_Real = $Location * $this->blockSize;
if ($this->iso->Seek($Location_Real, SEEK_SET) === false)
return false;
return $data;
}
/**
* @param string $outputFolder
* @param array $files
* @return void
* @throws UnsupportedOperationException
* @todo Implement extracting with reading & writing to FS
*/
public function extractFiles($outputFolder, array $files)
{
throw new UnsupportedOperationException();
}
/**
* @param string $outputFolder
* @return void
* @throws UnsupportedOperationException
* @todo Implement extracting with reading & writing to FS
*/
public function extractArchive($outputFolder)
{
throw new UnsupportedOperationException();
}
/**
* @param array $files
* @return void
* @throws UnsupportedOperationException
*/
public function deleteFiles(array $files)
{
throw new UnsupportedOperationException();
}
/**
* @param array $files
* @return void
* @throws UnsupportedOperationException
*/
public function addFiles(array $files)
{
throw new UnsupportedOperationException();
}
/**
* @param array $files
* @param string $archiveFileName
* @return void
* @throws UnsupportedOperationException
*/
public static function createArchive(array $files, $archiveFileName){
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace wapmorgan\UnifiedArchive\Formats;
/**
* Class Lzma
*
* @package wapmorgan\UnifiedArchive\Formats
* @requires ext-lzma2
*/
class Lzma extends OneFileFormat
{
const FORMAT_SUFFIX = 'xz';
/**
* Lzma constructor.
*
* @param $archiveFileName
*
* @throws \Exception
*/
public function __construct($archiveFileName)
{
parent::__construct($archiveFileName);
$this->modificationTime = filemtime($this->fileName);
}
/**
* @param string $fileName
*
* @return string|false
*/
public function getFileContent($fileName = null)
{
return stream_get_contents(xzopen($this->fileName, 'r'));
}
/**
* @param string $fileName
*
* @return bool|resource|string
*/
public function getFileResource($fileName = null)
{
return xzopen($this->fileName, 'r');
}
/**
* @param $data
*
* @return mixed|string
*/
protected static function compressData($data)
{
$fp = xzopen('php://temp', 'w');
xzwrite($fp, $data);
$data = stream_get_contents($fp);
xzclose($fp);
return $data;
}
}

View File

@ -0,0 +1,174 @@
<?php
namespace wapmorgan\UnifiedArchive\Formats;
use Exception;
use wapmorgan\UnifiedArchive\ArchiveEntry;
use wapmorgan\UnifiedArchive\ArchiveInformation;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveCreationException;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveModificationException;
use wapmorgan\UnifiedArchive\Exceptions\EmptyFileListException;
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException;
abstract class OneFileFormat extends BasicFormat
{
/** @var null|string Should be filled for real format like 'gz' or other */
const FORMAT_SUFFIX = null;
protected $fileName;
protected $inArchiveFileName;
protected $uncompressedSize;
protected $modificationTime;
/**
* BasicFormat constructor.
*
* @param string $archiveFileName
*
* @throws \Exception
*/
public function __construct($archiveFileName)
{
if (static::FORMAT_SUFFIX === null)
throw new \Exception('Format should be initialized');
$this->fileName = $archiveFileName;
$this->inArchiveFileName = basename($archiveFileName, '.'.self::FORMAT_SUFFIX);
}
/**
* @return ArchiveInformation
*/
public function getArchiveInformation()
{
$information = new ArchiveInformation();
$information->compressedFilesSize = filesize($this->fileName);
$information->uncompressedFilesSize = $this->uncompressedSize;
$information->files[] = $this->inArchiveFileName;
return $information;
}
/**
* @return array
*/
public function getFileNames()
{
return [$this->inArchiveFileName];
}
/**
* @param string $fileName
*
* @return bool
*/
public function isFileExists($fileName)
{
return $fileName === $this->inArchiveFileName;
}
/**
* @param string $fileName
* @return ArchiveEntry|false
*/
public function getFileData($fileName)
{
return new ArchiveEntry(
$this->inArchiveFileName,
filesize($this->fileName),
$this->uncompressedSize,
$this->modificationTime);
}
/**
* @param string $outputFolder
* @param array $files
* @return int
* @throws ArchiveExtractionException
*/
public function extractFiles($outputFolder, array $files = null)
{
return $this->extractArchive($outputFolder);
}
/**
* @param string $outputFolder
* @return int
* @throws ArchiveExtractionException
*/
public function extractArchive($outputFolder)
{
$data = $this->getFileContent($this->inArchiveFileName);
if ($data === false)
throw new ArchiveExtractionException('Could not extract archive');
$size = strlen($data);
$written = file_put_contents($outputFolder.$this->inArchiveFileName, $data);
if ($written === true) {
throw new ArchiveExtractionException('Could not extract file "'.$this->inArchiveFileName.'": could not write data');
} else if ($written < $size) {
throw new ArchiveExtractionException('Could not archive file "'.$this->inArchiveFileName.'": written '.$written.' of '.$size);
}
return 1;
}
/**
* @param array $files
* @return void
* @throws UnsupportedOperationException
*/
public function deleteFiles(array $files)
{
throw new UnsupportedOperationException();
}
/**
* @param array $files
* @return void
* @throws UnsupportedOperationException
*/
public function addFiles(array $files)
{
throw new UnsupportedOperationException();
}
/**
* @param array $files
* @param string $archiveFileName
* @return int
* @throws UnsupportedOperationException
* @throws EmptyFileListException
* @throws ArchiveCreationException
*/
public static function createArchive(array $files, $archiveFileName){
if (count($files) > 1) {
throw new UnsupportedOperationException('One-file format ('.__CLASS__.') could not archive few files');
}
if (empty($files)) {
throw new EmptyFileListException();
}
$filename = array_shift($files);
$compressed_content = static::compressData(file_get_contents($filename));
$size = strlen($compressed_content);
$written = file_put_contents($archiveFileName, $compressed_content);
if ($written === true) {
throw new ArchiveCreationException('Could not archive file: could not write data');
} else if ($written < $size) {
throw new ArchiveCreationException('Could not archive file: written '.$written.' of '.$size);
}
return 1;
}
/**
* @param $data
*
* @return mixed
* @throws UnsupportedOperationException
*/
protected static function compressData($data)
{
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,175 @@
<?php
namespace wapmorgan\UnifiedArchive\Formats;
use Exception;
use wapmorgan\UnifiedArchive\ArchiveEntry;
use wapmorgan\UnifiedArchive\ArchiveInformation;
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException;
class Rar extends BasicFormat
{
/** @var \RarArchive */
protected $rar;
/**
* BasicFormat constructor.
*
* @param string $archiveFileName
*
* @throws \Exception
*/
public function __construct($archiveFileName)
{
\RarException::setUsingExceptions(true);
$this->open($archiveFileName);
}
/**
* @param $archiveFileName
*
* @throws \Exception
*/
protected function open($archiveFileName)
{
$this->rar = \RarArchive::open($archiveFileName);
if ($this->rar === false) {
throw new Exception('Could not open Rar archive');
}
}
/**
* Rar format destructor
*/
public function __destruct()
{
$this->rar->close();
}
/**
* @return ArchiveInformation
*/
public function getArchiveInformation()
{
$information = new ArchiveInformation();
foreach ($this->rar->getEntries() as $i => $entry) {
if ($entry->isDirectory()) continue;
$information->files[] = $entry->getName();
$information->compressedFilesSize += $entry->getPackedSize();
$information->uncompressedFilesSize += $entry->getUnpackedSize();
}
return $information;
}
/**
* @return array
*/
public function getFileNames()
{
$files = [];
foreach ($this->rar->getEntries() as $i => $entry) {
if ($entry->isDirectory()) continue;
$files[] = $entry->getName();
}
return $files;
}
/**
* @param string $fileName
*
* @return bool
*/
public function isFileExists($fileName)
{
return $this->rar->getEntry($fileName) !== false;
}
/**
* @param string $fileName
*
* @return ArchiveEntry|false
*/
public function getFileData($fileName)
{
$entry = $this->rar->getEntry($fileName);
return new ArchiveEntry($fileName, $entry->getPackedSize(), $entry->getUnpackedSize(),
strtotime($entry->getFileTime()), $entry->getMethod() != 48);
}
/**
* @param string $fileName
*
* @return string|false
*/
public function getFileContent($fileName)
{
$entry = $this->rar->getEntry($fileName);
if ($entry->isDirectory()) return false;
return stream_get_contents($entry->getStream());
}
/**
* @param string $fileName
*
* @return bool|resource|string
*/
public function getFileResource($fileName)
{
$entry = $this->rar->getEntry($fileName);
if ($entry->isDirectory()) return false;
return $entry->getStream();
}
/**
* @param string $outputFolder
* @param array $files
*
* @return false|int
*/
public function extractFiles($outputFolder, array $files)
{
$count = 0;
foreach ($files as $file) {
if ($this->rar->getEntry($file)->extract($outputFolder)) {
$count++;
}
}
return $count;
}
/**
* @param string $outputFolder
*
* @return false|resource
*/
public function extractArchive($outputFolder)
{
return $this->extractFiles($outputFolder, $this->getFileNames());
}
/**
* @param array $files
* @throws UnsupportedOperationException
*/
public function deleteFiles(array $files)
{
throw new UnsupportedOperationException();
}
/**
* @param array $files
* @throws UnsupportedOperationException
*/
public function addFiles(array $files)
{
throw new UnsupportedOperationException();
}
/**
* @param array $files
* @param string $archiveFileName
* @throws UnsupportedOperationException
*/
public static function createArchive(array $files, $archiveFileName){
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,236 @@
<?php
namespace wapmorgan\UnifiedArchive\Formats;
use Exception;
use wapmorgan\UnifiedArchive\ArchiveEntry;
use wapmorgan\UnifiedArchive\ArchiveInformation;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveCreationException;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveModificationException;
class SevenZip extends BasicFormat
{
/** @var Archive7z */
protected $sevenZip;
/**
* BasicFormat constructor.
*
* @param string $archiveFileName
*
* @throws \Exception
*/
public function __construct($archiveFileName)
{
try {
$this->sevenZip = new Archive7z($archiveFileName, null, null);
} catch (\Archive7z\Exception $e) {
throw new Exception('Could not open 7Zip archive: '.$e->getMessage(), $e->getCode(), $e);
}
}
/**
* @return ArchiveInformation
*/
public function getArchiveInformation()
{
$information = new ArchiveInformation();
foreach ($this->sevenZip->getEntries() as $entry) {
$information->files[] = method_exists($entry, 'getUnixPath')
? $entry->getUnixPath()
: str_replace('\\', '/', $entry->getPath());
$information->compressedFilesSize += (int)$entry->getPackedSize();
$information->uncompressedFilesSize += (int)$entry->getSize();
}
return $information;
}
/**
* @return array
*/
public function getFileNames()
{
$files = [];
foreach ($this->sevenZip->getEntries() as $entry)
$files[] = $entry->getPath();
return $files;
}
/**
* @param string $fileName
*
* @return bool
*/
public function isFileExists($fileName)
{
return $this->sevenZip->getEntry($fileName) !== null;
}
/**
* @param string $fileName
*
* @return ArchiveEntry|false
*/
public function getFileData($fileName)
{
$entry = $this->sevenZip->getEntry($fileName);
return new ArchiveEntry($fileName, $entry->getPackedSize(), $entry->getSize(),
strtotime($entry->getModified()));
}
/**
* @param string $fileName
*
* @return string|false
*/
public function getFileContent($fileName)
{
$entry = $this->sevenZip->getEntry($fileName);
return $entry->getContent();
}
/**
* @param string $fileName
*
* @return bool|resource|string
*/
public function getFileResource($fileName)
{
$resource = fopen('php://temp', 'r+');
$entry = $this->sevenZip->getEntry($fileName);
fwrite($resource, $entry->getContent());
rewind($resource);
return $resource;
}
/**
* @param string $outputFolder
* @param array $files
* @return int
* @throws ArchiveExtractionException
*/
public function extractFiles($outputFolder, array $files)
{
$count = 0;
try {
$this->sevenZip->setOutputDirectory($outputFolder);
foreach ($files as $file) {
$this->sevenZip->extractEntry($file);
$count++;
}
return $count;
} catch (Exception $e) {
throw new ArchiveExtractionException('Could not extract archive: '.$e->getMessage(), $e->getCode(), $e);
}
}
/**
* @param string $outputFolder
*
* @return bool
* @throws ArchiveExtractionException
*/
public function extractArchive($outputFolder)
{
try {
$this->sevenZip->setOutputDirectory($outputFolder);
$this->sevenZip->extract();
return true;
} catch (Exception $e) {
throw new ArchiveExtractionException('Could not extract archive: '.$e->getMessage(), $e->getCode(), $e);
}
}
/**
* @param array $files
* @return int Number of deleted files
* @throws ArchiveModificationException
*/
public function deleteFiles(array $files)
{
$count = 0;
try {
foreach ($files as $file) {
$this->sevenZip->delEntry($file);
$count++;
}
return $count;
} catch (Exception $e) {
throw new ArchiveModificationException('Could not modify archive: '.$e->getMessage(), $e->getCode(), $e);
}
}
/**
* @param array $files
*
* @return int
* @throws ArchiveModificationException
*/
public function addFiles(array $files)
{
$added_files = 0;
try {
foreach ($files as $localName => $filename) {
if (!is_null($filename)) {
$this->sevenZip->addEntry($filename);
$this->sevenZip->renameEntry($filename, $localName);
$added_files++;
}
}
return $added_files;
} catch (Exception $e) {
throw new ArchiveModificationException('Could not modify archive: '.$e->getMessage(), $e->getCode(), $e);
}
}
/**
* @param array $files
* @param string $archiveFileName
* @return int
* @throws ArchiveCreationException
*/
public static function createArchive(array $files, $archiveFileName) {
try {
$seven_zip = new Archive7z($archiveFileName);
foreach ($files as $localName => $filename) {
if ($filename !== null) {
$seven_zip->addEntry($filename, true);
$seven_zip->renameEntry($filename, $localName);
}
}
unset($seven_zip);
} catch (Exception $e) {
throw new ArchiveCreationException('Could not create archive: '.$e->getMessage(), $e->getCode(), $e);
}
return count($files);
}
/**
* @return bool
* @throws \Archive7z\Exception
*/
public static function canCreateArchive()
{
return static::canAddFiles();
}
/**
* @return bool
* @throws \Archive7z\Exception
*/
public static function canAddFiles()
{
$version = Archive7z::getBinaryVersion();
return $version !== false && version_compare('9.30', $version, '<=');
}
/**
* @return bool
*/
public static function canDeleteFiles()
{
return true;
}
}

View File

@ -0,0 +1,649 @@
<?php
namespace wapmorgan\UnifiedArchive\Formats;
use Archive_Tar;
use Exception;
use FilesystemIterator;
use Phar;
use PharData;
use RecursiveIteratorIterator;
use wapmorgan\UnifiedArchive\ArchiveEntry;
use wapmorgan\UnifiedArchive\ArchiveInformation;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveCreationException;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveModificationException;
use wapmorgan\UnifiedArchive\Exceptions\NonExistentArchiveFileException;
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedArchiveException;
use wapmorgan\UnifiedArchive\LzwStreamWrapper;
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException;
/**
* Tar format handler
* @package wapmorgan\UnifiedArchive\Formats
*/
class Tar extends BasicFormat
{
const TAR = 'tar';
const TAR_GZIP = 'tgz';
const TAR_BZIP = 'tbz2';
const TAR_LZMA = 'txz';
const TAR_LZW = 'tar.z';
/** @var bool */
static protected $enabledPearTar;
/** @var bool */
static protected $enabledPharData;
/**
* Checks system configuration for available Tar-manipulation libraries
*/
protected static function checkRequirements()
{
if (self::$enabledPharData === null || self::$enabledPearTar === null) {
self::$enabledPearTar = class_exists('\Archive_Tar');
self::$enabledPharData = class_exists('\PharData');
}
}
/**
* Checks whether archive can be opened with current system configuration
* @param $archiveFileName
* @return boolean
*/
// public static function canOpenArchive($archiveFileName)
// {
// self::checkRequirements();
//
// $type = self::detectArchiveType($archiveFileName);
// if ($type !== false) {
// return self::canOpenType($type);
// }
//
// return false;
// }
/**
* Detect archive type by its filename or content.
* @param $archiveFileName
* @param bool $contentCheck
* @return string|boolean One of TarArchive type constants OR false if type is not detected
*/
public static function detectArchiveType($archiveFileName, $contentCheck = true)
{
// by file name
if (preg_match('~\.(?<ext>tar|tgz|tbz2|txz|tar\.(gz|bz2|xz|z))$~', strtolower($archiveFileName), $match)) {
switch ($match['ext']) {
case 'tar':
return self::TAR;
case 'tgz':
case 'tar.gz':
return self::TAR_GZIP;
case 'tbz2':
case 'tar.bz2':
return self::TAR_BZIP;
case 'txz':
case 'tar.xz':
return self::TAR_LZMA;
case 'tar.z':
return self::TAR_LZW;
}
}
// by content
if ($contentCheck) {
$mime_type = mime_content_type($archiveFileName);
switch ($mime_type) {
case 'application/x-tar':
return self::TAR;
case 'application/x-gtar':
return self::TAR_GZIP;
}
}
return false;
}
/**
* Checks whether specific archive type can be opened with current system configuration
* @param $type
* @return boolean
*/
// public static function canOpenType($type)
// {
// self::checkRequirements();
// switch ($type) {
// case self::TAR:
// return self::$enabledPearTar || self::$enabledPharData;
//
// case self::TAR_GZIP:
// return (self::$enabledPearTar || self::$enabledPharData) && extension_loaded('zlib');
//
// case self::TAR_BZIP:
// return (self::$enabledPearTar || self::$enabledPharData) && extension_loaded('bz2');
//
//
// case self::TAR_LZMA:
// return self::$enabledPearTar && extension_loaded('lzma2');
//
// case self::TAR_LZW:
// return self::$enabledPearTar && LzwStreamWrapper::isBinaryAvailable();
// }
//
// return false;
// }
/**
* @param array $files
* @param string $archiveFileName
* @return false|int
* @throws Exception
*/
public static function createArchive(array $files, $archiveFileName)
{
static::checkRequirements();
if (static::$enabledPharData)
return static::createArchiveForPhar($files, $archiveFileName);
if (static::$enabledPearTar)
return static::createArchiveForPear($files, $archiveFileName);
throw new UnsupportedOperationException('Archive_Tar nor PharData not available');
}
/**
* Creates an archive via Pear library
* @param array $files
* @param $archiveFileName
* @return int
* @throws ArchiveCreationException
*/
protected static function createArchiveForPear(array $files, $archiveFileName)
{
$compression = null;
switch (strtolower(pathinfo($archiveFileName, PATHINFO_EXTENSION))) {
case 'gz':
case 'tgz':
$compression = 'gz';
break;
case 'bz2':
case 'tbz2':
$compression = 'bz2';
break;
case 'xz':
$compression = 'lzma2';
break;
case 'z':
$tar_aname = 'compress.lzw://' . $archiveFileName;
break;
}
if (isset($tar_aname))
$tar = new Archive_Tar($tar_aname, $compression);
else
$tar = new Archive_Tar($archiveFileName, $compression);
foreach ($files as $localName => $filename) {
$remove_dir = dirname($filename);
$add_dir = dirname($localName);
if (is_null($filename)) {
if ($tar->addString($localName, '') === false)
throw new ArchiveCreationException('Error when adding directory '.$localName.' to archive');
} else {
if ($tar->addModify($filename, $add_dir, $remove_dir) === false)
throw new ArchiveCreationException('Error when adding file '.$filename.' to archive');
}
}
$tar = null;
return count($files);
}
/**
* Creates an archive via Phar library
* @param array $files
* @param $archiveFileName
* @return bool
* @throws ArchiveCreationException
*/
protected static function createArchiveForPhar(array $files, $archiveFileName)
{
if (preg_match('~^(.+)\.(tar\.(gz|bz2))$~i', $archiveFileName, $match)) {
$ext = $match[2];
$basename = $match[1];
} else {
$ext = pathinfo($archiveFileName, PATHINFO_EXTENSION);
$basename = dirname($archiveFileName).'/'.basename($archiveFileName, '.'.$ext);
}
$tar = new PharData($basename.'.tar', 0, null, Phar::TAR);
try {
foreach ($files as $localName => $filename) {
if (is_null($filename)) {
if (!in_array($localName, ['/', ''], true)) {
if ($tar->addEmptyDir($localName) === false) {
throw new ArchiveCreationException('Error when adding directory '.$localName.' to archive');
}
}
} else {
if ($tar->addFile($filename, $localName) === false) {
throw new ArchiveCreationException('Error when adding file '.$localName.' to archive');
}
}
}
} catch (Exception $e) {
throw new ArchiveCreationException('Error when creating archive: '.$e->getMessage(), $e->getCode(), $e);
}
switch (strtolower(pathinfo($archiveFileName, PATHINFO_EXTENSION))) {
case 'gz':
case 'tgz':
$tar->compress(Phar::GZ, $ext);
break;
case 'bz2':
case 'tbz2':
$tar->compress(Phar::BZ2, $ext);
break;
}
$tar = null;
return count($files);
}
/** @var string Full path to archive */
protected $archiveFileName;
/** @var string Full path to archive */
protected $archiveType;
/** @var Archive_Tar|PharData */
protected $tar;
/** @var float Overall compression ratio of Tar archive when Archive_Tar is used */
protected $pearCompressionRatio;
/** @var array<string, integer> List of files and their index in listContent() result */
protected $pearFilesIndex;
/** @var int Flags for iterator */
const PHAR_FLAGS = FilesystemIterator::UNIX_PATHS;
/**
* Tar format constructor.
*
* @param string $archiveFileName
* @throws Exception
*/
public function __construct($archiveFileName)
{
static::checkRequirements();
$this->archiveFileName = realpath($archiveFileName);
$this->archiveType = static::detectArchiveType($this->archiveFileName);
if ($this->archiveType === false)
throw new UnsupportedArchiveException('Could not detect type for archive '.$this->archiveFileName);
$this->open($this->archiveType);
}
/**
* Tar destructor
*/
public function __destruct()
{
$this->tar = null;
}
/**
* @param string $archiveType
* @throws UnsupportedArchiveException
*/
protected function open($archiveType)
{
switch ($archiveType) {
case self::TAR_GZIP:
if (self::$enabledPharData) {
$this->tar = new PharData($this->archiveFileName, self::PHAR_FLAGS);
} else {
$this->tar = new Archive_Tar($this->archiveFileName, 'gz');
}
break;
case self::TAR_BZIP:
if (self::$enabledPharData) {
$this->tar = new PharData($this->archiveFileName, self::PHAR_FLAGS);
} else {
$this->tar = new Archive_Tar($this->archiveFileName, 'bz2');
}
break;
case self::TAR_LZMA:
if (!self::$enabledPearTar) {
throw new UnsupportedArchiveException('Archive_Tar not available');
}
$this->tar = new Archive_Tar($this->archiveFileName, 'lzma2');
break;
case self::TAR_LZW:
if (!self::$enabledPearTar) {
throw new UnsupportedArchiveException('Archive_Tar not available');
}
LzwStreamWrapper::registerWrapper();
$this->tar = new Archive_Tar('compress.lzw://' . $this->archiveFileName);
break;
default:
if (self::$enabledPharData) {
$this->tar = new PharData($this->archiveFileName, self::PHAR_FLAGS);
} else {
$this->tar = new Archive_Tar($this->archiveFileName);
}
break;
}
}
/**
* @return ArchiveInformation
*/
public function getArchiveInformation()
{
$information = new ArchiveInformation();
if ($this->tar instanceof Archive_Tar) {
$this->pearFilesIndex = [];
foreach ($this->tar->listContent() as $i => $file) {
// BUG workaround: http://pear.php.net/bugs/bug.php?id=20275
if ($file['filename'] === 'pax_global_header') {
continue;
}
$information->files[] = $file['filename'];
$information->uncompressedFilesSize += $file['size'];
$this->pearFilesIndex[$file['filename']] = $i;
}
$information->uncompressedFilesSize = filesize($this->archiveFileName);
$this->pearCompressionRatio = $information->uncompressedFilesSize != 0
? ceil($information->compressedFilesSize / $information->uncompressedFilesSize)
: 1;
} else {
$stream_path_length = strlen('phar://'.$this->archiveFileName.'/');
foreach (new RecursiveIteratorIterator($this->tar) as $i => $file) {
$information->files[] = substr($file->getPathname(), $stream_path_length);
$information->compressedFilesSize += $file->getCompressedSize();
$information->uncompressedFilesSize += filesize($file->getPathname());
}
}
return $information;
}
/**
* @return array
*/
public function getFileNames()
{
return $this->tar instanceof Archive_Tar
? $this->getFileNamesForPear()
: $this->getFileNamesForPhar();
}
/**
* @param string $fileName
* @return bool
*/
public function isFileExists($fileName)
{
if ($this->tar instanceof Archive_Tar)
return isset($this->pearFilesIndex[$fileName]);
try {
$this->tar->offsetGet($fileName);
return true;
} catch (Exception $e) {
return false;
}
}
/**
* @param string $fileName
* @return ArchiveEntry
* @throws NonExistentArchiveFileException
*/
public function getFileData($fileName)
{
if ($this->tar instanceof Archive_Tar) {
if (!isset($this->pearFilesIndex[$fileName]))
throw new NonExistentArchiveFileException('File '.$fileName.' is not found in archive files list');
$index = $this->pearFilesIndex[$fileName];
$files_list = $this->tar->listContent();
if (!isset($files_list[$index]))
throw new NonExistentArchiveFileException('File '.$fileName.' is not found in Tar archive');
$data = $files_list[$index];
unset($files_list);
return new ArchiveEntry($fileName, $data['size'] / $this->pearCompressionRatio,
$data['size'], $data['mtime'], in_array(strtolower(pathinfo($this->archiveFileName,
PATHINFO_EXTENSION)), array('gz', 'bz2', 'xz', 'Z')));
}
/** @var \PharFileInfo $entry_info */
$entry_info = $this->tar->offsetGet($fileName);
return new ArchiveEntry($fileName, $entry_info->getSize(), filesize($entry_info->getPathname()),
0, $entry_info->isCompressed());
}
/**
* @param string $fileName
* @return string
* @throws NonExistentArchiveFileException
*/
public function getFileContent($fileName)
{
if ($this->tar instanceof Archive_Tar) {
if (!isset($this->pearFilesIndex[$fileName]))
throw new NonExistentArchiveFileException('File '.$fileName.' is not found in archive files list');
return $this->tar->extractInString($fileName);
}
return $this->tar->offsetGet($fileName)->getContent();
}
/**
* @param string $fileName
* @return resource
* @throws NonExistentArchiveFileException
*/
public function getFileResource($fileName)
{
$resource = fopen('php://temp', 'r+');
if ($this->tar instanceof Archive_Tar) {
if (!isset($this->pearFilesIndex[$fileName]))
throw new NonExistentArchiveFileException('File '.$fileName.' is not found in archive files list');
fwrite($resource, $this->tar->extractInString($fileName));
} else
fwrite($resource, $this->tar->offsetGet($fileName)->getContent());
rewind($resource);
return $resource;
}
/**
* @param string $outputFolder
* @param array $files
* @return int
* @throws ArchiveExtractionException
*/
public function extractFiles($outputFolder, array $files)
{
if ($this->tar instanceof Archive_Tar) {
$result = $this->tar->extractList($files, $outputFolder);
} else {
$result = $this->tar->extractTo($outputFolder, $files, true);
}
if ($result === false) {
throw new ArchiveExtractionException('Error when extracting from '.$this->archiveFileName);
}
return count($files);
}
/**
* @param string $outputFolder
* @return false|int
* @throws ArchiveExtractionException
*/
public function extractArchive($outputFolder)
{
if ($this->tar instanceof Archive_Tar) {
$result = $this->tar->extract($outputFolder);
} else {
$result = $this->tar->extractTo($outputFolder, null, true);
}
if ($result === false) {
throw new ArchiveExtractionException('Error when extracting from '.$this->archiveFileName);
}
return 1;
}
/**
* @param array $files
* @return int
* @throws UnsupportedOperationException
*/
public function deleteFiles(array $files)
{
if ($this->tar instanceof Archive_Tar)
throw new UnsupportedOperationException();
$deleted = 0;
foreach ($files as $i => $file) {
if ($this->tar->delete($file))
$deleted++;
}
$this->tar = null;
$this->open($this->archiveType);
return $deleted;
}
/**
* @param array $files
* @return false|int
* @throws ArchiveModificationException
*/
public function addFiles(array $files)
{
$added = 0;
if ($this->tar instanceof Archive_Tar) {
foreach ($files as $localName => $filename) {
$remove_dir = dirname($filename);
$add_dir = dirname($localName);
if (is_null($filename)) {
if ($this->tar->addString($localName, "") === false) {
throw new ArchiveModificationException('Could not add directory "'.$filename.'": '.$this->tar->error_object->message, $this->tar->error_object->code);
}
} else {
if ($this->tar->addModify($filename, $add_dir, $remove_dir) === false) {
throw new ArchiveModificationException('Could not add file "'.$filename.'": '.$this->tar->error_object->message, $this->tar->error_object->code);
}
$added++;
}
}
} else {
try {
foreach ($files as $localName => $filename) {
if (is_null($filename)) {
$this->tar->addEmptyDir($localName);
} else {
$this->tar->addFile($filename, $localName);
$added++;
}
}
} catch (Exception $e) {
throw new ArchiveModificationException('Could not add file "'.$filename.'": '.$e->getMessage(), $e->getCode());
}
$this->tar = null;
// reopen to refresh files list properly
$this->open($this->archiveType);
}
return $added;
}
/**
* @return array
*/
protected function getFileNamesForPear()
{
$files = [];
$Content = $this->tar->listContent();
foreach ($Content as $i => $file) {
// BUG workaround: http://pear.php.net/bugs/bug.php?id=20275
if ($file['filename'] === 'pax_global_header') {
continue;
}
$files[] = $file['filename'];
}
return $files;
}
/**
* @return array
*/
protected function getFileNamesForPhar()
{
$files = [];
$stream_path_length = strlen('phar://'.$this->archiveFileName.'/');
foreach (new RecursiveIteratorIterator($this->tar) as $i => $file) {
$files[] = substr($file->getPathname(), $stream_path_length);
}
return $files;
}
/**
* @return bool
*/
public static function canCreateArchive()
{
return true;
}
/**
* @return bool
*/
public static function canAddFiles()
{
return true;
}
/**
* @return bool
*/
public static function canDeleteFiles()
{
static::checkRequirements();
return self::$enabledPharData;
}
}

View File

@ -0,0 +1,273 @@
<?php
namespace wapmorgan\UnifiedArchive\Formats;
use Exception;
use wapmorgan\UnifiedArchive\ArchiveEntry;
use wapmorgan\UnifiedArchive\ArchiveInformation;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveCreationException;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveModificationException;
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException;
use wapmorgan\UnifiedArchive\PclzipZipInterface;
use ZipArchive;
/**
* Class Zip
*
* @package wapmorgan\UnifiedArchive\Formats
* @requires ext-zip
*/
class Zip extends BasicFormat
{
/** @var ZipArchive */
protected $zip;
/**
* BasicFormat constructor.
*
* @param string $archiveFileName
* @throws \Exception
*/
public function __construct($archiveFileName)
{
$this->open($archiveFileName);
}
/**
* @param string $archiveFileName
* @throws UnsupportedOperationException
*/
protected function open($archiveFileName)
{
$this->zip = new ZipArchive();
$open_result = $this->zip->open($archiveFileName);
if ($open_result !== true) {
throw new UnsupportedOperationException('Could not open Zip archive: '.$open_result);
}
}
/**
* Zip format destructor
*/
public function __destruct()
{
unset($this->zip);
}
/**
* @return ArchiveInformation
*/
public function getArchiveInformation()
{
$information = new ArchiveInformation();
for ($i = 0; $i < $this->zip->numFiles; $i++) {
$file = $this->zip->statIndex($i);
// skip directories
if (in_array(substr($file['name'], -1), ['/', '\\'], true))
continue;
$information->files[$i] = $file['name'];
$information->compressedFilesSize += $file['comp_size'];
$information->uncompressedFilesSize += $file['size'];
}
return $information;
}
/**
* @return array
*/
public function getFileNames()
{
$files = [];
for ($i = 0; $i < $this->zip->numFiles; $i++) {
$file_name = $this->zip->getNameIndex($i);
// skip directories
if (in_array(substr($file_name, -1), ['/', '\\'], true))
continue;
$files[] = $file_name;
}
return $files;
}
/**
* @param string $fileName
*
* @return bool
*/
public function isFileExists($fileName)
{
return $this->zip->statName($fileName) !== false;
}
/**
* @param string $fileName
*
* @return ArchiveEntry
*/
public function getFileData($fileName)
{
$stat = $this->zip->statName($fileName);
return new ArchiveEntry($fileName, $stat['comp_size'], $stat['size'], $stat['mtime'],
$stat['comp_method'] != 0);
}
/**
* @param string $fileName
*
* @return string|false
* @throws \Exception
*/
public function getFileContent($fileName)
{
$result = $this->zip->getFromName($fileName);
if ($result === false)
throw new Exception('Could not get file information: '.$result);
return $result;
}
/**
* @param string $fileName
*
* @return bool|resource|string
*/
public function getFileResource($fileName)
{
return $this->zip->getStream($fileName);
}
/**
* @param string $outputFolder
* @param array $files
* @return int Number of extracted files
* @throws ArchiveExtractionException
*/
public function extractFiles($outputFolder, array $files)
{
if ($this->zip->extractTo($outputFolder, $files) === false)
throw new ArchiveExtractionException($this->zip->getStatusString(), $this->zip->status);
return count($files);
}
/**
* @param string $outputFolder
* @return int Number of extracted files
*@throws ArchiveExtractionException
*/
public function extractArchive($outputFolder)
{
if ($this->zip->extractTo($outputFolder) === false)
throw new ArchiveExtractionException($this->zip->getStatusString(), $this->zip->status);
return $this->zip->numFiles;
}
/**
* @param array $files
* @return int
* @throws ArchiveModificationException
* @throws UnsupportedOperationException
*/
public function deleteFiles(array $files)
{
$count = 0;
foreach ($files as $file) {
if ($this->zip->deleteName($file) === false)
throw new ArchiveModificationException($this->zip->getStatusString(), $this->zip->status);
$count++;
}
// reopen archive to save changes
$archive_filename = $this->zip->filename;
$this->zip->close();
$this->open($archive_filename);
return $count;
}
/**
* @param array $files
* @return int
* @throws ArchiveModificationException
* @throws UnsupportedOperationException
*/
public function addFiles(array $files)
{
$added_files = 0;
foreach ($files as $localName => $fileName) {
if (is_null($fileName)) {
if ($this->zip->addEmptyDir($localName) === false)
throw new ArchiveModificationException($this->zip->getStatusString(), $this->zip->status);
} else {
if ($this->zip->addFile($fileName, $localName) === false)
throw new ArchiveModificationException($this->zip->getStatusString(), $this->zip->status);
$added_files++;
}
}
// reopen archive to save changes
$archive_filename = $this->zip->filename;
$this->zip->close();
$this->open($archive_filename);
return $added_files;
}
/**
* @param array $files
* @param string $archiveFileName
* @return int
* @throws ArchiveCreationException
*/
public static function createArchive(array $files, $archiveFileName){
$zip = new ZipArchive();
$result = $zip->open($archiveFileName, ZipArchive::CREATE);
if ($result !== true)
throw new ArchiveCreationException('ZipArchive error: '.$result);
foreach ($files as $localName => $fileName) {
if ($fileName === null) {
if ($zip->addEmptyDir($localName) === false)
throw new ArchiveCreationException('Could not archive directory "'.$localName.'": '.$zip->getStatusString(), $zip->status);
} else {
if ($zip->addFile($fileName, $localName) === false)
throw new ArchiveCreationException('Could not archive file "'.$fileName.'": '.$zip->getStatusString(), $zip->status);
}
}
$zip->close();
return count($files);
}
/**
* @return PclzipZipInterface
*/
public function getPclZip()
{
return new PclzipZipInterface($this->zip);
}
/**
* @return bool
*/
public static function canCreateArchive()
{
return true;
}
/**
* @return bool
*/
public static function canAddFiles()
{
return true;
}
/**
* @return bool
*/
public static function canDeleteFiles()
{
return true;
}
}

View File

@ -0,0 +1,440 @@
<?php
namespace wapmorgan\UnifiedArchive;
/**
* Stream-wrapper and handler for lzw-compressed data.
* @requires "compress" system command (linux-only)
*
* @package wapmorgan\UnifiedArchive
*/
class LzwStreamWrapper
{
private static $registered = false;
private static $installed;
/**
*
*/
public static function registerWrapper()
{
if (!self::$registered)
stream_wrapper_register('compress.lzw', __CLASS__);
self::$registered = true;
}
public static $TMP_FILE_THRESHOLD = 0.5;
private static $AVERAGE_COMPRESSION_RATIO = 2;
public static $forceTmpFile = false;
/** High limit. unit: MBytes.
*/
public static $highLimit = 512;
private $mode;
private $path;
private $tmp;
private $tmp2;
private $data;
private $dataSize;
private $pointer;
private $writtenBytes = 0;
/**
* @param $path
* @param $mode
* @param $options
* @return bool
* @throws \Exception
*/
public function stream_open($path, $mode, $options)
{
// check for compress & uncompress utility
$this->checkBinary();
if (self::$installed === false)
throw new \Exception('compress and uncompress commands are required');
$schema = 'compress.lzw://';
if (strncasecmp($schema, $path, strlen($schema)) == 0)
$path = substr($path, strlen($schema));
if (file_exists($path)) {
$this->path = realpath($path);
$expected_data_size = filesize($path)
* self::$AVERAGE_COMPRESSION_RATIO;
$available_memory = $this->getAvailableMemory();
if ($expected_data_size <=
(self::$TMP_FILE_THRESHOLD * $available_memory)
&& !self::$forceTmpFile
&& $expected_data_size < (self::$highLimit * 1024 * 1024)) {
$this->read();
} else {
$prefix = basename(__FILE__, '.php');
if (($tmp = tempnam(sys_get_temp_dir(), $prefix)) === false)
throw new \Exception(__CLASS__.', line '.__LINE__.
': Could not create temporary file in '.
sys_get_temp_dir());
if (($tmp2 = tempnam(sys_get_temp_dir(), $prefix)) === false)
throw new \Exception(__CLASS__.', line '.__LINE__.
': Could not create temporary file in '.
sys_get_temp_dir());
$this->tmp = $tmp;
$this->tmp2 = $tmp2;
$this->read();
}
} else {
$this->path = $path;
if (self::$forceTmpFile) {
$prefix = basename(__FILE__, '.php');
if (($tmp = tempnam(sys_get_temp_dir(), $prefix)) === false)
throw new \Exception(__CLASS__.', line '.__LINE__.
': Could not create temporary file in '.
sys_get_temp_dir());
if (($tmp2 = tempnam(sys_get_temp_dir(), $prefix)) === false)
throw new \Exception(__CLASS__.', line '.__LINE__.
': Could not create temporary file in '.
sys_get_temp_dir());
$this->tmp = $tmp;
$this->tmp2 = $tmp2;
$this->pointer = 0;
} else {
$this->pointer = 0;
}
}
$this->mode = $mode;
return true;
}
/**
* @return float|int|string
* @throws \Exception
*/
public function getAvailableMemory()
{
$limit = strtoupper(ini_get('memory_limit'));
$s = array('K', 'M', 'G');
if (($multipleer = array_search(substr($limit, -1), $s)) !== false) {
$limit = substr($limit, 0, -1) * pow(1024, $multipleer + 1);
$limit -= memory_get_usage();
} elseif ($limit == -1) {
$limit = $this->getSystemMemory();
}
// var_dump(['multipleer' => $multipleer]);
// var_dump(['memory_limit' => $memory_limit]);
return $limit;
}
/**
* @return string
* @throws \Exception
*/
public function getSystemMemory()
{
self::exec('free --bytes | head -n3 | tail -n1 | awk \'{print $4}\'',
$output, $resultCode);
return trim($output);
}
/**
* @param $command
* @param $output
* @param null $resultCode
* @throws \Exception
*/
private static function exec($command, &$output, &$resultCode = null)
{
if (function_exists('system')) {
ob_start();
system($command, $resultCode);
$output = ob_get_contents();
ob_end_clean();
return;
} elseif (function_exists('exec')) {
$execOutput = array();
exec($command, $execOutput, $resultCode);
$output = implode(PHP_EOL, $execOutput);
return;
} elseif (function_exists('proc_open')) {
$process = proc_open($command, array(1 =>
fopen('php://memory', 'w')), $pipes);
$output = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$resultCode = proc_close($process);
return;
} elseif (function_exists('shell_exec')) {
$output = shell_exec($command);
return;
} else {
throw new \Exception(__FILE__.', line '.__LINE__
.': Execution functions is required! Make sure one of exec'.
' function is allowed (system, exec, proc_open, shell_exec)');
}
}
/**
* @throws \Exception
*/
private function read()
{
if ($this->tmp !== null) {
self::exec('uncompress --stdout '.escapeshellarg($this->path).
' > '.$this->tmp, $output, $resultCode);
// var_dump(['command' => 'uncompress --stdout '.
// escapeshellarg($this->path).' > '.$this->tmp, 'output' =>
// $output, 'resultCode' => $resultCode]);
if ($resultCode == 0 || $resultCode == 2 || is_null($resultCode)) {
$this->dataSize = filesize($this->tmp);
// rewind pointer
$this->pointer = 0;
} else {
throw new \Exception(__FILE__.', line '.__LINE__.
': Could not read file '.$this->path);
}
} else {
self::exec('uncompress --stdout '.escapeshellarg($this->path),
$output, $resultCode);
$this->data = &$output;
if ($resultCode == 0 || $resultCode == 2 || is_null($resultCode)) {
$this->dataSize = strlen($this->data);
// rewind pointer
$this->pointer = 0;
} else {
throw new \Exception(__FILE__.', line '.__LINE__.
': Could not read file '.$this->path);
}
}
}
/**
* @return array
*/
public function stream_stat()
{
return array(
'size' => $this->dataSize,
);
}
/**
* @throws \Exception
*/
public function stream_close()
{
// rewrite file
if ($this->writtenBytes > 0) {
// stored in temp file
if ($this->tmp !== null) {
// compress in tmp2
self::exec('compress -c '.escapeshellarg($this->tmp).' > '.
escapeshellarg($this->tmp2), $output, $code);
// escapeshellarg($this->tmp).' > '.escapeshellarg($this->tmp2),
// 'output' => $output, 'code' => $code]);
if ($code == 0 || $code == 2 || is_null($code)) {
// rewrite original file
if (rename($this->tmp2, $this->path) !== true) {
throw new \RuntimeException(__FILE__ . ', line ' . __LINE__ .
': Could not replace original file ' . $this->path);
}
} else {
throw new \RuntimeException(__FILE__.', line '.__LINE__.
': Could not compress changed data in '.$this->tmp2);
}
} else { // stored in local var
// compress in original path
// $this->exec('compress '.escapeshellarg($this->tmp).' > '.
// escapeshellarg($this->tmp2), $output, $resultCode);
if (!function_exists('proc_open')) {
throw new \Exception('proc_open is necessary for writing '.
'changed data in the file');
}
//var_dump(['command' => 'compress > '.
// escapeshellarg($this->path), 'path' => $this->path]);
$process = proc_open('compress > '.escapeshellarg($this->path),
array(0 => array('pipe', 'r')), $pipes);
// write data to process' input
fwrite($pipes[0], $this->data);
fclose($pipes[0]);
$resultCode = proc_close($process);
if ($resultCode == 0 || $resultCode == 2) {
// ok
} else {
throw new \RuntimeException(__FILE__.', line '.__LINE__.
': Could not compress changed data in '.$this->path);
}
}
}
if ($this->tmp !== null) {
unlink($this->tmp);
if (file_exists($this->tmp2)) unlink($this->tmp2);
} else {
$this->data = null;
}
}
/**
* @param $count
* @return bool|string
*/
public function stream_read($count)
{
if ($this->tmp !== null) {
$fp = fopen($this->tmp, 'r'.(strpos($this->mode, 'b') !== 0 ? 'b'
: null));
fseek($fp, $this->pointer);
$data = fread($fp, $count);
$this->pointer = ftell($fp);
fclose($fp);
return $data;
} else {
$data = substr($this->data, $this->pointer,
($this->pointer + $count));
$this->pointer = $this->pointer + $count;
return $data;
}
}
/**
* @return bool
*/
public function stream_eof()
{
return $this->pointer >= $this->dataSize;
}
/**
* @return mixed
*/
public function stream_tell()
{
return $this->pointer;
}
/**
* @param $data
* @return bool|int
*/
public function stream_write($data)
{
$this->writtenBytes += strlen($data);
if ($this->tmp !== null) {
$fp = fopen($this->tmp, 'w'.(strpos($this->mode, 'b') !== 0 ? 'b'
: null));
fseek($fp, $this->pointer);
$count = fwrite($fp, $data);
$this->pointer += $count;
fclose($fp);
return $count;
} else {
$count = strlen($data);
$prefix = substr($this->data, 0, $this->pointer);
$postfix = substr($this->data, ($this->pointer + $count));
$this->data = $prefix.$data.$postfix;
$this->pointer += $count;
return $count;
}
}
/**
* @param $offset
* @param int $whence
* @return bool
*/
public function stream_seek($offset, $whence = SEEK_SET)
{
switch ($whence) {
case SEEK_SET:
$this->pointer = $offset;
break;
case SEEK_CUR:
$this->pointer += $offset;
break;
case SEEK_END:
$actual_data_size = (is_null($this->tmp)) ? strlen($this->data)
: filesize($this->tmp);
$this->pointer = $actual_data_size - $offset;
break;
default:
return false;
}
return true;
}
/**
* @param $operation
* @return bool
*/
public function stream_lock($operation)
{
if ($this->tmp !== null) {
return false;
} else {
return true;
}
}
/**
* @param $new_size
*/
public function stream_truncate($new_size)
{
$actual_data_size = (is_null($this->tmp)) ? strlen($this->data)
: filesize($this->tmp);
if ($new_size > $actual_data_size) {
$this->stream_write(str_repeat("\00", $new_size
- $actual_data_size));
} elseif ($new_size < $actual_data_size) {
if ($this->tmp === null) {
$this->data = substr($this->data, 0, $new_size);
} else {
$fp = fopen($this->tmp, 'w'.(strpos($this->mode, 'b') !== 0
? 'b' : null));
ftruncate($fp, $new_size);
fclose($fp);
}
}
}
/**
* @throws \Exception
*/
protected static function checkBinary()
{
if (self::$installed === null) {
if (strncasecmp(PHP_OS, 'win', 3) === 0) {
self::$installed = false;
} else {
self::exec('command -v compress', $output);
if (empty($output)) {
self::$installed = false;
} else {
self::exec('command -v uncompress', $output);
if (empty($output)) {
self::$installed = false;
} else {
self::$installed = true;
}
}
}
}
}
/**
* @return boolean
* @throws \Exception
*/
public static function isBinaryAvailable()
{
self::checkBinary();
return self::$installed;
}
}

View File

@ -0,0 +1,800 @@
<?php
namespace wapmorgan\UnifiedArchive;
if (!defined('PCLZIP_ERR_NO_ERROR')) {
// ----- Constants
if (!defined('PCLZIP_READ_BLOCK_SIZE')) {
define('PCLZIP_READ_BLOCK_SIZE', 2048);
}
if (!defined('PCLZIP_SEPARATOR')) {
define('PCLZIP_SEPARATOR', ',');
}
if (!defined('PCLZIP_ERROR_EXTERNAL')) {
define('PCLZIP_ERROR_EXTERNAL', 0);
}
if (!defined('PCLZIP_TEMPORARY_DIR')) {
define('PCLZIP_TEMPORARY_DIR', sys_get_temp_dir());
}
define('PCLZIP_ERR_USER_ABORTED', 2);
define('PCLZIP_ERR_NO_ERROR', 0);
define('PCLZIP_ERR_WRITE_OPEN_FAIL', -1);
define('PCLZIP_ERR_READ_OPEN_FAIL', -2);
define('PCLZIP_ERR_INVALID_PARAMETER', -3);
define('PCLZIP_ERR_MISSING_FILE', -4);
define('PCLZIP_ERR_FILENAME_TOO_LONG', -5);
define('PCLZIP_ERR_INVALID_ZIP', -6);
define('PCLZIP_ERR_BAD_EXTRACTED_FILE', -7);
define('PCLZIP_ERR_DIR_CREATE_FAIL', -8);
define('PCLZIP_ERR_BAD_EXTENSION', -9);
define('PCLZIP_ERR_BAD_FORMAT', -10);
define('PCLZIP_ERR_DELETE_FILE_FAIL', -11);
define('PCLZIP_ERR_RENAME_FILE_FAIL', -12);
define('PCLZIP_ERR_BAD_CHECKSUM', -13);
define('PCLZIP_ERR_INVALID_ARCHIVE_ZIP', -14);
define('PCLZIP_ERR_MISSING_OPTION_VALUE', -15);
define('PCLZIP_ERR_INVALID_OPTION_VALUE', -16);
define('PCLZIP_ERR_ALREADY_A_DIRECTORY', -17);
define('PCLZIP_ERR_UNSUPPORTED_COMPRESSION', -18);
define('PCLZIP_ERR_UNSUPPORTED_ENCRYPTION', -19);
define('PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE', -20);
define('PCLZIP_ERR_DIRECTORY_RESTRICTION', -21);
// ----- Options values
define('PCLZIP_OPT_PATH', 77001);
define('PCLZIP_OPT_ADD_PATH', 77002);
define('PCLZIP_OPT_REMOVE_PATH', 77003);
define('PCLZIP_OPT_REMOVE_ALL_PATH', 77004);
define('PCLZIP_OPT_SET_CHMOD', 77005);
define('PCLZIP_OPT_EXTRACT_AS_STRING', 77006);
define('PCLZIP_OPT_NO_COMPRESSION', 77007);
define('PCLZIP_OPT_BY_NAME', 77008);
define('PCLZIP_OPT_BY_INDEX', 77009);
define('PCLZIP_OPT_BY_EREG', 77010);
define('PCLZIP_OPT_BY_PREG', 77011);
define('PCLZIP_OPT_COMMENT', 77012);
define('PCLZIP_OPT_ADD_COMMENT', 77013);
define('PCLZIP_OPT_PREPEND_COMMENT', 77014);
define('PCLZIP_OPT_EXTRACT_IN_OUTPUT', 77015);
define('PCLZIP_OPT_REPLACE_NEWER', 77016);
define('PCLZIP_OPT_STOP_ON_ERROR', 77017);
// Having big trouble with crypt. Need to multiply 2 long int
// which is not correctly supported by PHP ...
//define( 'PCLZIP_OPT_CRYPT', 77018 );
define('PCLZIP_OPT_EXTRACT_DIR_RESTRICTION', 77019);
define('PCLZIP_OPT_TEMP_FILE_THRESHOLD', 77020);
define('PCLZIP_OPT_ADD_TEMP_FILE_THRESHOLD', 77020); // alias
define('PCLZIP_OPT_TEMP_FILE_ON', 77021);
define('PCLZIP_OPT_ADD_TEMP_FILE_ON', 77021); // alias
define('PCLZIP_OPT_TEMP_FILE_OFF', 77022);
define('PCLZIP_OPT_ADD_TEMP_FILE_OFF', 77022); // alias
// ----- File description attributes
define('PCLZIP_ATT_FILE_NAME', 79001);
define('PCLZIP_ATT_FILE_NEW_SHORT_NAME', 79002);
define('PCLZIP_ATT_FILE_NEW_FULL_NAME', 79003);
define('PCLZIP_ATT_FILE_MTIME', 79004);
define('PCLZIP_ATT_FILE_CONTENT', 79005);
define('PCLZIP_ATT_FILE_COMMENT', 79006);
// ----- Call backs values
define('PCLZIP_CB_PRE_EXTRACT', 78001);
define('PCLZIP_CB_POST_EXTRACT', 78002);
define('PCLZIP_CB_PRE_ADD', 78003);
define('PCLZIP_CB_POST_ADD', 78004);
}
class PclzipZipInterface
{
const SELECT_FILTER_PASS = 1;
const SELECT_FILTER_REFUSE = 0;
const AVERAGE_ZIP_COMPRESSION_RATIO = 2;
private $archive;
/**
* PclzipZipInterface constructor.
*
* @param \ZipArchive $archive
*/
public function __construct(\ZipArchive $archive)
{
$this->archive = $archive;
}
/**
* @param $localname
* @param $filename
*
* @return object
*/
public function createFileHeader($localname, $filename)
{
return (object) array(
'filename' => $filename,
'stored_filename' => $localname,
'size' => filesize($filename),
'compressed_size' => ceil(filesize($filename)
/ self::AVERAGE_ZIP_COMPRESSION_RATIO),
'mtime' => filemtime($filename),
'comment' => null,
'folder' => is_dir($filename),
'status' => 'ok',
);
}
/**
* Creates a new archive
* Two ways of usage:
* <code>create($content, [$addDir, [$removeDir]])</code>
* <code>create($content, [... options ...]])</code>
*/
public function create($content)
{
if (is_array($content)) $paths_list = $content;
else $paths_list = explode(',', $content);
$report = array();
$options = func_get_args();
array_shift($options);
// parse options
if (isset($options[0]) && is_string($options[0])) {
$options[PCLZIP_OPT_ADD_PATH] = $options[0];
if (isset($options[1]) && is_string($options[1])) {
$options[PCLZIP_OPT_REMOVE_PATH] = $options[1];
}
} else {
$options = array_combine(
array_filter($options, function ($v) {return (bool) $v&2;}),
array_filter($options, function ($v) {return (bool) ($v-1)&2;})
);
}
// filters initiation
$filters = array();
if (isset($options[PCLZIP_OPT_REMOVE_PATH])
&& !isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
$filters[] = function (&$key, &$value) use ($options) {
$key = str_replace($key, null, $key); };
if (isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
$filters[] = function (&$key, &$value) { $key = basename($key); };
if (isset($options[PCLZIP_OPT_ADD_PATH]))
$filters[] = function (&$key, &$value) use ($options) {
$key = rtrim($options[PCLZIP_OPT_ADD_PATH], '/').'/'.
ltrim($key, '/');
};
if (isset($options[PCLZIP_CB_PRE_ADD])
&& is_callable($options[PCLZIP_CB_PRE_ADD]))
$preAddCallback = $options[PCLZIP_CB_PRE_ADD];
else $preAddCallback = function () { return 1; };
if (isset($options[PCLZIP_CB_POST_ADD])
&& is_callable($options[PCLZIP_CB_POST_ADD]))
$postAddCallback = $options[PCLZIP_CB_POST_ADD];
else $postAddCallback = function () { return 1; };
if (isset($options[PCLZIP_OPT_COMMENT]))
$this->archive->setArchiveComment($options[PCLZIP_OPT_COMMENT]);
// scan filesystem for files list
$files_list = array();
foreach ($content as $file_to_add) {
$report[] = $this->addSnippet($file_to_add, $filters,
$preAddCallback, $postAddCallback);
// additional dir contents
if (is_dir($file_to_add)) {
$directory_contents = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(
$file_to_add, \RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST);
foreach ($directory_contents as $file_to_add) {
$report[] = $this->addSnippet($file_to_add, $filters,
$preAddCallback, $postAddCallback);
}
}
}
// ...
return $report;
}
private function addSnippet($file_to_add, array $filters, $preAddCallback,
$postAddCallback)
{
if (is_file($file_to_add) || is_dir($file_to_add)) {
// apply filters to a file
$localname = $file_to_add;
$filename = $file_to_add;
foreach ($filters as $filter)
call_user_func($filter, $localname, $filename);
$file_header = $this->createFileHeader($localname, $filename);
if (call_user_func($preAddCallback, $file_header) == 1) {
//
// Check for max length > 255
//
if (strlen(basename($file_header->stored_filename)) > 255)
$file_header->status = 'filename_too_long';
if (is_file($filename))
$this->archive->addFile($file_header->filename,
$file_header->stored_filename);
else if (is_dir($filename))
$this->archive->addEmptyDir($file_header->stored_filename);
} else {
//
// File was skipped
//
$file_header->status = 'skipped';
}
return $file_header;
}
}
/**
* Lists archive content
*/
public function listContent()
{
$filesList = array();
$numFiles = $this->archive->numFiles;
for ($i = 0; $i < $numFiles; $i++) {
$statIndex = $this->archive->statIndex($i);
$filesList[] = (object) array(
'filename' => $statIndex['name'],
'stored_filename' => $statIndex['name'],
'size' => $statIndex['size'],
'compressed_size' => $statIndex['comp_size'],
'mtime' => $statIndex,
'comment' => ($comment = $this->archive->getCommentIndex
($statIndex['index']) !== false) ? $comment : null,
'folder' => in_array(substr($statIndex['name'], -1),
array('/', '\\')),
'index' => $statIndex['index'],
'status' => 'ok',
);
}
return $filesList;
}
/**
* Extracts files
* Two ways of usage:
* <code>extract([$extractPath, [$removePath]])</code>
* <code>extract([... options ...]])</code>
*/
public function extract()
{
$options = func_get_args();
array_shift($options);
// parse options
if (isset($options[0]) && is_string($options[0])) {
$options[PCLZIP_OPT_PATH] = $options[0];
if (isset($options[1]) && is_string($options[1])) {
$options[PCLZIP_OPT_REMOVE_PATH] = $options[1];
}
} else {
$options = array_combine(
array_filter($options, function ($v) {return (bool) $v&2;}),
array_filter($options, function ($v) {return (bool) ($v-1)&2;})
);
}
// filters initiation
if (isset($options[PCLZIP_OPT_PATH]))
$extractPath = rtrim($options[PCLZIP_OPT_PATH], '/');
else $extractPath = rtrim(getcwd(), '/');
$filters = array();
if (isset($options[PCLZIP_OPT_REMOVE_PATH])
&& !isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
$filters[] = function (&$key, &$value) use ($options) {
$key = str_replace($key, null, $key);
};
if (isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
$filters[] = function (&$key, &$value) { $key = basename($key); };
if (isset($options[PCLZIP_OPT_ADD_PATH]))
$filters[] = function (&$key, &$value) use ($options) {
$key = rtrim($options[PCLZIP_OPT_ADD_PATH], '/').'/'.
ltrim($key, '/');
};
if (isset($options[PCLZIP_CB_PRE_EXTRACT])
&& is_callable($options[PCLZIP_CB_PRE_EXTRACT]))
$preExtractCallback = $options[PCLZIP_CB_PRE_EXTRACT];
else $preExtractCallback = function () { return 1; };
if (isset($options[PCLZIP_CB_POST_EXTRACT])
&& is_callable($options[PCLZIP_CB_POST_EXTRACT]))
$postExtractCallback = $options[PCLZIP_CB_POST_EXTRACT];
else $postExtractCallback = function () { return 1; };
// exact matching
if (isset($options[PCLZIP_OPT_BY_NAME]))
$selectFilter = function ($key, $value) use ($options) {
$allowedNames = is_array($options[PCLZIP_OPT_BY_NAME])
? $options[PCLZIP_OPT_BY_NAME]
: explode(',', $options[PCLZIP_OPT_BY_NAME]);
foreach ($allowedNames as $name) {
// select directory with nested files
if (in_array(substr($name, -1), array('/', '\\'))) {
if (strncasecmp($name, $key, strlen($name)) === 0) {
// that's a file inside a dir or that dir
return self::SELECT_FILTER_PASS;
}
} else {
// select exact name only
if (strcasecmp($name, $key) === 0) {
// that's a file with this name
return self::SELECT_FILTER_PASS;
}
}
}
// that file is not in allowed list
return self::SELECT_FILTER_REFUSE;
};
// <ereg> rule
else if (isset($options[PCLZIP_OPT_BY_EREG]) && function_exists('ereg'))
$selectFilter = function ($key, $value) use ($options) {
return (ereg($options[PCLZIP_OPT_BY_EREG], $key) !== false)
? self::SELECT_FILTER_PASS
: self::SELECT_FILTER_REFUSE;
};
// <preg_match> rule
else if (isset($options[PCLZIP_OPT_BY_PREG]))
$selectFilter = function ($key, $value) use ($options) {
return preg_match($options[PCLZIP_OPT_BY_PREG], $key)
? self::SELECT_FILTER_PASS
: self::SELECT_FILTER_REFUSE;
};
// index rule
else if (isset($options[PCLZIP_OPT_BY_INDEX]))
$selectFilter = function ($key, $value, $index) use ($options) {
$allowedIndexes = array();
foreach ($options[PCLZIP_OPT_BY_INDEX] as $rule) {
$parts = explode('-', $rule);
if (count($parts) == 1) $allowedIndexes[] = $rule;
else $allowedIndexes = array_merge(
range($parts[0], $parts[1]), $allowedIndexes);
}
return in_array($index, $allowedIndexes) ? self::SELECT_FILTER_PASS
: self::SELECT_FILTER_REFUSE;
};
// no rule
else
$selectFilter = function () { return self::SELECT_FILTER_PASS; };
if (isset($options[PCLZIP_OPT_EXTRACT_AS_STRING]))
$anotherOutputFormat = PCLZIP_OPT_EXTRACT_AS_STRING;
else if (isset($options[PCLZIP_OPT_EXTRACT_IN_OUTPUT]))
$anotherOutputFormat = PCLZIP_OPT_EXTRACT_IN_OUTPUT;
else $anotherOutputFormat = false;
if (isset($options[PCLZIP_OPT_REPLACE_NEWER]))
$doNotReplaceNewer = false;
else $doNotReplaceNewer = true;
if (isset($options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION]))
$restrictExtractDir = $options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION];
else $restrictExtractDir = false;
$report = array();
foreach ($this->listContent() as $file_header) {
// add file information to report
$report[] = $file_header;
// refuse by select rule
if (call_user_func($selectFilter, $file_header->stored_filename,
$file_header->filename, $file_header->index)
=== self::SELECT_FILTER_REFUSE) {
//
// I don't know need to remain this file in report or not,
// but for now I remove
array_pop($report);
// $file_header->status = 'filtered';
//
continue;
}
//
// add extract path in case of extraction
// for some reason need to do it before call pre extract callback
// (pclzip.lib.php v2.8.2, line 3670)
// so I decided to do it here too
//
if ($anotherOutputFormat === false) {
$file_header->filename = realpath($extractPath.'/'.
$file_header->filename);
//
// check for path correlation with restricted path
//
if ($restrictExtractDir !== false) {
$filename = $file_header->filename;
$restrictedDir = realpath($restrictExtractDir);
if (strncasecmp($restrictedDir, $filename,
strlen($restrictedDir)) !== 0) {
// refuse file extraction
$file_header->status = 'filtered';
continue;
}
}
}
// apply pre extract callback
$callback_result = call_user_func($preExtractCallback,
$file_header);
if ($callback_result == 1) {
// go on ...
} elseif ($callback_result == 0) {
// skip current file
$file_header->status = 'skipped';
continue;
} elseif ($callback_result == 2) {
// skip & stop extraction
$file_header->status = 'aborted';
break;
}
// return content
if ($anotherOutputFormat == PCLZIP_OPT_EXTRACT_AS_STRING) {
$file_header->content
= $this->archive->getFromName($file_header->stored_filename);
}
// echo content
else if ($anotherOutputFormat == PCLZIP_OPT_EXTRACT_IN_OUTPUT) {
echo $this->archive->getFromName($file_header->stored_filename);
}
// extract content
else if ($anotherOutputFormat === false) {
// apply path filters
foreach ($filters as $filter) call_user_func($filter,
$file_header->stored_filename, $file_header->filename);
// dir extraction process
if ($file_header->folder) {
// if dir doesn't exist
if (!is_dir($file_header->filename)) {
// try to create folder
if (!mkdir($file_header)) {
$file_header->status = 'path_creation_fail';
continue;
}
}
}
// file extraction process
else {
// check if path is already taken by a folder
if (is_dir($file_header->filename)) {
$file_header->status = 'already_a_directory';
continue;
}
// check if file path is not writable
if (!is_writable($file_header->filename)) {
$file_header->status = 'write_protected';
continue;
}
// check if file exists and it's newer
if (is_file($file_header->filename)) {
if (filemtime($file_header->filename)
> $file_header->mtime) {
// skip extraction if option EXTRACT_NEWER isn't set
if ($doNotReplaceNewer) {
$file_header->status = 'newer_exist';
continue;
}
}
}
$directory = dirname($file_header->filename);
// check if running process can not create extraction folder
if (!is_dir($directory)) {
if (!mkdir($directory)) {
$file_header->status = 'path_creation_fail';
continue;
}
}
// extraction
if (copy("zip://".$this->archive->filename."#"
.$file_header->stored_filename
, $file_header->filename)) {
// ok
}
// extraction fails
else {
$file_header->status = 'write_error';
continue;
}
}
}
// apply post extract callback
$callback_result = call_user_func($postExtractCallback,
$file_header);
if ($callback_result == 1) {
// go on
} elseif ($callback_result == 2) {
// skip & stop extraction
break;
}
}
return $report;
}
/**
* Reads properties of archive
*/
public function properties()
{
return array(
'nb' => $this->archive->numFiles,
'comment' =>
(($comment = $this->archive->getArchiveComment() !== false)
? $comment : null),
'status' => 'OK',
);
}
/**
* Adds files in archive
* <code>add($content, [$addDir, [$removeDir]])</code>
* <code>add($content, [ ... options ... ])</code>
*/
public function add($content)
{
if (is_array($content)) $paths_list = $content;
else $paths_list = array_map(explode(',', $content));
$report = array();
$options = func_get_args();
array_shift($options);
// parse options
if (isset($options[0]) && is_string($options[0])) {
$options[PCLZIP_OPT_ADD_PATH] = $options[0];
if (isset($options[1]) && is_string($options[1])) {
$options[PCLZIP_OPT_REMOVE_PATH] = $options[1];
}
} else {
$options = array_combine(
array_filter($options, function ($v) {return (bool) $v&2;}),
array_filter($options, function ($v) {return (bool) ($v-1)&2;})
);
}
// filters initiation
$filters = array();
if (isset($options[PCLZIP_OPT_REMOVE_PATH])
&& !isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
$filters[] = function (&$key, &$value) use ($options) {
$key = str_replace($key, null, $key);
};
if (isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
$filters[] = function (&$key, &$value) { $key = basename($key); };
if (isset($options[PCLZIP_OPT_ADD_PATH]))
$filters[] = function (&$key, &$value) use ($options) {
$key = rtrim($options[PCLZIP_OPT_ADD_PATH], '/').'/'.
ltrim($key, '/');
};
if (isset($options[PCLZIP_CB_PRE_ADD])
&& is_callable($options[PCLZIP_CB_PRE_ADD]))
$preAddCallback = $options[PCLZIP_CB_PRE_ADD];
else $preAddCallback = function () { return 1; };
if (isset($options[PCLZIP_CB_POST_ADD])
&& is_callable($options[PCLZIP_CB_POST_ADD]))
$postAddCallback = $options[PCLZIP_CB_POST_ADD];
else $postAddCallback = function () { return 1; };
if (isset($options[PCLZIP_OPT_COMMENT]))
$this->archive->setArchiveComment($options[PCLZIP_OPT_COMMENT]);
if (isset($options[PCLZIP_OPT_ADD_COMMENT])) {
$comment =
($comment = $this->archive->getArchiveComment() !== false)
? $comment : null;
$this->archive->setArchiveComment(
$comment . $options[PCLZIP_OPT_ADD_COMMENT]);
}
if (isset($options[PCLZIP_OPT_PREPEND_COMMENT])) {
$comment =
($comment = $this->archive->getArchiveComment() !== false)
? $comment : null;
$this->archive->setArchiveComment(
$options[PCLZIP_OPT_PREPEND_COMMENT] . $comment);
}
// scan filesystem for files list
$files_list = array();
foreach ($content as $file_to_add) {
$report[] = $this->addSnippet($file_to_add, $filters,
$preAddCallback, $postAddCallback);
// additional dir contents
if (is_dir($file_to_add)) {
$directory_contents = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(
$file_to_add, \RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST);
foreach ($directory_contents as $file_to_add) {
$report[] = $this->addSnippet($file_to_add, $filters,
$preAddCallback, $postAddCallback);
}
}
}
// ...
return $report;
}
/**
* Removes files from archive
* Usage:
* <code>delete([... options ...])</code>
*/
public function delete()
{
$report = array();
$options = func_get_args();
$options = array_combine(
array_filter($options, function ($v) {return (bool) $v&2;}),
array_filter($options, function ($v) {return (bool) ($v-1)&2;})
);
// exact matching
if (isset($options[PCLZIP_OPT_BY_NAME]))
$selectFilter = function ($key, $value) use ($options) {
$allowedNames = is_array($options[PCLZIP_OPT_BY_NAME])
? $options[PCLZIP_OPT_BY_NAME]
: explode(',', $options[PCLZIP_OPT_BY_NAME]);
foreach ($allowedNames as $name) {
// select directory with nested files
if (in_array(substr($name, -1), array('/', '\\'))) {
if (strncasecmp($name, $key, strlen($name)) === 0) {
// that's a file inside a dir or that dir
return self::SELECT_FILTER_PASS;
}
} else {
// select exact name only
if (strcasecmp($name, $key) === 0) {
// that's a file with this name
return self::SELECT_FILTER_PASS;
}
}
}
// that file is not in allowed list
return self::SELECT_FILTER_REFUSE;
};
// <ereg> rule
else if (isset($options[PCLZIP_OPT_BY_EREG]) && function_exists('ereg'))
$selectFilter = function ($key, $value) use ($options) {
return (ereg($options[PCLZIP_OPT_BY_EREG], $key) !== false)
? self::SELECT_FILTER_PASS
: self::SELECT_FILTER_REFUSE;
};
// <preg_match> rule
else if (isset($options[PCLZIP_OPT_BY_PREG]))
$selectFilter = function ($key, $value) use ($options) {
return preg_match($options[PCLZIP_OPT_BY_PREG], $key)
? self::SELECT_FILTER_PASS
: self::SELECT_FILTER_REFUSE;
};
// index rule
else if (isset($options[PCLZIP_OPT_BY_INDEX]))
$selectFilter = function ($key, $value, $index) use ($options) {
$allowedIndexes = array();
foreach ($options[PCLZIP_OPT_BY_INDEX] as $rule) {
$parts = explode('-', $rule);
if (count($parts) == 1) $allowedIndexes[] = $rule;
else $allowedIndexes = array_merge(
range($parts[0], $parts[1]), $allowedIndexes);
}
return in_array($index, $allowedIndexes)
? self::SELECT_FILTER_PASS
: self::SELECT_FILTER_REFUSE;
};
// no rule
else
$selectFilter = function () { return self::SELECT_FILTER_PASS; };
foreach ($this->listContent() as $file_header) {
// select by select rule
if (call_user_func($selectFilter, $file_header->stored_filename,
$file_header->filename, $file_header->index)
=== self::SELECT_FILTER_REFUSE) {
// delete file from archive
if ($this->archive->deleteName($file_header->stored_filename)) {
// ok
continue;
}
// deletion fails
else {
return 0;
}
}
// unselected file add in report
$report[] = $file_header;
}
return $report;
}
/**
* Merges given archive into current archive
* Two ways of usage:
* <code>merge($filename)</code>
* <code>merge(UnifiedArchive $unifiedArchiveInstance)</code>
* This implementation is more intelligent than original' one.
*/
public function merge($a)
{
// filename
if (is_string($a)) {
if ($a = UnifiedArchive::open($a) !== null) {
// ok
} else {
// // unsupported type of archive
return 0;
}
}
// UnifiedArchive instance
else if ($a instanceof UnifiedArchive) {
// go on
}
// invalid argument
else {
return 0;
}
$tempDir = tempnam(PCLZIP_TEMPORARY_DIR, 'merging');
if (file_exists($tempDir)) unlink($tempDir);
if (!mkdir($tempDir)) return 0;
// go through archive content list and copy all files
foreach ($a->getFileNames() as $filename) {
// dir merging process
if (in_array(substr($filename, -1), array('/', '\\'))) {
$this->archive->addEmptyDir(rtrim($filename, '/\\'));
}
// file merging process
else {
// extract file in temporary dir
if ($a->extractNode($tempDir, '/'.$filename)) {
// go on
} else {
// extraction fails
return 0;
}
// add file in archive
if ($this->archive->addFile($tempDir.'/'.$filename,
$filename)) {
// ok
} else {
return 0;
}
}
}
call_user_func(function ($directory) {
foreach (glob($directory.'/*') as $f) {
if (is_dir($f)) call_user_func(__FUNCTION__, $f);
else unlink($f);
}
}, $tempDir);
return 1;
}
/**
* Duplicates archive
*/
public function duplicate($clone_filename)
{
return copy($this->archive->filename, $clone_filename) ? 1 : 0;
}
}

View File

@ -0,0 +1,762 @@
<?php
namespace wapmorgan\UnifiedArchive;
use InvalidArgumentException;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveCreationException;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException;
use wapmorgan\UnifiedArchive\Exceptions\ArchiveModificationException;
use wapmorgan\UnifiedArchive\Exceptions\EmptyFileListException;
use wapmorgan\UnifiedArchive\Exceptions\FileAlreadyExistsException;
use wapmorgan\UnifiedArchive\Exceptions\NonExistentArchiveFileException;
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedArchiveException;
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException;
use wapmorgan\UnifiedArchive\Formats\BasicFormat;
use wapmorgan\UnifiedArchive\Formats\Bzip;
use wapmorgan\UnifiedArchive\Formats\Cab;
use wapmorgan\UnifiedArchive\Formats\Gzip;
use wapmorgan\UnifiedArchive\Formats\Iso;
use wapmorgan\UnifiedArchive\Formats\Lzma;
use wapmorgan\UnifiedArchive\Formats\Rar;
use wapmorgan\UnifiedArchive\Formats\SevenZip;
use wapmorgan\UnifiedArchive\Formats\Tar;
use wapmorgan\UnifiedArchive\Formats\Zip;
/**
* Class which represents archive in one of supported formats.
*/
class UnifiedArchive
{
const VERSION = '1.0.0';
const ZIP = 'zip';
const SEVEN_ZIP = '7zip';
const RAR = 'rar';
const GZIP = 'gzip';
const BZIP = 'bzip2';
const LZMA = 'lzma2';
const ISO = 'iso';
const CAB = 'cab';
const TAR = 'tar';
const TAR_GZIP = 'tgz';
const TAR_BZIP = 'tbz2';
const TAR_LZMA = 'txz';
const TAR_LZW = 'tar.z';
/** @var array List of archive format handlers */
protected static $formatHandlers = [
self::ZIP => Zip::class,
self::SEVEN_ZIP => SevenZip::class,
self::RAR => Rar::class,
self::GZIP => Gzip::class,
self::BZIP => Bzip::class,
self::LZMA => Lzma::class,
self::ISO => Iso::class,
self::CAB => Cab::class,
self::TAR => Tar::class,
self::TAR_GZIP => Tar::class,
self::TAR_BZIP => Tar::class,
self::TAR_LZMA => Tar::class,
self::TAR_LZW => Tar::class,
];
/** @var array List of archive types with support-state */
static protected $enabledTypes = [];
/** @var string Type of current archive */
protected $type;
/** @var BasicFormat Adapter for current archive */
protected $archive;
/** @var array List of files in current archive */
protected $files;
/** @var int Number of files in archive */
protected $filesQuantity;
/** @var int Cumulative size of uncompressed files */
protected $uncompressedFilesSize;
/** @var int Cumulative size of compressed files */
protected $compressedFilesSize;
/** @var int Total size of archive file */
protected $archiveSize;
/**
* Creates a UnifiedArchive instance for passed archive
*
* @param string $fileName Archive filename
* @return UnifiedArchive|null Returns UnifiedArchive in case of successful reading of the file
* @throws InvalidArgumentException If archive file is not readable
*/
public static function open($fileName)
{
self::checkRequirements();
if (!file_exists($fileName) || !is_readable($fileName))
throw new InvalidArgumentException('Could not open file: '.$fileName);
$type = self::detectArchiveType($fileName);
if (!self::canOpenType($type)) {
return null;
}
return new self($fileName, $type);
}
/**
* Checks whether archive can be opened with current system configuration
*
* @param string $fileName Archive filename
* @return bool
*/
public static function canOpenArchive($fileName)
{
self::checkRequirements();
$type = self::detectArchiveType($fileName);
return $type !== false && self::canOpenType($type);
}
/**
* Checks whether specific archive type can be opened with current system configuration
*
* @param string $type One of predefined archive types (class constants)
* @return bool
*/
public static function canOpenType($type)
{
self::checkRequirements();
return isset(self::$enabledTypes[$type])
? self::$enabledTypes[$type]
: false;
}
/**
* Checks whether specified archive can be created
*
* @param string $type One of predefined archive types (class constants)
* @return bool
*/
public static function canCreateType($type)
{
self::checkRequirements();
return isset(self::$enabledTypes[$type])
? call_user_func([static::$formatHandlers[$type], 'canCreateArchive'])
: false;
}
/**
* Detect archive type by its filename or content
*
* @param string $fileName Archive filename
* @param bool $contentCheck Whether archive type can be detected by content
* @return string|bool One of UnifiedArchive type constants OR false if type is not detected
*/
public static function detectArchiveType($fileName, $contentCheck = true)
{
// by file name
$ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
if (stripos($fileName, '.tar.') !== false && preg_match('~\.(?<ext>tar\.(gz|bz2|xz|z))$~', strtolower($fileName), $match)) {
switch ($match['ext']) {
case 'tar.gz':
return self::TAR_GZIP;
case 'tar.bz2':
return self::TAR_BZIP;
case 'tar.xz':
return self::TAR_LZMA;
case 'tar.z':
return self::TAR_LZW;
}
}
switch ($ext) {
case 'zip':
return self::ZIP;
case '7z':
return self::SEVEN_ZIP;
case 'rar':
return self::RAR;
case 'gz':
return self::GZIP;
case 'bz2':
return self::BZIP;
case 'xz':
return self::LZMA;
case 'iso':
return self::ISO;
case 'cab':
return self::CAB;
case 'tar':
return self::TAR;
case 'tgz':
return self::TAR_GZIP;
case 'tbz2':
return self::TAR_BZIP;
case 'txz':
return self::TAR_LZMA;
}
// by file content
if ($contentCheck) {
$mime_type = mime_content_type($fileName);
switch ($mime_type) {
case 'application/zip':
return self::ZIP;
case 'application/x-7z-compressed':
return self::SEVEN_ZIP;
case 'application/x-rar':
return self::RAR;
case 'application/zlib':
return self::GZIP;
case 'application/x-bzip2':
return self::BZIP;
case 'application/x-lzma':
return self::LZMA;
case 'application/x-iso9660-image':
return self::ISO;
case 'application/vnd.ms-cab-compressed':
return self::CAB;
case 'application/x-tar':
return self::TAR;
case 'application/x-gtar':
return self::TAR_GZIP;
}
}
return false;
}
/**
* Opens the file as one of supported formats
*
* @param string $fileName Archive filename
* @param string $type Archive type
* @throws UnsupportedArchiveException If archive can not be opened
*/
public function __construct($fileName, $type)
{
self::checkRequirements();
$this->type = $type;
$this->archiveSize = filesize($fileName);
if (!isset(static::$formatHandlers[$type]))
throw new UnsupportedArchiveException('Unsupported archive type: '.$type.' of archive '.$fileName);
$handler_class = static::$formatHandlers[$type];
$this->archive = new $handler_class($fileName);
$this->scanArchive();
}
/**
* Rescans array after modification
*/
protected function scanArchive()
{
$information = $this->archive->getArchiveInformation();
$this->files = $information->files;
$this->compressedFilesSize = $information->compressedFilesSize;
$this->uncompressedFilesSize = $information->uncompressedFilesSize;
$this->filesQuantity = count($information->files);
}
/**
* Closes archive
*/
public function __destruct()
{
unset($this->archive);
}
/**
* Returns an instance of class implementing PclZipOriginalInterface
* interface.
*
* @return PclzipZipInterface Returns an instance of a class implementing PclZipOriginalInterface
* @throws UnsupportedOperationException
*/
public function getPclZipInterface()
{
return $this->archive->getPclZip();
}
/**
* Counts number of files
*
* @return int
*/
public function countFiles()
{
return $this->filesQuantity;
}
/**
* Counts cumulative size of all uncompressed data (bytes)
*
* @return int
*/
public function countUncompressedFilesSize()
{
return $this->uncompressedFilesSize;
}
/**
* Returns size of archive file in bytes
*
* @return int
*/
public function getArchiveSize()
{
return $this->archiveSize;
}
/**
* Returns type of archive
*
* @return string One of class constants
*/
public function getArchiveType()
{
return $this->type;
}
/**
* Counts cumulative size of all compressed data (in bytes)
*
* @return int
*/
public function countCompressedFilesSize()
{
return $this->compressedFilesSize;
}
/**
* Returns list of files, excluding folders.
*
* Paths is present in unix-style (with forward slash - /).
*
* @return array List of files
*/
public function getFileNames()
{
return array_values($this->files);
}
/**
* Checks that file exists in archive
*
* @param string $fileName File name in archive
* @return bool
*/
public function isFileExists($fileName)
{
return in_array($fileName, $this->files, true);
}
/**
* Returns file metadata of file in archive
*
* @param string $fileName File name in archive
* @return ArchiveEntry
* @throws NonExistentArchiveFileException
*/
public function getFileData($fileName)
{
if (!in_array($fileName, $this->files, true))
throw new NonExistentArchiveFileException('File '.$fileName.' does not exist in archive');
return $this->archive->getFileData($fileName);
}
/**
* Returns full file content as string
*
* @param string $fileName File name in archive
* @return string
* @throws NonExistentArchiveFileException
*/
public function getFileContent($fileName)
{
if (!in_array($fileName, $this->files, true))
throw new NonExistentArchiveFileException('File '.$fileName.' does not exist in archive');
return $this->archive->getFileContent($fileName);
}
/**
* Returns a resource for reading file from archive
*
* @param string $fileName File name in archive
* @return resource
* @throws NonExistentArchiveFileException
*/
public function getFileResource($fileName)
{
if (!in_array($fileName, $this->files, true))
throw new NonExistentArchiveFileException('File '.$fileName.' does not exist in archive');
return $this->archive->getFileResource($fileName);
}
/**
* Unpacks files to disk
*
* @param string $outputFolder Extraction output dir
* @param string|array|null $files One file or files list or null to extract all content.
* @param bool $expandFilesList Whether paths like 'src/' should be expanded to all files inside 'src/' dir or not.
* @return int Number of extracted files
* @throws EmptyFileListException
* @throws ArchiveExtractionException
*/
public function extractFiles($outputFolder, $files = null, $expandFilesList = false)
{
if ($files !== null) {
if (is_string($files)) $files = [$files];
if ($expandFilesList)
$files = self::expandFileList($this->files, $files);
if (empty($files))
throw new EmptyFileListException('Files list is empty!');
return $this->archive->extractFiles($outputFolder, $files);
} else {
return $this->archive->extractArchive($outputFolder);
}
}
/**
* Updates existing archive by removing files from it
*
* Only 7zip and zip types support deletion.
* @param string|string[] $fileOrFiles
* @param bool $expandFilesList
*
* @return bool|int
* @throws EmptyFileListException
* @throws UnsupportedOperationException
* @throws ArchiveModificationException
*/
public function deleteFiles($fileOrFiles, $expandFilesList = false)
{
$fileOrFiles = is_string($fileOrFiles) ? [$fileOrFiles] : $fileOrFiles;
if ($expandFilesList && $fileOrFiles !== null)
$fileOrFiles = self::expandFileList($this->files, $fileOrFiles);
if (empty($fileOrFiles))
throw new EmptyFileListException('Files list is empty!');
$result = $this->archive->deleteFiles($fileOrFiles);
$this->scanArchive();
return $result;
}
/**
* Updates existing archive by adding new files
*
* @param string[] $fileOrFiles See [[archiveFiles]] method for file list format.
* @return int|bool Number of added files
* @throws ArchiveModificationException
* @throws EmptyFileListException
* @throws UnsupportedOperationException
*/
public function addFiles($fileOrFiles)
{
$files_list = self::createFilesList($fileOrFiles);
if (empty($files_list))
throw new EmptyFileListException('Files list is empty!');
$result = $this->archive->addFiles($files_list);
$this->scanArchive();
return $result;
}
/**
* Adds file into archive
*
* @param string $file File name to be added
* @param string|null $inArchiveName If not passed, full path will be preserved.
* @return bool
* @throws ArchiveModificationException
* @throws EmptyFileListException
* @throws UnsupportedOperationException
*/
public function addFile($file, $inArchiveName = null)
{
if (!is_file($file))
throw new InvalidArgumentException($file.' is not a valid file to add in archive');
return ($inArchiveName !== null
? $this->addFiles([$file => $inArchiveName])
: $this->addFiles([$file])) === 1;
}
/**
* Adds directory contents to archive
*
* @param string $directory
* @param string|null $inArchivePath If not passed, full paths will be preserved.
* @return bool
* @throws ArchiveModificationException
* @throws EmptyFileListException
* @throws UnsupportedOperationException
*/
public function addDirectory($directory, $inArchivePath = null)
{
if (!is_dir($directory) || !is_readable($directory))
throw new InvalidArgumentException($directory.' is not a valid directory to add in archive');
return ($inArchivePath !== null
? $this->addFiles([$directory => $inArchivePath])
: $this->addFiles([$inArchivePath])) > 0;
}
/**
* Prepare files list for archiving
*
* @param string $fileOrFiles File of list of files. See [[archiveFiles]] for details.
* @param string $archiveName File name of archive. See [[archiveFiles]] for details.
* @return array An array containing entries:
* - totalSize (int) - size in bytes for all files
* - numberOfFiles (int) - quantity of files
* - files (array) - list of files prepared for archiving
* - type (string) - prepared format for archive. One of class constants
* @throws EmptyFileListException
* @throws UnsupportedArchiveException
*/
public static function prepareForArchiving($fileOrFiles, $archiveName)
{
$archiveType = self::detectArchiveType($archiveName, false);
if ($archiveType === false)
throw new UnsupportedArchiveException('Could not detect archive type for name "'.$archiveName.'"');
$files_list = self::createFilesList($fileOrFiles);
if (empty($files_list))
throw new EmptyFileListException('Files list is empty!');
$totalSize = 0;
foreach ($files_list as $fn) {
$totalSize += filesize($fn);
}
return [
'totalSize' => $totalSize,
'numberOfFiles' => count($files_list),
'files' => $files_list,
'type' => $archiveType,
];
}
/**
* Creates an archive with passed files list
*
* @param string|string[]|array<string,string> $fileOrFiles List of files. Can be one of three formats:
* 1. A string containing path to file or directory.
* File will have it's basename.
* `UnifiedArchive::archiveFiles('/etc/php.ini', 'archive.zip)` will store
* file with 'php.ini' name.
* Directory contents will be stored in archive root.
* `UnifiedArchive::archiveFiles('/var/log/', 'archive.zip')` will store all
* directory contents in archive root.
* 2. An array with strings containing pats to files or directories.
* Files and directories will be stored with full paths.
* `UnifiedArchive::archiveFiles(['/etc/php.ini', '/var/log/'], 'archive.zip)`
* will preserve full paths.
* 3. An array with strings where keys are strings.
* Files will have name from key.
* Directories contents will have prefix from key.
* `UnifiedArchive::archiveFiles(['doc.txt' => 'very_long_name_of_document.txt',
* 'static' => '/var/www/html/static/'], 'archive.zip')`
*
* @param string $archiveName File name of archive. Type of archive will be determined by it's name.
* @return int Count of stored files is returned.
* @throws EmptyFileListException
* @throws FileAlreadyExistsException
* @throws UnsupportedOperationException
* @throws ArchiveCreationException
*/
public static function archiveFiles($fileOrFiles, $archiveName)
{
if (file_exists($archiveName))
throw new FileAlreadyExistsException('Archive '.$archiveName.' already exists!');
self::checkRequirements();
$info = static::prepareForArchiving($fileOrFiles, $archiveName);
if (!isset(static::$formatHandlers[$info['type']]))
throw new UnsupportedArchiveException('Unsupported archive type: '.$info['type'].' of archive '.$archiveName);
/** @var BasicFormat $handler_class */
$handler_class = static::$formatHandlers[$info['type']];
return $handler_class::createArchive($info['files'], $archiveName);
}
/**
* Creates an archive with one file
*
* @param string $file
* @param string $archiveName
* @return bool
* @throws EmptyFileListException
* @throws FileAlreadyExistsException
* @throws UnsupportedOperationException
* @throws ArchiveCreationException
*/
public static function archiveFile($file, $archiveName)
{
if (!is_file($file)) {
throw new InvalidArgumentException($file . ' is not a valid file to archive');
}
return static::archiveFiles($file, $archiveName) === 1;
}
/**
* Creates an archive with full directory contents
*
* @param string $directory
* @param string $archiveName
* @return bool
* @throws ArchiveCreationException
* @throws EmptyFileListException
* @throws FileAlreadyExistsException
* @throws UnsupportedOperationException
*/
public static function archiveDirectory($directory, $archiveName)
{
if (!is_dir($directory) || !is_readable($directory))
throw new InvalidArgumentException($directory.' is not a valid directory to archive');
return static::archiveFiles($directory, $archiveName) > 0;
}
/**
* Tests system configuration
*/
protected static function checkRequirements()
{
if (empty(self::$enabledTypes)) {
self::$enabledTypes[self::ZIP] = extension_loaded('zip');
self::$enabledTypes[self::SEVEN_ZIP] = class_exists('\Archive7z\Archive7z');
self::$enabledTypes[self::RAR] = extension_loaded('rar');
self::$enabledTypes[self::GZIP] = extension_loaded('zlib');
self::$enabledTypes[self::BZIP] = extension_loaded('bz2');
self::$enabledTypes[self::LZMA] = extension_loaded('xz');
self::$enabledTypes[self::ISO] = class_exists('\CISOFile');
self::$enabledTypes[self::CAB] = class_exists('\CabArchive');
self::$enabledTypes[self::TAR] = class_exists('\Archive_Tar') || class_exists('\PharData');
self::$enabledTypes[self::TAR_GZIP] = (class_exists('\Archive_Tar') || class_exists('\PharData')) && extension_loaded('zlib');
self::$enabledTypes[self::TAR_BZIP] = (class_exists('\Archive_Tar') || class_exists('\PharData')) && extension_loaded('bz2');
self::$enabledTypes[self::TAR_LZMA] = class_exists('\Archive_Tar') && extension_loaded('lzma2');
self::$enabledTypes[self::TAR_LZW] = class_exists('\Archive_Tar') && LzwStreamWrapper::isBinaryAvailable();
}
}
/**
* Expands files list
* @param $archiveFiles
* @param $files
* @return array
*/
protected static function expandFileList($archiveFiles, $files)
{
$newFiles = [];
foreach ($files as $file) {
foreach ($archiveFiles as $archiveFile) {
if (fnmatch($file.'*', $archiveFile))
$newFiles[] = $archiveFile;
}
}
return $newFiles;
}
/**
* @param string|array $nodes
* @return array|bool
*/
protected static function createFilesList($nodes)
{
$files = [];
// passed an extended list
if (is_array($nodes)) {
foreach ($nodes as $source => $destination) {
if (is_numeric($source))
$source = $destination;
$destination = rtrim($destination, '/\\*');
// if is directory
if (is_dir($source))
self::importFilesFromDir(rtrim($source, '/\\*').'/*',
!empty($destination) ? $destination.'/' : null, true, $files);
else if (is_file($source))
$files[$destination] = $source;
}
} else if (is_string($nodes)) { // passed one file or directory
// if is directory
if (is_dir($nodes))
self::importFilesFromDir(rtrim($nodes, '/\\*').'/*', null, true,
$files);
else if (is_file($nodes))
$files[basename($nodes)] = $nodes;
}
return $files;
}
/**
* @param string $source
* @param string|null $destination
* @param bool $recursive
* @param array $map
*/
protected static function importFilesFromDir($source, $destination, $recursive, &$map)
{
// $map[$destination] = rtrim($source, '/*');
// do not map root archive folder
if ($destination !== null)
$map[$destination] = null;
foreach (glob($source, GLOB_MARK) as $node) {
if (in_array(substr($node, -1), ['/', '\\'], true) && $recursive) {
self::importFilesFromDir(str_replace('\\', '/', $node).'*',
$destination.basename($node).'/', $recursive, $map);
} elseif (is_file($node) && is_readable($node)) {
$map[$destination.basename($node)] = $node;
}
}
}
/**
* @return bool
*/
public function canAddFiles()
{
return call_user_func([static::$formatHandlers[$this->type], 'canAddFiles']);
}
/**
* @return bool
*/
public function canDeleteFiles()
{
return call_user_func([static::$formatHandlers[$this->type], 'canDeleteFiles']);
}
}