first commit
This commit is contained in:
108
catch/src/Support/Composer.php
Normal file
108
catch/src/Support/Composer.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace Catch\Support;
|
||||
|
||||
use Illuminate\Support\Composer as LaravelComposer;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Laravel\SerializableClosure\Exceptions\PhpVersionNotSupportedException;
|
||||
|
||||
class Composer extends LaravelComposer
|
||||
{
|
||||
protected bool $ignorePlatformReqs = false;
|
||||
|
||||
|
||||
/**
|
||||
* require package
|
||||
* @param string $package
|
||||
* @return string
|
||||
* @throws PhpVersionNotSupportedException
|
||||
*/
|
||||
public function require(string $package): string
|
||||
{
|
||||
$this->checkPHPVersion();
|
||||
|
||||
$command = ['require', $package];
|
||||
|
||||
return $this->runCommand($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* require dev-package
|
||||
*
|
||||
* @param string $package
|
||||
* @return string
|
||||
* @throws PhpVersionNotSupportedException
|
||||
*/
|
||||
public function requireDev(string $package): string
|
||||
{
|
||||
$this->checkPHPVersion();
|
||||
|
||||
$command = ['require', '--dev', $package];
|
||||
|
||||
return $this->runCommand($command);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* remove
|
||||
*
|
||||
* @param string $package
|
||||
*/
|
||||
public function remove(string $package)
|
||||
{
|
||||
$this->runCommand([
|
||||
'remove', $package
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $command
|
||||
* @return string
|
||||
*/
|
||||
protected function runCommand(array $command): string
|
||||
{
|
||||
$command = array_merge($this->findComposer(), $command);
|
||||
|
||||
if ($this->ignorePlatformReqs) {
|
||||
$command[] = '--ignore-platform-reqs';
|
||||
}
|
||||
|
||||
$process = $this->getProcess($command);
|
||||
|
||||
$process->run();
|
||||
|
||||
return $process->getOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @throws PhpVersionNotSupportedException
|
||||
* @return void
|
||||
*/
|
||||
protected function checkPHPVersion(): void
|
||||
{
|
||||
$composerJson = json_decode(File::get(base_path().DIRECTORY_SEPARATOR.'composer.json'), true);
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
|
||||
$needPHPVersion = Str::of($composerJson['require']['php'])->remove('^');
|
||||
|
||||
if (version_compare($phpVersion, $needPHPVersion, '<') && ! $this->ignorePlatformReqs) {
|
||||
throw new PhpVersionNotSupportedException("PHP $phpVersion 版本太低, 需要 PHP {$needPHPVersion}!如果想忽略版本要求, s可使用 {ignorePlatFormReqs} 方法然后安装");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function ignorePlatFormReqs(): static
|
||||
{
|
||||
$this->ignorePlatformReqs = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
55
catch/src/Support/DB/Query.php
Normal file
55
catch/src/Support/DB/Query.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Catch\Support\DB;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class Query
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected static string|null $log = null;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function listen(): void
|
||||
{
|
||||
DB::listen(function ($query) {
|
||||
$sql = str_replace(
|
||||
'?',
|
||||
'%s',
|
||||
sprintf('[%s] '.$query->sql.' | %s ms'.PHP_EOL, date('Y-m-d H:i'), $query->time)
|
||||
);
|
||||
|
||||
static::$log .= vsprintf($sql, $query->bindings);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function log(): void
|
||||
{
|
||||
if (static::$log) {
|
||||
$sqlLogPath = storage_path('logs'.DIRECTORY_SEPARATOR.'query'.DIRECTORY_SEPARATOR);
|
||||
|
||||
if (! File::isDirectory($sqlLogPath)) {
|
||||
File::makeDirectory($sqlLogPath, 0777, true);
|
||||
}
|
||||
|
||||
$logFile = $sqlLogPath.date('Ymd').'.log';
|
||||
|
||||
if (! File::exists($logFile)) {
|
||||
File::put($logFile, '', true);
|
||||
}
|
||||
|
||||
file_put_contents($logFile, static::$log.PHP_EOL, LOCK_EX | FILE_APPEND);
|
||||
|
||||
static::$log = null;
|
||||
}
|
||||
}
|
||||
}
|
19
catch/src/Support/DB/SoftDelete.php
Normal file
19
catch/src/Support/DB/SoftDelete.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Catch\Support\DB;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
|
||||
class SoftDelete extends SoftDeletingScope
|
||||
{
|
||||
/**
|
||||
* @param Builder $builder
|
||||
* @param Model $model
|
||||
*/
|
||||
public function apply(Builder $builder, Model $model)
|
||||
{
|
||||
$builder->where($model->getQualifiedDeletedAtColumn(), '=', 0);
|
||||
}
|
||||
}
|
140
catch/src/Support/Macros/Blueprint.php
Normal file
140
catch/src/Support/Macros/Blueprint.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Catch\Support\Macros;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint as LaravelBlueprint;
|
||||
|
||||
class Blueprint
|
||||
{
|
||||
/**
|
||||
* boot;
|
||||
*/
|
||||
public static function boot(): void
|
||||
{
|
||||
$bluePrint = new static();
|
||||
|
||||
$bluePrint->createdAt();
|
||||
|
||||
$bluePrint->updatedAt();
|
||||
|
||||
$bluePrint->deletedAt();
|
||||
|
||||
$bluePrint->status();
|
||||
|
||||
$bluePrint->creatorId();
|
||||
|
||||
$bluePrint->unixTimestamp();
|
||||
|
||||
$bluePrint->parentId();
|
||||
|
||||
$bluePrint->sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* created unix timestamp
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function createdAt(): void
|
||||
{
|
||||
LaravelBlueprint::macro(__FUNCTION__, function () {
|
||||
$this->unsignedInteger('created_at')->default(0)->comment('created time');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* update unix timestamp
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updatedAt(): void
|
||||
{
|
||||
LaravelBlueprint::macro(__FUNCTION__, function () {
|
||||
$this->unsignedInteger('updated_at')->default(0)->comment('updated time');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* soft delete
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deletedAt(): void
|
||||
{
|
||||
LaravelBlueprint::macro(__FUNCTION__, function () {
|
||||
$this->unsignedInteger('deleted_at')->default(0)->comment('delete time');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* unix timestamp
|
||||
*
|
||||
* @param bool $softDeleted
|
||||
* @return void
|
||||
*/
|
||||
public function unixTimestamp(bool $softDeleted = true): void
|
||||
{
|
||||
LaravelBlueprint::macro(__FUNCTION__, function () use ($softDeleted) {
|
||||
$this->createdAt();
|
||||
$this->updatedAt();
|
||||
|
||||
if ($softDeleted) {
|
||||
$this->deletedAt();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* creator id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function creatorId(): void
|
||||
{
|
||||
LaravelBlueprint::macro(__FUNCTION__, function () {
|
||||
$this->unsignedInteger('creator_id')->default(0)->comment('creator id');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* parent ID
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function parentId(): void
|
||||
{
|
||||
LaravelBlueprint::macro(__FUNCTION__, function () {
|
||||
$this->unsignedInteger('parent_id')->default(0)->comment('parent id');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* status
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function status(): void
|
||||
{
|
||||
LaravelBlueprint::macro(__FUNCTION__, function ($default = 1) {
|
||||
$this->tinyInteger('status')->default($default)->comment('1:normal 2: forbidden');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* sort
|
||||
*
|
||||
* @param int $default
|
||||
* @return void
|
||||
*/
|
||||
public function sort(int $default = 1): void
|
||||
{
|
||||
LaravelBlueprint::macro(__FUNCTION__, function () use ($default) {
|
||||
$this->integer('sort')->comment('sort')->default($default);
|
||||
});
|
||||
}
|
||||
}
|
107
catch/src/Support/Macros/Builder.php
Normal file
107
catch/src/Support/Macros/Builder.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Catch\Support\Macros;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Database\Eloquent\Builder as LaravelBuilder;
|
||||
|
||||
class Builder
|
||||
{
|
||||
/**
|
||||
* boot
|
||||
*/
|
||||
public static function boot(): void
|
||||
{
|
||||
$builder = new static();
|
||||
|
||||
$builder->whereLike();
|
||||
|
||||
$builder->quickSearch();
|
||||
|
||||
$builder->tree();
|
||||
}
|
||||
|
||||
/**
|
||||
* where like
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function whereLike(): void
|
||||
{
|
||||
LaravelBuilder::macro(__FUNCTION__, function ($filed, $value) {
|
||||
return $this->where($filed, 'like', "%$value%");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* quick search
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function quickSearch(): void
|
||||
{
|
||||
LaravelBuilder::macro(__FUNCTION__, function (array $params = []) {
|
||||
$params = array_merge(request()->all(), $params);
|
||||
|
||||
if (! property_exists($this->model, 'searchable')) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// filter null & empty string
|
||||
$params = array_filter($params, function ($value) {
|
||||
return (is_string($value) && strlen($value)) || is_numeric($value);
|
||||
});
|
||||
|
||||
$wheres = [];
|
||||
|
||||
if (! empty($this->model->searchable)) {
|
||||
foreach ($this->model->searchable as $field => $op) {
|
||||
// 临时变量
|
||||
$_field = $field;
|
||||
// contains alias
|
||||
if (str_contains($field, '.')) {
|
||||
[, $_field] = explode('.', $field);
|
||||
}
|
||||
|
||||
if (isset($params[$_field])) {
|
||||
$opString = Str::of($op)->lower();
|
||||
if ($opString->exactly('op')) {
|
||||
$value = implode(',', $params[$_field]);
|
||||
} elseif ($opString->exactly('like')) {
|
||||
$value = "%{$params[$_field]}%";
|
||||
} elseif ($opString->exactly('rlike')) {
|
||||
$value = "{$params[$_field]}%";
|
||||
} elseif ($opString->exactly('llike')) {
|
||||
$value = "%{$params[$_field]}";
|
||||
} else {
|
||||
$value = $params[$_field];
|
||||
}
|
||||
$wheres[] = [$field, $op, $value];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->where($wheres);
|
||||
|
||||
return $this;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* where like
|
||||
*
|
||||
* @time 2021年08月06日
|
||||
* @return void
|
||||
*/
|
||||
public function tree(): void
|
||||
{
|
||||
LaravelBuilder::macro(__FUNCTION__, function (string $id, string $parentId, ...$fields) {
|
||||
$fields = array_merge([$id, $parentId], $fields);
|
||||
|
||||
return $this->get($fields)->toTree(0, $parentId);
|
||||
});
|
||||
}
|
||||
}
|
65
catch/src/Support/Macros/Collection.php
Normal file
65
catch/src/Support/Macros/Collection.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Catch\Support\Macros;
|
||||
|
||||
use Catch\Support\Tree;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Support\Collection as LaravelCollection;
|
||||
|
||||
class Collection
|
||||
{
|
||||
/**
|
||||
* boot
|
||||
*/
|
||||
public static function boot(): void
|
||||
{
|
||||
$collection = new static();
|
||||
|
||||
$collection->toOptions();
|
||||
|
||||
$collection->toTree();
|
||||
}
|
||||
|
||||
/**
|
||||
* collection to tree
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function toTree(): void
|
||||
{
|
||||
LaravelCollection::macro(__FUNCTION__, function (int $pid = 0, string $pidField = 'parent_id', string $child = 'children') {
|
||||
return Tree::done($this->all(), $pid, $pidField, $child);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* toOptions
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function toOptions(): void
|
||||
{
|
||||
LaravelCollection::macro(__FUNCTION__, function () {
|
||||
return $this->transform(function ($item, $key) use (&$options) {
|
||||
if ($item instanceof Arrayable) {
|
||||
$item = $item->toArray();
|
||||
}
|
||||
|
||||
if (is_array($item)) {
|
||||
$item = array_values($item);
|
||||
return [
|
||||
'value' => $item[0],
|
||||
'label' => $item[1]
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'value' => $key,
|
||||
'label' => $item
|
||||
];
|
||||
}
|
||||
})->values();
|
||||
});
|
||||
}
|
||||
}
|
23
catch/src/Support/Macros/Register.php
Normal file
23
catch/src/Support/Macros/Register.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Catch\Support\Macros;
|
||||
|
||||
/**
|
||||
* boot
|
||||
*/
|
||||
class Register
|
||||
{
|
||||
/**
|
||||
* macros boot
|
||||
*/
|
||||
public static function boot(): void
|
||||
{
|
||||
Blueprint::boot();
|
||||
|
||||
Collection::boot();
|
||||
|
||||
Builder::boot();
|
||||
}
|
||||
}
|
171
catch/src/Support/Module/Driver/DatabaseDriver.php
Normal file
171
catch/src/Support/Module/Driver/DatabaseDriver.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | CatchAdmin [Just Like ~ ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2017~2021 https://catchadmin.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( https://github.com/JaguarJack/catchadmin-laravel/blob/master/LICENSE.md )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: JaguarJack [ njphper@gmail.com ]
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Catch\Support\Module\Driver;
|
||||
|
||||
use Catch\CatchAdmin;
|
||||
use Catch\Contracts\ModuleRepositoryInterface;
|
||||
use Catch\Enums\Status;
|
||||
use Catch\Exceptions\FailedException;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* DatabaseDriver
|
||||
*/
|
||||
class DatabaseDriver implements ModuleRepositoryInterface
|
||||
{
|
||||
protected Model $model;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->model = $this->createModuleModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* all
|
||||
*
|
||||
* @param array $search
|
||||
* @return Collection
|
||||
*/
|
||||
public function all(array $search): Collection
|
||||
{
|
||||
return $this->model::query()
|
||||
->when($search['name'] ?? false, function ($query) use ($search) {
|
||||
$query->where('name', 'like', '%'.$search['name'].'%');
|
||||
})->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* create module json
|
||||
*
|
||||
* @param array $module
|
||||
* @return bool|int
|
||||
*/
|
||||
public function create(array $module): bool|int
|
||||
{
|
||||
$this->hasSameModule($module);
|
||||
|
||||
return $this->model->save([
|
||||
'name' => $module['name'],
|
||||
'path' => $module['path'],
|
||||
'description' => $module['desc'],
|
||||
'keywords' => $module['keywords'],
|
||||
'service' => sprintf('\\%s%s', CatchAdmin::getModuleNamespace($module['name']), ucfirst($module['name']).'ServiceProvider'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* module info
|
||||
*
|
||||
* @param string $name
|
||||
* @return Collection
|
||||
*/
|
||||
public function show(string $name): Collection
|
||||
{
|
||||
return $this->model->where('name', $name)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* update module json
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $module
|
||||
* @return bool|int
|
||||
*/
|
||||
public function update(string $name, array $module): bool|int
|
||||
{
|
||||
return $this->model->where('name', $name)
|
||||
|
||||
->update([
|
||||
'name' => $module['name'],
|
||||
'alias' => $module['alias'],
|
||||
'description' => $module['desc'],
|
||||
'keywords' => $module['keywords'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* delete module json
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool|int
|
||||
*/
|
||||
public function delete(string $name): bool|int
|
||||
{
|
||||
return $this->model->where('name', $name)->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* disable or enable
|
||||
*
|
||||
* @param $name
|
||||
* @return bool|int
|
||||
*/
|
||||
public function disOrEnable($name): bool|int
|
||||
{
|
||||
$module = $this->show($name);
|
||||
|
||||
$module->status = (int) $module->status;
|
||||
|
||||
return $module->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* get enabled
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getEnabled(): Collection
|
||||
{
|
||||
// TODO: Implement getEnabled() method.
|
||||
return $this->model->where('status', Status::Enable->value())->get();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $module
|
||||
* @return void
|
||||
*/
|
||||
protected function hasSameModule(array $module): void
|
||||
{
|
||||
if ($this->model->where('name', $module['name'])->first()) {
|
||||
throw new FailedException(sprintf('Module [%s] has been created', $module['name']));
|
||||
}
|
||||
|
||||
if ($this->model->where('alias', $module['alias'])->first()) {
|
||||
throw new FailedException(sprintf('Module Alias [%s] has been exised', $module['alias']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create model
|
||||
* @return Model
|
||||
*/
|
||||
protected function createModuleModel(): Model
|
||||
{
|
||||
return new class () extends Model {
|
||||
protected $table;
|
||||
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
parent::__construct($attributes);
|
||||
|
||||
$this->table = Container::getInstance()->make('config')->get('catch.module.driver.table_name');
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
193
catch/src/Support/Module/Driver/FileDriver.php
Normal file
193
catch/src/Support/Module/Driver/FileDriver.php
Normal file
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | CatchAdmin [Just Like ~ ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2017~2021 https://catchadmin.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( https://github.com/JaguarJack/catchadmin-laravel/blob/master/LICENSE.md )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: JaguarJack [ njphper@gmail.com ]
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Catch\Support\Module\Driver;
|
||||
|
||||
use Catch\CatchAdmin;
|
||||
use Catch\Contracts\ModuleRepositoryInterface;
|
||||
use Catch\Exceptions\FailedException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* FileDriver
|
||||
*/
|
||||
class FileDriver implements ModuleRepositoryInterface
|
||||
{
|
||||
protected string $moduleJson;
|
||||
|
||||
/**
|
||||
* construct
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->moduleJson = storage_path('app').DIRECTORY_SEPARATOR.'modules.json';
|
||||
}
|
||||
|
||||
/**
|
||||
* all
|
||||
*
|
||||
* @param array $search
|
||||
* @return Collection
|
||||
*/
|
||||
public function all(array $search = []): Collection
|
||||
{
|
||||
if (! File::exists($this->moduleJson)) {
|
||||
return Collection::make([]);
|
||||
}
|
||||
|
||||
if (! Str::length(File::get($this->moduleJson))) {
|
||||
return Collection::make([]);
|
||||
}
|
||||
|
||||
$modules = Collection::make(\json_decode(File::get($this->moduleJson), true))->values();
|
||||
|
||||
$name = $search['name'] ?? '';
|
||||
|
||||
if (! $name) {
|
||||
return $modules;
|
||||
}
|
||||
|
||||
return $modules->filter(function ($module) use ($name) {
|
||||
return Str::of($module['name'])->contains($name);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* create module json
|
||||
*
|
||||
* @param array $module
|
||||
* @return bool
|
||||
*/
|
||||
public function create(array $module): bool
|
||||
{
|
||||
$modules = $this->all();
|
||||
|
||||
$this->hasSameModule($module, $modules);
|
||||
|
||||
$module['service'] = sprintf('\\%s', CatchAdmin::getModuleServiceProvider($module['path']));
|
||||
$module['version'] = '1.0.0';
|
||||
$module['enable'] = true;
|
||||
|
||||
File::put($this->moduleJson, $modules->push($module)->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* module info
|
||||
*
|
||||
* @param string $name
|
||||
* @return Collection
|
||||
*/
|
||||
public function show(string $name): Collection
|
||||
{
|
||||
foreach ($this->all() as $module) {
|
||||
if (Str::of($module['name'])->exactly($name)) {
|
||||
return Collection::make($module);
|
||||
}
|
||||
}
|
||||
|
||||
throw new FailedException("Module [$name] not Found");
|
||||
}
|
||||
|
||||
/**
|
||||
* update module json
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $module
|
||||
* @return bool
|
||||
*/
|
||||
public function update(string $name, array $module): bool
|
||||
{
|
||||
File::put($this->moduleJson, $this->all()->map(function ($m) use ($module, $name) {
|
||||
if (Str::of($name)->exactly($m['name'])) {
|
||||
$m['path'] = $module['path'];
|
||||
$m['name'] = $module['name'];
|
||||
$m['description'] = $module['description'] ?? '';
|
||||
$m['keywords'] = $module['keywords'] ?? '';
|
||||
$m['enable'] = $module['enable'];
|
||||
}
|
||||
return $m;
|
||||
})->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* delete module json
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function delete(string $name): bool
|
||||
{
|
||||
File::put($this->moduleJson, $this->all()->filter(function ($module) use ($name) {
|
||||
if (! Str::of($name)->exactly($module['name'])) {
|
||||
return $module;
|
||||
}
|
||||
})->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* disable or enable
|
||||
*
|
||||
* @param $name
|
||||
* @return bool|int
|
||||
*/
|
||||
public function disOrEnable($name): bool|int
|
||||
{
|
||||
return File::put($this->moduleJson, $this->all()->map(function ($module) use ($name) {
|
||||
if (Str::of($module['name'])->exactly($name)) {
|
||||
$module['enable'] = ! $module['enable'];
|
||||
}
|
||||
return $module;
|
||||
})->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
/**
|
||||
* get enabled
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getEnabled(): Collection
|
||||
{
|
||||
// TODO: Implement getEnabled() method.
|
||||
return $this->all()->where('enable', true)->values();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $module
|
||||
* @param Collection $modules
|
||||
* @return void
|
||||
*/
|
||||
protected function hasSameModule(array $module, Collection $modules): void
|
||||
{
|
||||
if ($modules->count()) {
|
||||
if ($modules->pluck('name')->contains($module['name'])) {
|
||||
throw new FailedException(sprintf('Module [%s] has been created', $module['name']));
|
||||
}
|
||||
|
||||
if ($modules->pluck('path')->contains($module['path'])) {
|
||||
throw new FailedException(sprintf('Module path [%s] has been existed', $module['path']));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
97
catch/src/Support/Module/Installer.php
Normal file
97
catch/src/Support/Module/Installer.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Catch\Support\Module;
|
||||
|
||||
use Catch\Contracts\ModuleRepositoryInterface;
|
||||
use Catch\Support\Composer;
|
||||
|
||||
/**
|
||||
* installer
|
||||
*/
|
||||
abstract class Installer
|
||||
{
|
||||
/**
|
||||
* construct
|
||||
*
|
||||
* @param ModuleRepositoryInterface $moduleRepository
|
||||
*/
|
||||
public function __construct(protected ModuleRepositoryInterface $moduleRepository)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* module info
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function info(): array;
|
||||
|
||||
/**
|
||||
* migration
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function migration(): string;
|
||||
|
||||
/**
|
||||
* seed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function seeder(): string;
|
||||
|
||||
/**
|
||||
* require packages
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function requirePackages(): void;
|
||||
|
||||
|
||||
/**
|
||||
* remove packages
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function removePackages(): void;
|
||||
|
||||
/**
|
||||
* uninstall
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function uninstall(): void
|
||||
{
|
||||
$this->moduleRepository->delete($this->info()['name']);
|
||||
|
||||
|
||||
$this->removePackages();
|
||||
}
|
||||
|
||||
/**
|
||||
* invoke
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __invoke(): void
|
||||
{
|
||||
// TODO: Implement __invoke() method.
|
||||
$this->moduleRepository->create($this->info());
|
||||
|
||||
// migration
|
||||
|
||||
// seed
|
||||
|
||||
$this->requirePackages();
|
||||
}
|
||||
|
||||
/**
|
||||
* composer installer
|
||||
*
|
||||
* @return Composer
|
||||
*/
|
||||
protected function composer(): Composer
|
||||
{
|
||||
return app(Composer::class);
|
||||
}
|
||||
}
|
63
catch/src/Support/Module/ModuleManager.php
Normal file
63
catch/src/Support/Module/ModuleManager.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | CatchAdmin [Just Like ~ ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2017~2021 https://catchadmin.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( https://github.com/JaguarJack/catchadmin-laravel/blob/master/LICENSE.md )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: JaguarJack [ njphper@gmail.com ]
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Catch\Support\Module;
|
||||
|
||||
use Catch\Support\Module\Driver\DatabaseDriver;
|
||||
use Catch\Support\Module\Driver\FileDriver;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Manager;
|
||||
|
||||
class ModuleManager extends Manager
|
||||
{
|
||||
public function __construct(Container|\Closure $container)
|
||||
{
|
||||
if ($container instanceof \Closure) {
|
||||
$container = $container();
|
||||
}
|
||||
|
||||
parent::__construct($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultDriver(): string
|
||||
{
|
||||
// TODO: Implement getDefaultDriver() method.
|
||||
return $this->config->get('catch.module.driver.default');
|
||||
}
|
||||
|
||||
/**
|
||||
* create file driver
|
||||
*
|
||||
* @return FileDriver
|
||||
*/
|
||||
public function createFileDriver(): FileDriver
|
||||
{
|
||||
return new FileDriver();
|
||||
}
|
||||
|
||||
/**
|
||||
* create database driver
|
||||
*
|
||||
* @return DatabaseDriver
|
||||
*/
|
||||
public function createDatabaseDriver(): DatabaseDriver
|
||||
{
|
||||
return new DatabaseDriver();
|
||||
}
|
||||
}
|
144
catch/src/Support/Module/ModuleRepository.php
Normal file
144
catch/src/Support/Module/ModuleRepository.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | CatchAdmin [Just Like ~ ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2017~2021 https://catchadmin.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( https://github.com/JaguarJack/catchadmin-laravel/blob/master/LICENSE.md )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: JaguarJack [ njphper@gmail.com ]
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Catch\Support\Module;
|
||||
|
||||
use Catch\Contracts\ModuleRepositoryInterface;
|
||||
use Catch\Events\Module\Created;
|
||||
use Catch\Events\Module\Creating;
|
||||
use Catch\Events\Module\Deleted;
|
||||
use Catch\Events\Module\Updated;
|
||||
use Catch\Events\Module\Updating;
|
||||
use Exception;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
|
||||
/**
|
||||
* FileDriver
|
||||
*/
|
||||
class ModuleRepository
|
||||
{
|
||||
protected ModuleRepositoryInterface $moduleRepository;
|
||||
|
||||
/**
|
||||
* construct
|
||||
*/
|
||||
public function __construct(ModuleRepositoryInterface $moduleRepository)
|
||||
{
|
||||
$this->moduleRepository = $moduleRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* all
|
||||
*
|
||||
* @param array $search
|
||||
* @return Collection
|
||||
*/
|
||||
public function all(array $search): Collection
|
||||
{
|
||||
return $this->moduleRepository->all($search);
|
||||
}
|
||||
|
||||
/**
|
||||
* create module json
|
||||
*
|
||||
* @param array $module
|
||||
* @return bool
|
||||
*/
|
||||
public function create(array $module): bool
|
||||
{
|
||||
Event::dispatch(new Creating($module));
|
||||
|
||||
$this->moduleRepository->create($module);
|
||||
|
||||
Event::dispatch(new Created($module));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* module info
|
||||
*
|
||||
* @param string $name
|
||||
* @return Collection
|
||||
* @throws Exception
|
||||
*/
|
||||
public function show(string $name): Collection
|
||||
{
|
||||
try {
|
||||
return $this->moduleRepository->show($name);
|
||||
} catch (Exception $e) {
|
||||
throw new $e();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update module json
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $module
|
||||
* @return bool
|
||||
*/
|
||||
public function update(string $name, array $module): bool
|
||||
{
|
||||
Event::dispatch(new Updating($name, $module));
|
||||
|
||||
$this->moduleRepository->update($name, $module);
|
||||
|
||||
Event::dispatch(new Updated($name, $module));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* delete module json
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public function delete(string $name): bool
|
||||
{
|
||||
$module = $this->show($name);
|
||||
|
||||
$this->moduleRepository->delete($name);
|
||||
|
||||
Event::dispatch(new Deleted($module));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* disable or enable
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool|int
|
||||
*/
|
||||
public function disOrEnable(string $name): bool|int
|
||||
{
|
||||
return $this->moduleRepository->disOrEnable($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* get enabled
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getEnabled(): Collection
|
||||
{
|
||||
// TODO: Implement getEnabled() method.
|
||||
return $this->moduleRepository->getEnabled();
|
||||
}
|
||||
}
|
61
catch/src/Support/Tree.php
Normal file
61
catch/src/Support/Tree.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | CatchAdmin [Just Like ~ ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2017~2021 https://catchadmin.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( https://github.com/JaguarJack/catchadmin-laravel/blob/master/LICENSE.md )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: JaguarJack [ njphper@gmail.com ]
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Catch\Support;
|
||||
|
||||
class Tree
|
||||
{
|
||||
protected static string $pk = 'id';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $pk
|
||||
* @return Tree
|
||||
*/
|
||||
public static function setPk(string $pk): Tree
|
||||
{
|
||||
self::$pk = $pk;
|
||||
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* return done
|
||||
*
|
||||
* @param array $items
|
||||
* @param int $pid
|
||||
* @param string $pidField
|
||||
* @param string $child
|
||||
* @return array
|
||||
*/
|
||||
public static function done(array $items, int $pid = 0, string $pidField = 'parent_id', string $child = 'children'): array
|
||||
{
|
||||
$tree = [];
|
||||
|
||||
foreach ($items as $item) {
|
||||
if ($item[$pidField] == $pid) {
|
||||
$children = self::done($items, $item[self::$pk], $pidField, $child);
|
||||
|
||||
if (count($children)) {
|
||||
$item[$child] = $children;
|
||||
}
|
||||
|
||||
$tree[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
}
|
200
catch/src/Support/Zip/ZipRepository.php
Normal file
200
catch/src/Support/Zip/ZipRepository.php
Normal file
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | CatchAdmin [Just Like ~ ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2017~2021 https://catchadmin.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( https://github.com/JaguarJack/catchadmin-laravel/blob/master/LICENSE.md )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: JaguarJack [ njphper@gmail.com ]
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Catch\Support\Zip;
|
||||
|
||||
use Exception;
|
||||
use ZipArchive;
|
||||
|
||||
class ZipRepository
|
||||
{
|
||||
private mixed $archive;
|
||||
|
||||
/**
|
||||
* Construct with a given path
|
||||
*
|
||||
* @param $filePath
|
||||
* @param bool $create
|
||||
* @param $archive
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*
|
||||
*/
|
||||
public function __construct($filePath, bool $create, $archive = null)
|
||||
{
|
||||
//Check if ZipArchive is available
|
||||
if (! class_exists('ZipArchive')) {
|
||||
throw new Exception('Error: Your PHP version is not compiled with zip support');
|
||||
}
|
||||
$this->archive = $archive ? $archive : new ZipArchive();
|
||||
|
||||
$res = $this->archive->open($filePath, ($create ? ZipArchive::CREATE : 0));
|
||||
if ($res !== true) {
|
||||
throw new Exception("Error: Failed to open $filePath! Error: ".$this->getErrorMessage($res));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file to the opened Archive
|
||||
*
|
||||
* @param $pathToFile
|
||||
* @param $pathInArchive
|
||||
*/
|
||||
public function addFile($pathToFile, $pathInArchive): void
|
||||
{
|
||||
$this->archive->addFile($pathToFile, $pathInArchive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an empty directory
|
||||
*
|
||||
* @param $dirName
|
||||
*/
|
||||
public function addEmptyDir($dirName): void
|
||||
{
|
||||
$this->archive->addEmptyDir($dirName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file to the opened Archive using its contents
|
||||
*
|
||||
* @param string $name
|
||||
* @param $content
|
||||
*/
|
||||
public function addFromString(string $name, $content): void
|
||||
{
|
||||
$this->archive->addFromString($name, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a file permanently from the Archive
|
||||
*
|
||||
* @param string $pathInArchive
|
||||
*/
|
||||
public function removeFile(string $pathInArchive): void
|
||||
{
|
||||
$this->archive->deleteName($pathInArchive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content of a file
|
||||
*
|
||||
* @param string $pathInArchive
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFileContent(string $pathInArchive): string
|
||||
{
|
||||
return $this->archive->getFromName($pathInArchive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stream of a file
|
||||
*
|
||||
* @param string $pathInArchive
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getFileStream(string $pathInArchive): bool
|
||||
{
|
||||
return $this->archive->getStream($pathInArchive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will loop over every item in the archive and will execute the callback on them
|
||||
* Will provide the filename for every item
|
||||
*
|
||||
* @param $callback
|
||||
*/
|
||||
public function each($callback): void
|
||||
{
|
||||
for ($i = 0; $i < $this->archive->numFiles; ++$i) {
|
||||
//skip if folder
|
||||
$stats = $this->archive->statIndex($i);
|
||||
if ($stats['size'] === 0 && $stats['crc'] === 0) {
|
||||
continue;
|
||||
}
|
||||
call_user_func_array($callback, [
|
||||
'file' => $this->archive->getNameIndex($i),
|
||||
'stats' => $this->archive->statIndex($i)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the file is in the archive
|
||||
*
|
||||
* @param $fileInArchive
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function fileExists($fileInArchive): bool
|
||||
{
|
||||
return $this->archive->locateName($fileInArchive) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the password to be used for decompressing
|
||||
* function named usePassword for clarity
|
||||
*
|
||||
* @param $password
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function usePassword($password): bool
|
||||
{
|
||||
return $this->archive->setPassword($password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status of the archive as a string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStatus(): string
|
||||
{
|
||||
return $this->archive->getStatusString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the archive and saves it
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
@$this->archive->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* get error message
|
||||
*
|
||||
* @param $resultCode
|
||||
* @return string
|
||||
*/
|
||||
private function getErrorMessage($resultCode): string
|
||||
{
|
||||
return match ($resultCode) {
|
||||
ZipArchive::ER_EXISTS => 'ZipArchive::ER_EXISTS - File already exists.',
|
||||
ZipArchive::ER_INCONS => 'ZipArchive::ER_INCONS - Zip archive inconsistent.',
|
||||
ZipArchive::ER_MEMORY => 'ZipArchive::ER_MEMORY - Malloc failure.',
|
||||
ZipArchive::ER_NOENT => 'ZipArchive::ER_NOENT - No such file.',
|
||||
ZipArchive::ER_NOZIP => 'ZipArchive::ER_NOZIP - Not a zip archive.',
|
||||
ZipArchive::ER_OPEN => 'ZipArchive::ER_OPEN - Can\'t open file.',
|
||||
ZipArchive::ER_READ => 'ZipArchive::ER_READ - Read error.',
|
||||
ZipArchive::ER_SEEK => 'ZipArchive::ER_SEEK - Seek error.',
|
||||
default => "An unknown error [$resultCode] has occurred.",
|
||||
};
|
||||
}
|
||||
}
|
626
catch/src/Support/Zip/Zipper.php
Normal file
626
catch/src/Support/Zip/Zipper.php
Normal file
@@ -0,0 +1,626 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | CatchAdmin [Just Like ~ ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2017~2021 https://catchadmin.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( https://github.com/JaguarJack/catchadmin-laravel/blob/master/LICENSE.md )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: JaguarJack [ njphper@gmail.com ]
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Catch\Support\Zip;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* This Zipper class is a wrapper around the ZipArchive methods with some handy functions
|
||||
*
|
||||
* Class Zipper
|
||||
*
|
||||
*/
|
||||
class Zipper
|
||||
{
|
||||
/**
|
||||
* Constant for extracting
|
||||
*/
|
||||
public const WHITELIST = 1;
|
||||
|
||||
/**
|
||||
* Constant for extracting
|
||||
*/
|
||||
public const BLACKLIST = 2;
|
||||
|
||||
/**
|
||||
* Constant for matching only strictly equal file names
|
||||
*/
|
||||
public const EXACT_MATCH = 4;
|
||||
|
||||
/**
|
||||
* @var string Represents the current location in the archive
|
||||
*/
|
||||
private string $currentFolder = '';
|
||||
|
||||
/**
|
||||
* @var Filesystem Handler to the file system
|
||||
*/
|
||||
private Filesystem $file;
|
||||
|
||||
/**
|
||||
* @var ZipRepository|null Handler to the archive
|
||||
*/
|
||||
private ?ZipRepository $repository;
|
||||
|
||||
/**
|
||||
* @var string The path to the current zip file
|
||||
*/
|
||||
private string $filePath;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Filesystem|null $fs
|
||||
*/
|
||||
public function __construct(Filesystem $fs = null)
|
||||
{
|
||||
$this->file = $fs ? $fs : new Filesystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (is_object($this->repository)) {
|
||||
$this->repository->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new zip Archive if the file does not exists
|
||||
* opens a zip archive if the file exists
|
||||
*
|
||||
* @param $pathToFile string The file to open
|
||||
* @return $this Zipper instance
|
||||
* @throws Exception
|
||||
*/
|
||||
public function make(string $pathToFile): Zipper
|
||||
{
|
||||
$new = $this->createArchiveFile($pathToFile);
|
||||
|
||||
$this->repository = new ZipRepository($pathToFile, $new);
|
||||
|
||||
$this->filePath = $pathToFile;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new zip archive or open an existing one
|
||||
*
|
||||
* @param $pathToFile
|
||||
*
|
||||
* @return $this
|
||||
* @throws Exception
|
||||
*
|
||||
*/
|
||||
public function zip($pathToFile): Zipper
|
||||
{
|
||||
$this->make($pathToFile);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new phar file or open one
|
||||
*
|
||||
* @param $pathToFile
|
||||
*
|
||||
* @return $this
|
||||
* @throws Exception
|
||||
*
|
||||
*/
|
||||
public function phar($pathToFile): Zipper
|
||||
{
|
||||
$this->make($pathToFile, 'phar');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new rar file or open one
|
||||
*
|
||||
* @param $pathToFile
|
||||
*
|
||||
* @return $this
|
||||
* @throws Exception
|
||||
*
|
||||
*/
|
||||
public function rar($pathToFile): Zipper
|
||||
{
|
||||
$this->make($pathToFile, 'rar');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the opened zip archive to the specified location <br/>
|
||||
* you can provide an array of files and folders and define if they should be a white list
|
||||
* or a black list to extract. By default this method compares file names using "string starts with" logic
|
||||
*
|
||||
* @param $path string The path to extract to
|
||||
* @param array $files An array of files
|
||||
* @param int $methodFlags The Method the files should be treated
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function extractTo(string $path, array $files = [], int $methodFlags = self::BLACKLIST): void
|
||||
{
|
||||
if (! $this->file->exists($path) && ! $this->file->makeDirectory($path, 0755, true)) {
|
||||
throw new RuntimeException('Failed to create folder');
|
||||
}
|
||||
|
||||
if ($methodFlags & self::EXACT_MATCH) {
|
||||
$matchingMethod = function ($haystack) use ($files) {
|
||||
return in_array($haystack, $files, true);
|
||||
};
|
||||
} else {
|
||||
$matchingMethod = function ($haystack) use ($files) {
|
||||
return Str::startsWith($haystack, $files);
|
||||
};
|
||||
}
|
||||
|
||||
if ($methodFlags & self::WHITELIST) {
|
||||
$this->extractFilesInternal($path, $matchingMethod);
|
||||
} else {
|
||||
// blacklist - extract files that do not match with $matchingMethod
|
||||
$this->extractFilesInternal($path, function ($filename) use ($matchingMethod) {
|
||||
return ! $matchingMethod($filename);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts matching files/folders from the opened zip archive to the specified location.
|
||||
*
|
||||
* @param string $extractToPath The path to extract to
|
||||
* @param string $regex regular expression used to match files. See @link http://php.net/manual/en/reference.pcre.pattern.syntax.php
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function extractMatchingRegex(string $extractToPath, string $regex): void
|
||||
{
|
||||
if (empty($regex)) {
|
||||
throw new InvalidArgumentException('Missing pass valid regex parameter');
|
||||
}
|
||||
|
||||
$this->extractFilesInternal($extractToPath, function ($filename) use ($regex) {
|
||||
$match = preg_match($regex, $filename);
|
||||
if ($match === 1) {
|
||||
return true;
|
||||
} elseif ($match === false) {
|
||||
//invalid pattern for preg_match raises E_WARNING and returns FALSE
|
||||
//so if you have custom error_handler set to catch and throw E_WARNINGs you never end up here
|
||||
//but if you have not - this will throw exception
|
||||
throw new RuntimeException("regular expression match on '$filename' failed with error. Please check if pattern is valid regular expression.");
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content of a single file if available
|
||||
*
|
||||
* @param $filePath string The full path (including all folders) of the file in the zip
|
||||
*
|
||||
* @return string returns the content or throws an exception
|
||||
* @throws Exception
|
||||
*
|
||||
*/
|
||||
public function getFileContent(string $filePath): string
|
||||
{
|
||||
if ($this->repository->fileExists($filePath) === false) {
|
||||
throw new Exception(sprintf('The file "%s" cannot be found', $filePath));
|
||||
}
|
||||
return $this->repository->getFileContent($filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one or multiple files to the zip.
|
||||
*
|
||||
* @param $pathToAdd array|string An array or string of files and folders to add
|
||||
* @param mixed|null $fileName
|
||||
*
|
||||
* @return $this Zipper instance
|
||||
*/
|
||||
public function add(array|string $pathToAdd, mixed $fileName = null): Zipper
|
||||
{
|
||||
if (is_array($pathToAdd)) {
|
||||
foreach ($pathToAdd as $key => $dir) {
|
||||
if (! is_int($key)) {
|
||||
$this->add($dir, $key);
|
||||
} else {
|
||||
$this->add($dir);
|
||||
}
|
||||
}
|
||||
} elseif ($this->file->isFile($pathToAdd)) {
|
||||
if ($fileName) {
|
||||
$this->addFile($pathToAdd, $fileName);
|
||||
} else {
|
||||
$this->addFile($pathToAdd);
|
||||
}
|
||||
} else {
|
||||
$this->addDir($pathToAdd);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an empty directory
|
||||
*
|
||||
* @param $dirName
|
||||
*
|
||||
* @return Zipper
|
||||
*/
|
||||
public function addEmptyDir($dirName): Zipper
|
||||
{
|
||||
$this->repository->addEmptyDir($dirName);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file to the zip using its contents
|
||||
*
|
||||
* @param $filename string The name of the file to create
|
||||
* @param $content string The file contents
|
||||
*
|
||||
* @return $this Zipper instance
|
||||
*/
|
||||
public function addString(string $filename, string $content): Zipper
|
||||
{
|
||||
$this->addFromString($filename, $content);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status of the zip.
|
||||
*
|
||||
* @return string The status of the internal zip file
|
||||
*/
|
||||
public function getStatus(): string
|
||||
{
|
||||
return $this->repository->getStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a file or array of files and folders from the zip archive
|
||||
*
|
||||
* @param $fileToRemove array|string The path/array to the files in the zip
|
||||
*
|
||||
* @return $this Zipper instance
|
||||
*/
|
||||
public function remove(array|string $fileToRemove): Zipper
|
||||
{
|
||||
if (is_array($fileToRemove)) {
|
||||
$self = $this;
|
||||
$this->repository->each(function ($file) use ($fileToRemove, $self) {
|
||||
if (Str::startsWith($file, $fileToRemove)) {
|
||||
$self->getRepository()->removeFile($file);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$this->repository->removeFile($fileToRemove);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of the current zip file if there is one.
|
||||
*
|
||||
* @return string The path to the file
|
||||
*/
|
||||
public function getFilePath(): string
|
||||
{
|
||||
return $this->filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the password to be used for decompressing
|
||||
*
|
||||
* @param $password
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function usePassword($password): bool
|
||||
{
|
||||
return $this->repository->usePassword($password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the zip file and frees all handles
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
if (null !== $this->repository) {
|
||||
$this->repository->close();
|
||||
}
|
||||
$this->filePath = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal folder to the given path.<br/>
|
||||
* Useful for extracting only a segment of a zip file.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function folder(string $path): Zipper
|
||||
{
|
||||
$this->currentFolder = $path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the internal folder to the root of the zip file.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function home(): Zipper
|
||||
{
|
||||
$this->currentFolder = '';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the archive file
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
if (null !== $this->repository) {
|
||||
$this->repository->close();
|
||||
}
|
||||
|
||||
$this->file->delete($this->filePath);
|
||||
$this->filePath = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of the Archive
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getArchiveType(): string
|
||||
{
|
||||
return get_class($this->repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current internal folder pointer
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCurrentFolderPath(): string
|
||||
{
|
||||
return $this->currentFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a file is present in the archive
|
||||
*
|
||||
* @param $fileInArchive
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function contains($fileInArchive): bool
|
||||
{
|
||||
return $this->repository->fileExists($fileInArchive);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZipRepository
|
||||
*/
|
||||
public function getRepository(): ZipRepository
|
||||
{
|
||||
return $this->repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Filesystem
|
||||
*/
|
||||
public function getFileHandler(): Filesystem
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path to the internal folder
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getInternalPath(): string
|
||||
{
|
||||
return empty($this->currentFolder) ? '' : $this->currentFolder.'/';
|
||||
}
|
||||
|
||||
/**
|
||||
* List all files that are within the archive
|
||||
*
|
||||
* @param string|null $regexFilter regular expression to filter returned files/folders. See @link http://php.net/manual/en/reference.pcre.pattern.syntax.php
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listFiles(string $regexFilter = null): array
|
||||
{
|
||||
$filesList = [];
|
||||
if ($regexFilter) {
|
||||
$filter = function ($file) use (&$filesList, $regexFilter) {
|
||||
// push/pop an error handler here to to make sure no error/exception thrown if $expected is not a regex
|
||||
set_error_handler(function () {
|
||||
});
|
||||
$match = preg_match($regexFilter, $file);
|
||||
restore_error_handler();
|
||||
|
||||
if ($match === 1) {
|
||||
$filesList[] = $file;
|
||||
} elseif ($match === false) {
|
||||
throw new RuntimeException("regular expression match on '$file' failed with error. Please check if pattern is valid regular expression.");
|
||||
}
|
||||
};
|
||||
} else {
|
||||
$filter = function ($file) use (&$filesList) {
|
||||
$filesList[] = $file;
|
||||
};
|
||||
}
|
||||
$this->repository->each($filter);
|
||||
|
||||
return $filesList;
|
||||
}
|
||||
|
||||
private function getCurrentFolderWithTrailingSlash(): string
|
||||
{
|
||||
if (empty($this->currentFolder)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$lastChar = mb_substr($this->currentFolder, -1);
|
||||
if ($lastChar !== '/' || $lastChar !== '\\') {
|
||||
return $this->currentFolder.'/';
|
||||
}
|
||||
|
||||
return $this->currentFolder;
|
||||
}
|
||||
|
||||
//---------------------PRIVATE FUNCTIONS-------------
|
||||
|
||||
/**
|
||||
* @param $pathToZip
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*
|
||||
*/
|
||||
private function createArchiveFile($pathToZip): bool
|
||||
{
|
||||
if (! $this->file->exists($pathToZip)) {
|
||||
$dirname = dirname($pathToZip);
|
||||
if (! $this->file->exists($dirname) && ! $this->file->makeDirectory($dirname, 0755, true)) {
|
||||
throw new RuntimeException('Failed to create folder');
|
||||
} elseif (! $this->file->isWritable($dirname)) {
|
||||
throw new Exception(sprintf('The path "%s" is not writeable', $pathToZip));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $pathToDir
|
||||
*/
|
||||
private function addDir($pathToDir): void
|
||||
{
|
||||
// First go over the files in this directory and add them to the repository.
|
||||
foreach ($this->file->files($pathToDir) as $file) {
|
||||
$this->addFile($pathToDir.'/'.basename($file));
|
||||
}
|
||||
|
||||
// Now let's visit the subdirectories and add them, too.
|
||||
foreach ($this->file->directories($pathToDir) as $dir) {
|
||||
$old_folder = $this->currentFolder;
|
||||
$this->currentFolder = empty($this->currentFolder) ? basename($dir) : $this->currentFolder.'/'.basename($dir);
|
||||
$this->addDir($pathToDir.'/'.basename($dir));
|
||||
$this->currentFolder = $old_folder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the file to the zip
|
||||
*
|
||||
* @param string $pathToAdd
|
||||
* @param string|null $fileName
|
||||
*/
|
||||
private function addFile(string $pathToAdd, string $fileName = null): void
|
||||
{
|
||||
if (! $fileName) {
|
||||
$info = pathinfo($pathToAdd);
|
||||
$fileName = isset($info['extension']) ?
|
||||
$info['filename'].'.'.$info['extension'] :
|
||||
$info['filename'];
|
||||
}
|
||||
|
||||
$this->repository->addFile($pathToAdd, $this->getInternalPath().$fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the file to the zip from content
|
||||
*
|
||||
* @param $filename
|
||||
* @param $content
|
||||
*/
|
||||
private function addFromString($filename, $content): void
|
||||
{
|
||||
$this->repository->addFromString($this->getInternalPath().$filename, $content);
|
||||
}
|
||||
|
||||
private function extractFilesInternal($path, callable $matchingMethod): void
|
||||
{
|
||||
$self = $this;
|
||||
$this->repository->each(function ($fileName) use ($path, $matchingMethod, $self) {
|
||||
$currentPath = $self->getCurrentFolderWithTrailingSlash();
|
||||
if (! empty($currentPath) && ! Str::startsWith($fileName, $currentPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$filename = str_replace($self->getInternalPath(), '', $fileName);
|
||||
if ($matchingMethod($filename)) {
|
||||
$self->extractOneFileInternal($fileName, $path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $fileName
|
||||
* @param $path
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function extractOneFileInternal($fileName, $path): void
|
||||
{
|
||||
$tmpPath = str_replace($this->getInternalPath(), '', $fileName);
|
||||
|
||||
//Prevent Zip traversal attacks
|
||||
if (str_contains($fileName, '../') || str_contains($fileName, '..\\')) {
|
||||
throw new RuntimeException('Special characters found within filenames');
|
||||
}
|
||||
|
||||
// We need to create the directory first in case it doesn't exist
|
||||
$dir = pathinfo($path.DIRECTORY_SEPARATOR.$tmpPath, PATHINFO_DIRNAME);
|
||||
if (! $this->file->exists($dir) && ! $this->file->makeDirectory($dir, 0755, true, true)) {
|
||||
throw new RuntimeException('Failed to create folders');
|
||||
}
|
||||
|
||||
$toPath = $path.DIRECTORY_SEPARATOR.$tmpPath;
|
||||
$fileStream = $this->getRepository()->getFileStream($fileName);
|
||||
$this->getFileHandler()->put($toPath, $fileStream);
|
||||
}
|
||||
}
|
122
catch/src/Support/helpers.php
Normal file
122
catch/src/Support/helpers.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | CatchAdmin [Just Like ~ ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2017~2021 https://catchadmin.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( https://github.com/JaguarJack/catchadmin-laravel/blob/master/LICENSE.md )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: JaguarJack [ njphper@gmail.com ]
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Console\Application as Artisan;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\VarDumper\VarDumper;
|
||||
|
||||
/**
|
||||
* load commands
|
||||
*/
|
||||
if (! function_exists('loadCommands')) {
|
||||
/**
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
function loadCommands($paths, $namespace, $searchPath = null): void
|
||||
{
|
||||
if (! $searchPath) {
|
||||
$searchPath = dirname($paths).DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
$paths = Collection::make(Arr::wrap($paths))->unique()->filter(function ($path) {
|
||||
return is_dir($path);
|
||||
});
|
||||
|
||||
if ($paths->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ((new Finder())->in($paths->toArray())->files() as $command) {
|
||||
$command = $namespace.str_replace(['/', '.php'], ['\\', ''], Str::after($command->getRealPath(), $searchPath));
|
||||
|
||||
if (is_subclass_of($command, Command::class) &&
|
||||
! (new ReflectionClass($command))->isAbstract()) {
|
||||
Artisan::starting(function ($artisan) use ($command) {
|
||||
$artisan->resolve($command);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* table prefix
|
||||
*/
|
||||
if (! function_exists('withTablePrefix')) {
|
||||
function withTablePrefix(string $table): string
|
||||
{
|
||||
return DB::connection()->getTablePrefix().$table;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get guard name
|
||||
*/
|
||||
if (! function_exists('getGuardName')) {
|
||||
function getGuardName(): string
|
||||
{
|
||||
$guardKeys = array_keys(config('catch.auth.guards'));
|
||||
|
||||
if (count($guardKeys)) {
|
||||
return $guardKeys[0];
|
||||
}
|
||||
|
||||
return 'admin';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get table columns
|
||||
*/
|
||||
if (! function_exists('getTableColumns')) {
|
||||
function getTableColumns(string $table): array
|
||||
{
|
||||
$SQL = 'desc '.withTablePrefix($table);
|
||||
|
||||
$columns = [];
|
||||
|
||||
foreach (DB::select($SQL) as $column) {
|
||||
$columns[] = $column->Field;
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('dd_')) {
|
||||
/**
|
||||
* @param mixed ...$vars
|
||||
* @return never
|
||||
*/
|
||||
function dd_(...$vars): never
|
||||
{
|
||||
if (! in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && ! headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: *');
|
||||
header('Access-Control-Allow-Headers: *');
|
||||
|
||||
foreach ($vars as $v) {
|
||||
VarDumper::dump($v);
|
||||
}
|
||||
|
||||
exit(1);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user