* @link http://www.netcommons.org NetCommons Project * @license http://www.netcommons.org/license.txt NetCommons License */ App::uses('File', 'Utility'); App::uses('TemporaryFolder', 'Files.Utility'); App::uses('TemporaryFile', 'Files.Utility'); App::uses('NetCommonsFile', 'Files.Utility'); /** * Class NetCommonsZip * 基本的な利用シーンはZipしてダウンロードするテンポラリな使い方 */ class ZipDownloader { /** * @var TemporaryFolder 作業用フォルダ */ protected $_tmpFolder; /** * @var string|null password */ protected $_password = null; /** * @var string zip filepath */ public $path; /** * @var bool ファイルオープン常態 */ protected $_open = false; /** * @var string Zipコマンド */ protected $_zipCommand = 'zip'; /** * @var string クライアントOSの文字コード */ protected $_clientOsEncoding; /** * ZipDownloader constructor. */ public function __construct() { $this->_tmpFolder = new TemporaryFolder(); $this->_open = true; $userAgent = Hash::get($_SERVER, 'HTTP_USER_AGENT'); if (stristr($userAgent, 'Mac')) { // Macの場合 $this->_clientOsEncoding = 'UTF-8'; } elseif (stristr($userAgent, 'Windows')) { // Windowsの場合 $this->_clientOsEncoding = 'SJIS-win'; } else { $this->_clientOsEncoding = Configure::read('App.encoding'); } } /** * close * * zipの作成 * * @return bool */ public function close() { // zip作成先 $zipSaveFolder = new TemporaryFolder(); $this->path = $zipSaveFolder->path . DS . Security::hash(mt_rand() . microtime(), 'md5') . '.zip'; if (strlen($this->_password)) { // パスワードを使う $cmd = $this->_zipCommand; $execCmd = sprintf( '%s -r -e -P %s %s %s', $cmd, escapeshellarg($this->_password), escapeshellarg($this->path), '*' ); chdir($this->_tmpFolder->path); // コマンドを実行する //exec(($execCmd)); exec($execCmd, $output, $returnVar); //CakeLog::debug($execCmd); if ($returnVar > 0) { CakeLog::debug(' Error:output=' . json_encode($output) . ', return_var=' . $returnVar); return false; } } else { // パスワード無しZIP // ZipArchiverを使う $zip = new ZipArchive(); $zip->open($this->path, ZipArchive::CREATE); chdir($this->_tmpFolder->path); list($folders, $files) = $this->_tmpFolder->tree(); foreach ($folders as $folder) { if ($folder !== $this->_tmpFolder->path) { $relativePath = str_replace($this->_tmpFolder->path . DS, '', $folder); $zip->addEmptyDir($this->_convertFilename($relativePath)); } } foreach ($files as $file) { $relativePath = str_replace($this->_tmpFolder->path . DS, '', $file); $zip->addFile($relativePath, $this->_convertFilename($relativePath)); } $zip->close(); } $this->_open = false; } /** * ファイル追加 * * @param string $filePath 追加するファイルのパス * @param string|null $localName ZIPに追加するときのファイル名 * * @return void * @throws InternalErrorException */ public function addFile($filePath, $localName = null) { // ファイルをlocalNameにしてコピー if ($localName === null) { $localName = NetCommonsFile::basename($filePath); } $destPath = $this->_tmpFolder->path . DS . $localName; $result = copy($filePath, $this->_convertFilename($destPath)); if (!$result) { // 失敗 throw new InternalErrorException('NetCommonsZip File IO Error'); } } /** * add from string * * @param string $localname zipファイルに追加するときのファイル名 * @param string $contents 追加するファイルの中身 * * @return void */ public function addFromString($localname, $contents) { // ε(     v ゚ω゚) < 例外処理 $file = new File($this->_tmpFolder->path . DS . $localname, true); $file->write($contents); $file->close(); } /** * add folder * * @param string $folderPath zipに追加するフォルダのパス * * @return void * @throws InternalErrorException */ public function addFolder($folderPath) { $folder = new Folder($folderPath); if (!$folder->copy($this->_tmpFolder->path . DS . NetCommonsFile::basename($folder->path))) { throw new InternalErrorException('NetCommonsZip File IO Error'); } } /** * set password * * @param string $password ZIPの解凍・圧縮に使うパスワード * * @return void */ public function setPassword($password) { $this->_password = $password; } /** * Download * * @param string $filename download時のファイル名 * @return CakeResponse ダウンロードレスポンス */ public function download($filename) { // closeされてなかったらcloseする if ($this->_open) { $this->close(); } $response = new CakeResponse(); $response->type('application/zip'); $response->file($this->path, ['name' => $filename, 'download' => 'true']); return $response; } /** * OSのファイルシステムにあわせて文字コード変換を行う * * @param string $name ファイル名 * @return string */ protected function _convertFilename($name) { // Mac上でNC3をつかってるケースの対策 // Macファイルシステムでは濁点文字が2つの文字になるNFDなのをNFCに変換する if (class_exists('Normalizer')) { if (Normalizer::isNormalized($name, Normalizer::FORM_D)) { $name = Normalizer::normalize($name, Normalizer::FORM_C); } } $name = mb_convert_encoding($name, $this->_clientOsEncoding, 'auto'); return $name; } }