Initial commit
This commit is contained in:
193
application/Espo/Core/Utils/File/ClassMap.php
Normal file
193
application/Espo/Core/Utils/File/ClassMap.php
Normal file
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\File;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\DataCache;
|
||||
use Espo\Core\Utils\File\Manager as FileManager;
|
||||
use Espo\Core\Utils\Module;
|
||||
use Espo\Core\Utils\Module\PathProvider;
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
use ReflectionClass;
|
||||
|
||||
class ClassMap
|
||||
{
|
||||
public function __construct(
|
||||
private FileManager $fileManager,
|
||||
private Module $module,
|
||||
private DataCache $dataCache,
|
||||
private PathProvider $pathProvider,
|
||||
private Config\SystemConfig $systemConfig,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Return paths to class files.
|
||||
*
|
||||
* @param ?string[] $allowedMethods If specified, classes w/o specified method will be ignored.
|
||||
* @return array<string, class-string>
|
||||
*/
|
||||
public function getData(
|
||||
string $path,
|
||||
?string $cacheKey = null,
|
||||
?array $allowedMethods = null,
|
||||
bool $subDirs = false
|
||||
): array {
|
||||
|
||||
$data = null;
|
||||
|
||||
if (
|
||||
$cacheKey &&
|
||||
$this->dataCache->has($cacheKey) &&
|
||||
$this->systemConfig->useCache()
|
||||
) {
|
||||
/** @var array<string,class-string> $data */
|
||||
$data = $this->dataCache->get($cacheKey);
|
||||
}
|
||||
|
||||
if (is_array($data)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$data = $this->getClassNameHash(
|
||||
$this->pathProvider->getCore() . $path,
|
||||
$allowedMethods,
|
||||
$subDirs
|
||||
);
|
||||
|
||||
foreach ($this->module->getOrderedList() as $moduleName) {
|
||||
$data = array_merge(
|
||||
$data,
|
||||
$this->getClassNameHash(
|
||||
$this->pathProvider->getModule($moduleName) . $path,
|
||||
$allowedMethods,
|
||||
$subDirs
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$data = array_merge(
|
||||
$data,
|
||||
$this->getClassNameHash(
|
||||
$this->pathProvider->getCustom() . $path,
|
||||
$allowedMethods,
|
||||
$subDirs
|
||||
)
|
||||
);
|
||||
|
||||
if ($cacheKey && $this->systemConfig->useCache()) {
|
||||
$this->dataCache->store($cacheKey, $data);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[]|string $dirs
|
||||
* @param ?string[] $allowedMethods
|
||||
* @return array<string,class-string>
|
||||
*/
|
||||
private function getClassNameHash($dirs, ?array $allowedMethods = [], bool $subDirs = false): array
|
||||
{
|
||||
if (is_string($dirs)) {
|
||||
$dirs = (array) $dirs;
|
||||
}
|
||||
|
||||
$data = [];
|
||||
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($dir)) {
|
||||
/** @var string[] $fileList */
|
||||
$fileList = $this->fileManager->getFileList($dir, $subDirs, '\.php$', true);
|
||||
|
||||
$this->fillHashFromFileList($fileList, $dir, $allowedMethods, $data);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $fileList
|
||||
* @param ?string[] $allowedMethods
|
||||
* @param array<string,class-string> $data
|
||||
*/
|
||||
private function fillHashFromFileList(
|
||||
array $fileList,
|
||||
string $dir,
|
||||
?array $allowedMethods,
|
||||
array &$data,
|
||||
string $category = ''
|
||||
): void {
|
||||
|
||||
foreach ($fileList as $key => $file) {
|
||||
if (is_string($key)) {
|
||||
if (is_array($file)) {
|
||||
$this->fillHashFromFileList(
|
||||
$file,
|
||||
$dir . '/'. $key,
|
||||
$allowedMethods,
|
||||
$data,
|
||||
$category . $key . '\\'
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$filePath = Util::concatPath($dir, $file);
|
||||
$className = Util::getClassName($filePath);
|
||||
|
||||
$fileName = $this->fileManager->getFileName($filePath);
|
||||
|
||||
$class = new ReflectionClass($className);
|
||||
|
||||
if (!$class->isInstantiable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = Util::normalizeScopeName(ucfirst($fileName));
|
||||
|
||||
$name = $category . $name;
|
||||
|
||||
if (empty($allowedMethods)) {
|
||||
$data[$name] = $className;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($allowedMethods as $methodName) {
|
||||
if (method_exists($className, $methodName)) {
|
||||
$data[$name] = $className;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
application/Espo/Core/Utils/File/Exceptions/FileError.php
Normal file
33
application/Espo/Core/Utils/File/Exceptions/FileError.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\File\Exceptions;
|
||||
|
||||
class FileError extends \RuntimeException
|
||||
{}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\File\Exceptions;
|
||||
|
||||
class PermissionError extends FileError
|
||||
{}
|
||||
1163
application/Espo/Core/Utils/File/Manager.php
Normal file
1163
application/Espo/Core/Utils/File/Manager.php
Normal file
File diff suppressed because it is too large
Load Diff
70
application/Espo/Core/Utils/File/MimeType.php
Normal file
70
application/Espo/Core/Utils/File/MimeType.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\File;
|
||||
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
class MimeType
|
||||
{
|
||||
public function __construct(private Metadata $metadata)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getMimeTypeListByExtension(string $extension): array
|
||||
{
|
||||
$extensionLowerCase = strtolower($extension);
|
||||
|
||||
/** @var string[] */
|
||||
return $this->metadata
|
||||
->get(['app', 'file', 'extensionMimeTypeMap', $extensionLowerCase]) ?? [];
|
||||
}
|
||||
|
||||
public function getMimeTypeByExtension(string $extension): ?string
|
||||
{
|
||||
$typeList = $this->getMimeTypeListByExtension($extension);
|
||||
|
||||
return $typeList[0] ?? null;
|
||||
}
|
||||
|
||||
public static function matchMimeTypeToAcceptToken(string $mimeType, string $token): bool
|
||||
{
|
||||
if ($mimeType === $token) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($token, ['audio/*', 'video/*', 'image/*'])) {
|
||||
return strpos($mimeType, substr($token, 0, -2)) === 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
661
application/Espo/Core/Utils/File/Permission.php
Normal file
661
application/Espo/Core/Utils/File/Permission.php
Normal file
@@ -0,0 +1,661 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\File;
|
||||
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class Permission
|
||||
{
|
||||
|
||||
/**
|
||||
* Last permission error.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $permissionError = [];
|
||||
|
||||
/**
|
||||
* @var ?array<string, mixed>
|
||||
*/
|
||||
protected $permissionErrorRules = null;
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, mixed>>
|
||||
*/
|
||||
protected $writableMap = [
|
||||
'data' => [
|
||||
'recursive' => true,
|
||||
],
|
||||
'client/custom' => [
|
||||
'recursive' => true,
|
||||
],
|
||||
'custom/Espo/Custom' => [
|
||||
'recursive' => true,
|
||||
],
|
||||
'custom/Espo/Modules' => [
|
||||
'recursive' => true,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array{
|
||||
* dir: string|int|null,
|
||||
* file: string|int|null,
|
||||
* user: string|int|null,
|
||||
* group: string|int|null,
|
||||
* }
|
||||
*/
|
||||
protected $defaultPermissions = [
|
||||
'dir' => '0755',
|
||||
'file' => '0644',
|
||||
'user' => null,
|
||||
'group' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array{
|
||||
* file: string|int|null,
|
||||
* dir: string|int|null,
|
||||
* }
|
||||
*/
|
||||
protected $writablePermissions = [
|
||||
'file' => '0664',
|
||||
'dir' => '0775',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param ?array<string, mixed> $params
|
||||
*/
|
||||
public function __construct(private Manager $fileManager, ?array $params = null)
|
||||
{
|
||||
if ($params) {
|
||||
foreach ($params as $paramName => $paramValue) {
|
||||
switch ($paramName) {
|
||||
case 'defaultPermissions':
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->defaultPermissions = array_merge($this->defaultPermissions, $paramValue);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get default settings.
|
||||
*
|
||||
* @return array{
|
||||
* dir: string|int|null,
|
||||
* file: string|int|null,
|
||||
* user: string|int|null,
|
||||
* group: string|int|null,
|
||||
* }
|
||||
*/
|
||||
public function getDefaultPermissions(): array
|
||||
{
|
||||
return $this->defaultPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
public function getWritableMap(): array
|
||||
{
|
||||
return $this->writableMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getWritableList(): array
|
||||
{
|
||||
return array_keys($this->writableMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* dir: string|int|null,
|
||||
* file: string|int|null,
|
||||
* user: string|int|null,
|
||||
* group: string|int|null,
|
||||
* }
|
||||
*/
|
||||
public function getRequiredPermissions(string $path): array
|
||||
{
|
||||
$permission = $this->getDefaultPermissions();
|
||||
|
||||
foreach ($this->getWritableMap() as $writablePath => $writableOptions) {
|
||||
if (!$writableOptions['recursive'] && $path == $writablePath) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
return array_merge($permission, $this->writablePermissions);
|
||||
}
|
||||
|
||||
if ($writableOptions['recursive'] && str_starts_with($path, $writablePath)) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
return array_merge($permission, $this->writablePermissions);
|
||||
}
|
||||
}
|
||||
|
||||
return $permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default permission.
|
||||
*/
|
||||
public function setDefaultPermissions(string $path, bool $recurse = false): bool
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$permission = $this->getRequiredPermissions($path);
|
||||
|
||||
$result = $this->chmod($path, [$permission['file'], $permission['dir']], $recurse);
|
||||
|
||||
if (!empty($permission['user'])) {
|
||||
$result &= $this->chown($path, $permission['user'], $recurse);
|
||||
}
|
||||
|
||||
if (!empty($permission['group'])) {
|
||||
$result &= $this->chgrp($path, $permission['group'], $recurse);
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current permissions.
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
public function getCurrentPermission(string $filePath)
|
||||
{
|
||||
if (!file_exists($filePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var array{mode: mixed} $fileInfo */
|
||||
$fileInfo = stat($filePath);
|
||||
|
||||
return substr(base_convert((string) $fileInfo['mode'], 10, 8), -4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change permissions.
|
||||
*
|
||||
* @param string $path
|
||||
* @param int|array<int|string, string|int|null>|string $octal Ex. `0755`, `[0644, 0755]`, `['file' => 0644, 'dir' => 0755]`.
|
||||
* @param bool $recurse
|
||||
*/
|
||||
public function chmod(string $path, $octal, bool $recurse = false): bool
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @phpstan-var mixed $octal */
|
||||
|
||||
$permission = [];
|
||||
|
||||
if (is_array($octal)) {
|
||||
$count = 0;
|
||||
|
||||
$rule = ['file', 'dir'];
|
||||
|
||||
foreach ($octal as $key => $val) {
|
||||
$pKey = strval($key);
|
||||
|
||||
if (!in_array($pKey, $rule)) {
|
||||
$pKey = $rule[$count];
|
||||
}
|
||||
|
||||
if (!empty($pKey)) {
|
||||
$permission[$pKey]= $val;
|
||||
}
|
||||
|
||||
$count++;
|
||||
}
|
||||
} else if (
|
||||
/** @phpstan-ignore-next-line */
|
||||
is_int((int) $octal) // Always true. @todo Fix.
|
||||
) {
|
||||
$permission = [
|
||||
'file' => $octal,
|
||||
'dir' => $octal,
|
||||
];
|
||||
}
|
||||
|
||||
// Convert to octal value.
|
||||
foreach ($permission as $key => $val) {
|
||||
if (is_string($val)) {
|
||||
$permission[$key] = base_convert($val, 8, 10);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$recurse) {
|
||||
if (is_dir($path)) {
|
||||
return $this->chmodReal($path, $permission['dir']);
|
||||
}
|
||||
|
||||
return $this->chmodReal($path, $permission['file']);
|
||||
}
|
||||
|
||||
return $this->chmodRecurse($path, $permission['file'], $permission['dir']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change permissions recursive.
|
||||
*
|
||||
* @param int $fileOctal Ex. 0644.
|
||||
* @param int $dirOctal Ex. 0755.
|
||||
*/
|
||||
protected function chmodRecurse(string $path, $fileOctal = 0644, $dirOctal = 0755): bool
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_dir($path)) {
|
||||
return $this->chmodReal($path, $fileOctal);
|
||||
}
|
||||
|
||||
$result = $this->chmodReal($path, $dirOctal);
|
||||
|
||||
/** @var string[] $allFiles */
|
||||
$allFiles = $this->fileManager->getFileList($path);
|
||||
|
||||
foreach ($allFiles as $item) {
|
||||
$result &= $this->chmodRecurse($path . Util::getSeparator() . $item, $fileOctal, $dirOctal);
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change owner permission.
|
||||
*
|
||||
* @param int|string $user
|
||||
*/
|
||||
public function chown(string $path, $user = '', bool $recurse = false): bool
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($user)) {
|
||||
$user = $this->getDefaultOwner();
|
||||
}
|
||||
|
||||
if ($user === false) {
|
||||
// @todo Revise.
|
||||
$user = '';
|
||||
}
|
||||
|
||||
if (!$recurse) {
|
||||
return $this->chownReal($path, $user);
|
||||
}
|
||||
|
||||
return $this->chownRecurse($path, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change owner permission recursive.
|
||||
*
|
||||
* @param int|string $user
|
||||
*/
|
||||
protected function chownRecurse(string $path, $user): bool
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_dir($path)) {
|
||||
return $this->chownReal($path, $user);
|
||||
}
|
||||
|
||||
$result = $this->chownReal($path, $user);
|
||||
|
||||
/** @var string[] $allFiles */
|
||||
$allFiles = $this->fileManager->getFileList($path);
|
||||
|
||||
foreach ($allFiles as $item) {
|
||||
$result &= $this->chownRecurse($path . Util::getSeparator() . $item, $user);
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change group permission.
|
||||
*
|
||||
* @param int|string $group
|
||||
* @noinspection SpellCheckingInspection
|
||||
*/
|
||||
public function chgrp(string $path, $group = null, bool $recurse = false): bool
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($group)) {
|
||||
$group = $this->getDefaultGroup();
|
||||
}
|
||||
|
||||
if ($group === false) {
|
||||
// @todo Revise.
|
||||
$group = '';
|
||||
}
|
||||
|
||||
if (!$recurse) {
|
||||
return $this->chgrpReal($path, $group);
|
||||
}
|
||||
|
||||
return $this->chgrpRecurse($path, $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change group permission recursive.
|
||||
*
|
||||
* @param int|string $group
|
||||
* @noinspection SpellCheckingInspection
|
||||
*/
|
||||
protected function chgrpRecurse(string $path, $group): bool
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_dir($path)) {
|
||||
return $this->chgrpReal($path, $group);
|
||||
}
|
||||
|
||||
$result = $this->chgrpReal($path, $group);
|
||||
|
||||
/** @var string[] $allFiles */
|
||||
$allFiles = $this->fileManager->getFileList($path);
|
||||
|
||||
foreach ($allFiles as $item) {
|
||||
$result &= $this->chgrpRecurse($path . Util::getSeparator() . $item, $group);
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $mode
|
||||
*/
|
||||
protected function chmodReal(string $filename, $mode): bool
|
||||
{
|
||||
$result = @chmod($filename, $mode);
|
||||
|
||||
if ($result) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$defaultOwner = $this->getDefaultOwner(true);
|
||||
$defaultGroup = $this->getDefaultGroup(true);
|
||||
|
||||
if ($defaultOwner === false) {
|
||||
// @todo Revise.
|
||||
$defaultOwner = '';
|
||||
}
|
||||
|
||||
if ($defaultGroup === false) {
|
||||
// @todo Revise.
|
||||
$defaultGroup = '';
|
||||
}
|
||||
|
||||
$this->chown($filename, $defaultOwner);
|
||||
$this->chgrp($filename, $defaultGroup);
|
||||
|
||||
return @chmod($filename, $mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|string $user
|
||||
*/
|
||||
protected function chownReal(string $path, $user): bool
|
||||
{
|
||||
if (!function_exists('chown')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return @chown($path, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|string $group
|
||||
* @noinspection SpellCheckingInspection
|
||||
* @todo Revise the need of exception handling.
|
||||
*
|
||||
*/
|
||||
protected function chgrpReal(string $path, $group): bool
|
||||
{
|
||||
if (!function_exists('chgrp')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return @chgrp($path, $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default owner user.
|
||||
*
|
||||
* @return string|int|false owner id.
|
||||
*/
|
||||
public function getDefaultOwner(bool $usePosix = false)
|
||||
{
|
||||
$defaultPermissions = $this->getDefaultPermissions();
|
||||
|
||||
$owner = $defaultPermissions['user'];
|
||||
|
||||
if (empty($owner) && $usePosix) {
|
||||
$owner = function_exists('posix_getuid') ? posix_getuid() : null;
|
||||
}
|
||||
|
||||
if (empty($owner)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default group user.
|
||||
*
|
||||
* @return string|int|false Group id.
|
||||
*/
|
||||
public function getDefaultGroup(bool $usePosix = false)
|
||||
{
|
||||
$defaultPermissions = $this->getDefaultPermissions();
|
||||
|
||||
$group = $defaultPermissions['group'];
|
||||
|
||||
if (empty($group) && $usePosix) {
|
||||
$group = function_exists('posix_getegid') ? posix_getegid() : null;
|
||||
}
|
||||
|
||||
if (empty($group)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set permission regarding defined in permissionMap.
|
||||
*/
|
||||
public function setMapPermission(): bool
|
||||
{
|
||||
$this->permissionError = [];
|
||||
$this->permissionErrorRules = [];
|
||||
|
||||
$result = true;
|
||||
|
||||
foreach ($this->getWritableMap() as $path => $options) {
|
||||
if (!file_exists($path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->chmod($path, $this->writablePermissions, $options['recursive']);
|
||||
} catch (Throwable) {}
|
||||
|
||||
/** check is writable */
|
||||
$res = is_writable($path);
|
||||
|
||||
if (is_dir($path)) {
|
||||
try {
|
||||
$name = uniqid();
|
||||
|
||||
$res &= $this->fileManager->putContents($path . '/' . $name, 'test');
|
||||
|
||||
$res &= $this->fileManager->removeFile($name, $path);
|
||||
} catch (Throwable) {
|
||||
$res = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$res) {
|
||||
$result = false;
|
||||
|
||||
$this->permissionError[] = $path;
|
||||
$this->permissionErrorRules[$path] = $this->writablePermissions;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last permission error.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getLastError()
|
||||
{
|
||||
return $this->permissionError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last permission error rules.
|
||||
*
|
||||
* @return ?array<string, array<string, string>>
|
||||
*/
|
||||
public function getLastErrorRules()
|
||||
{
|
||||
return $this->permissionErrorRules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arrange permission file list.
|
||||
*
|
||||
* e.g.
|
||||
* ```
|
||||
* [
|
||||
* 'application/Espo/Controllers/Email.php',
|
||||
* 'application/Espo/Controllers/Import.php',
|
||||
* ]
|
||||
* ```
|
||||
* result will be `['application/Espo/Controllers']`.
|
||||
*
|
||||
* @param string[] $fileList
|
||||
* @return string[]
|
||||
*/
|
||||
public function arrangePermissionList(array $fileList): array
|
||||
{
|
||||
$betterList = [];
|
||||
|
||||
foreach ($fileList as $fileName) {
|
||||
$pathInfo = pathinfo($fileName);
|
||||
/** @var string $dirname */
|
||||
$dirname = $pathInfo['dirname'] ?? null;
|
||||
|
||||
$currentPath = $fileName;
|
||||
|
||||
if ($this->getSearchCount($dirname, $fileList) > 1) {
|
||||
$currentPath = $dirname;
|
||||
}
|
||||
|
||||
if (!$this->itemIncludes($currentPath, $betterList)) {
|
||||
$betterList[] = $currentPath;
|
||||
}
|
||||
}
|
||||
|
||||
return $betterList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of a search string in an array.
|
||||
*
|
||||
* @param string $search
|
||||
* @param string[] $array
|
||||
* @return int
|
||||
*/
|
||||
protected function getSearchCount(string $search, array $array)
|
||||
{
|
||||
$searchQuoted = $this->getPregQuote($search);
|
||||
|
||||
$number = 0;
|
||||
|
||||
foreach ($array as $value) {
|
||||
if (preg_match('/^' . $searchQuoted . '/', $value)) {
|
||||
$number++;
|
||||
}
|
||||
}
|
||||
|
||||
return $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $array
|
||||
*/
|
||||
protected function itemIncludes(string $item, array $array): bool
|
||||
{
|
||||
foreach ($array as $value) {
|
||||
$value = $this->getPregQuote($value);
|
||||
|
||||
if (preg_match('/^' . $value . '/', $item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getPregQuote(string $string): string
|
||||
{
|
||||
return preg_quote($string, '/-+=.');
|
||||
}
|
||||
}
|
||||
316
application/Espo/Core/Utils/File/Unifier.php
Normal file
316
application/Espo/Core/Utils/File/Unifier.php
Normal file
@@ -0,0 +1,316 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\File;
|
||||
|
||||
use Espo\Core\Utils\DataUtil;
|
||||
use Espo\Core\Utils\File\Manager as FileManager;
|
||||
use Espo\Core\Utils\Json;
|
||||
use Espo\Core\Utils\Module;
|
||||
use Espo\Core\Utils\Resource\PathProvider;
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
use JsonException;
|
||||
use LogicException;
|
||||
use stdClass;
|
||||
|
||||
class Unifier
|
||||
{
|
||||
protected bool $useObjects = false;
|
||||
private string $unsetFileName = 'unset.json';
|
||||
private const APPEND_VALUE = '__APPEND__';
|
||||
private const ANY_KEY = '__ANY__';
|
||||
|
||||
public function __construct(
|
||||
private FileManager $fileManager,
|
||||
private Module $module,
|
||||
private PathProvider $pathProvider
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Merge data of resource files.
|
||||
*
|
||||
* @param array<int, string[]> $forceAppendPathList
|
||||
* @return array<string, mixed>|stdClass
|
||||
*/
|
||||
public function unify(string $path, bool $noCustom = false, array $forceAppendPathList = [])
|
||||
{
|
||||
if ($this->useObjects) {
|
||||
return $this->unifyObject($path, $noCustom, $forceAppendPathList);
|
||||
}
|
||||
|
||||
return $this->unifyArray($path, $noCustom);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function unifyArray(string $path, bool $noCustom = false)
|
||||
{
|
||||
/** @var array<string, mixed> $data */
|
||||
$data = $this->unifySingle($this->pathProvider->getCore() . $path, true);
|
||||
|
||||
foreach ($this->getModuleList() as $moduleName) {
|
||||
$filePath = $this->pathProvider->getModule($moduleName) . $path;
|
||||
|
||||
/** @var array<string, mixed> $newData */
|
||||
$newData = $this->unifySingle($filePath, true);
|
||||
|
||||
/** @var array<string, mixed> $data */
|
||||
$data = Util::merge($data, $newData);
|
||||
}
|
||||
|
||||
if ($noCustom) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$customFilePath = $this->pathProvider->getCustom() . $path;
|
||||
|
||||
/** @var array<string, mixed> $newData */
|
||||
$newData = $this->unifySingle($customFilePath, true);
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
return Util::merge($data, $newData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string[]> $forceAppendPathList
|
||||
* @return stdClass
|
||||
*/
|
||||
private function unifyObject(string $path, bool $noCustom = false, array $forceAppendPathList = [])
|
||||
{
|
||||
/** @var stdClass $data */
|
||||
$data = $this->unifySingle($this->pathProvider->getCore() . $path, true);
|
||||
|
||||
foreach ($this->getModuleList() as $moduleName) {
|
||||
$filePath = $this->pathProvider->getModule($moduleName) . $path;
|
||||
|
||||
/** @var stdClass $itemData */
|
||||
$itemData = $this->unifySingle($filePath, true);
|
||||
|
||||
$this->prepareItemDataObject($itemData, $forceAppendPathList);
|
||||
|
||||
/** @var stdClass $data */
|
||||
$data = DataUtil::merge($data, $itemData);
|
||||
}
|
||||
|
||||
if ($noCustom) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$customFilePath = $this->pathProvider->getCustom() . $path;
|
||||
|
||||
/** @var stdClass $itemData */
|
||||
$itemData = $this->unifySingle($customFilePath, true);
|
||||
|
||||
$this->prepareItemDataObject($itemData, $forceAppendPathList);
|
||||
|
||||
/** @var stdClass */
|
||||
return DataUtil::merge($data, $itemData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|stdClass
|
||||
*/
|
||||
private function unifySingle(string $dirPath, bool $recursively)
|
||||
{
|
||||
$data = [];
|
||||
$unsets = [];
|
||||
|
||||
if ($this->useObjects) {
|
||||
$data = (object) [];
|
||||
}
|
||||
|
||||
if (empty($dirPath) || !$this->fileManager->exists($dirPath)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$fileList = $this->fileManager->getFileList($dirPath, $recursively, '\.json$');
|
||||
|
||||
//$dirName = $this->fileManager->getDirName($dirPath, false);
|
||||
|
||||
foreach ($fileList as $dirName => $item) {
|
||||
if (is_array($item)) {
|
||||
/** @var string $dirName */
|
||||
// Only a first level of a subdirectory.
|
||||
$itemValue = $this->unifySingle(
|
||||
Util::concatPath($dirPath, $dirName),
|
||||
false
|
||||
);
|
||||
|
||||
if ($this->useObjects) {
|
||||
/** @var stdClass $data */
|
||||
|
||||
$data->$dirName = $itemValue;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> $data */
|
||||
|
||||
$data[$dirName] = $itemValue;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var string $item */
|
||||
|
||||
$fileName = $item;
|
||||
|
||||
if ($fileName === $this->unsetFileName) {
|
||||
$fileContent = $this->fileManager->getContents($dirPath . '/' . $fileName);
|
||||
|
||||
$unsets = Json::decode($fileContent, true);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$itemValue = $this->getContents($dirPath . '/' . $fileName);
|
||||
|
||||
if (empty($itemValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $this->fileManager->getFileName($fileName, '.json');
|
||||
|
||||
if ($this->useObjects) {
|
||||
/** @var stdClass $data */
|
||||
|
||||
$data->$name = $itemValue;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> $data */
|
||||
|
||||
$data[$name] = $itemValue;
|
||||
}
|
||||
|
||||
if ($this->useObjects) {
|
||||
/** @var stdClass $data */
|
||||
|
||||
/** @var stdClass */
|
||||
return DataUtil::unsetByKey($data, $unsets);
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> $data */
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
return Util::unsetInArray($data, $unsets);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return stdClass|array<string, mixed>
|
||||
* @throws JsonException
|
||||
*/
|
||||
private function getContents(string $path)
|
||||
{
|
||||
$fileContent = $this->fileManager->getContents($path);
|
||||
|
||||
try {
|
||||
return Json::decode($fileContent, !$this->useObjects);
|
||||
} catch (JsonException) {
|
||||
throw new JsonException("JSON syntax error in '$path'.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getModuleList(): array
|
||||
{
|
||||
return $this->module->getOrderedList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string[]> $forceAppendPathList
|
||||
*/
|
||||
private function prepareItemDataObject(stdClass $data, array $forceAppendPathList): void
|
||||
{
|
||||
foreach ($forceAppendPathList as $path) {
|
||||
$this->addAppendToData($data, $path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $path
|
||||
*/
|
||||
private function addAppendToData(stdClass $data, array $path): void
|
||||
{
|
||||
if (count($path) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$nextPath = array_slice($path, 1);
|
||||
|
||||
$key = $path[0];
|
||||
|
||||
if ($key === self::ANY_KEY) {
|
||||
foreach (array_keys(get_object_vars($data)) as $itemKey) {
|
||||
$this->addAppendToDataItem($data, $itemKey, $nextPath);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addAppendToDataItem($data, $key, $nextPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $path
|
||||
*/
|
||||
private function addAppendToDataItem(stdClass $data, string $key, array $path): void
|
||||
{
|
||||
$item = $data->$key ?? null;
|
||||
|
||||
if (count($path) === 0) {
|
||||
if ($item === null) {
|
||||
$item = [];
|
||||
}
|
||||
|
||||
if (!is_array($item)) {
|
||||
throw new LogicException("Expected array in metadata, but non-array is set.");
|
||||
}
|
||||
|
||||
if (($item[0] ?? null) === self::APPEND_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data->$key = array_merge([self::APPEND_VALUE], $item);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$item instanceof stdClass) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addAppendToData($item, $path);
|
||||
}
|
||||
}
|
||||
35
application/Espo/Core/Utils/File/UnifierObj.php
Normal file
35
application/Espo/Core/Utils/File/UnifierObj.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\File;
|
||||
|
||||
class UnifierObj extends Unifier
|
||||
{
|
||||
protected bool $useObjects = true;
|
||||
}
|
||||
75
application/Espo/Core/Utils/File/ZipArchive.php
Normal file
75
application/Espo/Core/Utils/File/ZipArchive.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\File;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ZipArchive
|
||||
{
|
||||
private Manager $fileManager;
|
||||
|
||||
public function __construct(?Manager $fileManager = null)
|
||||
{
|
||||
if ($fileManager === null) {
|
||||
$fileManager = new Manager();
|
||||
}
|
||||
|
||||
$this->fileManager = $fileManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unzip archive.
|
||||
*
|
||||
* @param string $file Path to .zip file.
|
||||
* @param string $destinationPath Destination path.
|
||||
* @return bool
|
||||
*/
|
||||
public function unzip($file, $destinationPath)
|
||||
{
|
||||
if (!class_exists('\ZipArchive')) {
|
||||
throw new RuntimeException("php-zip extension is not installed. Cannot unzip the file.");
|
||||
}
|
||||
|
||||
$zip = new \ZipArchive;
|
||||
|
||||
$res = $zip->open($file);
|
||||
|
||||
if ($res === true) {
|
||||
$this->fileManager->mkdir($destinationPath);
|
||||
|
||||
$zip->extractTo($destinationPath);
|
||||
$zip->close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user