fix
This commit is contained in:
@@ -0,0 +1,199 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Wt\Core\App;
|
||||||
|
|
||||||
|
use RecursiveDirectoryIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
use UnexpectedValueException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ModuleCloner
|
||||||
|
*
|
||||||
|
* Обеспечивает функционал клонирования Bitrix модулей с автоматической
|
||||||
|
* заменой идентификаторов и пространств имен.
|
||||||
|
*/
|
||||||
|
class ModuleCloner
|
||||||
|
{
|
||||||
|
protected array $excludePatterns = [
|
||||||
|
'/\.git$/',
|
||||||
|
'/\.module\.json/',
|
||||||
|
'/clone\.php/',
|
||||||
|
'/ModuleCloner\.php/',
|
||||||
|
'/readme\.md/',
|
||||||
|
'/src\/\.env$/',
|
||||||
|
'/src\/config\.php$/',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly ModuleEntity $sourceEntity,
|
||||||
|
private readonly ModuleEntity $targetEntity
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Основной метод клонирования модуля.
|
||||||
|
*
|
||||||
|
* @throws UnexpectedValueException
|
||||||
|
*/
|
||||||
|
public function clone(): void
|
||||||
|
{
|
||||||
|
$this->copyStructure();
|
||||||
|
$this->fixContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Копирует структуру папок и файлов из источника в цель.
|
||||||
|
*/
|
||||||
|
protected function copyStructure(): void
|
||||||
|
{
|
||||||
|
$this->copyFolderSafe(
|
||||||
|
$this->sourceEntity->getPath(),
|
||||||
|
$this->targetEntity->getPath()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Рекурсивное копирование папки с фильтрацией и динамическим переименованием.
|
||||||
|
*/
|
||||||
|
protected function copyFolderSafe(string $source, string $destination): void
|
||||||
|
{
|
||||||
|
if ($this->shouldExclude($source)) {
|
||||||
|
echo "Skip: $source" . PHP_EOL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_dir($source)) {
|
||||||
|
$destination = $this->ensureDirectory($destination);
|
||||||
|
if ($destination === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dir = dir($source);
|
||||||
|
while (false !== ($entry = $dir->read())) {
|
||||||
|
if ($entry !== '.' && $entry !== '..') {
|
||||||
|
$srcPath = $source . DIRECTORY_SEPARATOR . $entry;
|
||||||
|
$dstPath = $destination . DIRECTORY_SEPARATOR . $entry;
|
||||||
|
|
||||||
|
$dstPath = $this->renamePathSegment($dstPath);
|
||||||
|
|
||||||
|
$this->copyFolderSafe($srcPath, $dstPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$dir->close();
|
||||||
|
} else {
|
||||||
|
$this->copyFileSafe($source, $destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Копирует файл с сохранением времени модификации.
|
||||||
|
*/
|
||||||
|
protected function copyFileSafe(string $source, string $destination): void
|
||||||
|
{
|
||||||
|
$time = filemtime($source);
|
||||||
|
if (file_exists($destination) && filemtime($destination) >= $time) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copy($source, $destination)) {
|
||||||
|
touch($destination, $time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создает директорию, если она отсутствует.
|
||||||
|
*/
|
||||||
|
protected function ensureDirectory(string $path): ?string
|
||||||
|
{
|
||||||
|
if (file_exists($path)) {
|
||||||
|
if (is_dir($path)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
unlink($path);
|
||||||
|
}
|
||||||
|
return mkdir($path, 0777, true) ? $path : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выполняет массовую замену строк в PHP файлах целевого модуля.
|
||||||
|
*/
|
||||||
|
protected function fixContent(): void
|
||||||
|
{
|
||||||
|
$iterator = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator($this->targetEntity->getPath()),
|
||||||
|
RecursiveIteratorIterator::LEAVES_ONLY
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($iterator as $file) {
|
||||||
|
if ($file->isFile() && $file->getExtension() === 'php') {
|
||||||
|
$path = $file->getPathname();
|
||||||
|
$content = file_get_contents($path);
|
||||||
|
$original = $content;
|
||||||
|
|
||||||
|
$replacements = $this->getReplacements();
|
||||||
|
foreach ($replacements as $search => $replace) {
|
||||||
|
$content = str_replace($search, $replace, $content);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($original !== $content) {
|
||||||
|
file_put_contents($path, $content);
|
||||||
|
echo "Fixed: $path" . PHP_EOL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Формирует карту замен на основе сущностей модулей.
|
||||||
|
*/
|
||||||
|
protected function getReplacements(): array
|
||||||
|
{
|
||||||
|
$s = $this->sourceEntity;
|
||||||
|
$t = $this->targetEntity;
|
||||||
|
|
||||||
|
$productS = ucfirst($s->getProductCode());
|
||||||
|
$productT = ucfirst($t->getProductCode());
|
||||||
|
$companyS = ucfirst($s->getCompanyCode());
|
||||||
|
$companyT = ucfirst($t->getCompanyCode());
|
||||||
|
|
||||||
|
return [
|
||||||
|
$s->getLangPrefix() => $t->getLangPrefix(),
|
||||||
|
$s->getId() => $t->getId(),
|
||||||
|
$s->getPrefix() => $t->getPrefix(),
|
||||||
|
$s->getNamespace() => $t->getNamespace(),
|
||||||
|
"$productS: $productS" => "$productT: $productT",
|
||||||
|
$productS => $productT,
|
||||||
|
$companyS => $companyT,
|
||||||
|
$s->getProductCode() => $t->getProductCode(),
|
||||||
|
$s->getCompanyCode() => $t->getCompanyCode(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Переименовывает сегменты пути (папки) в соответствии с именами модулей.
|
||||||
|
*/
|
||||||
|
protected function renamePathSegment(string $path): string
|
||||||
|
{
|
||||||
|
$sourceLast = collect(explode('.', $this->sourceEntity->getId()))->last();
|
||||||
|
$targetLast = collect(explode('.', $this->targetEntity->getId()))->last();
|
||||||
|
|
||||||
|
$path = str_replace(ucfirst($sourceLast), ucfirst($targetLast), $path);
|
||||||
|
$path = str_replace(strtolower($sourceLast), strtolower($targetLast), $path);
|
||||||
|
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, должен ли файл/папка быть исключен из копирования.
|
||||||
|
*/
|
||||||
|
protected function shouldExclude(string $path): bool
|
||||||
|
{
|
||||||
|
foreach ($this->excludePatterns as $pattern) {
|
||||||
|
if (preg_match($pattern, $path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,122 +1,36 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Wt\Core\App\ModuleEntity;
|
use Wt\Core\App\ModuleEntity;
|
||||||
|
use Wt\Core\App\ModuleCloner;
|
||||||
|
|
||||||
require './../wt.core/include.php';
|
require './../wt.core/include.php';
|
||||||
|
require './ModuleCloner.php';
|
||||||
|
|
||||||
$curPath = __DIR__;
|
if (!isset($argv[1])) {
|
||||||
$newPath = realpath($argv[1]);
|
echo "Error: Please provide the target module path as the first argument." . PHP_EOL;
|
||||||
$newEntity = __getModuleEntity($newPath);
|
exit(1);
|
||||||
$nullableEntity = __getModuleEntity(__DIR__);
|
|
||||||
|
|
||||||
function copy_folder_safe($d1, $d2, $upd = true, $force = true, $exclude = [], $handler = null) {
|
|
||||||
$cm = 0;
|
|
||||||
preg_filter($exclude, $exclude, $d1, -1, $cm);
|
|
||||||
if($cm){
|
|
||||||
echo 'skip ' . $d1 . PHP_EOL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(is_callable($handler)){
|
|
||||||
call_user_func_array($handler, [$d1, &$d2]);
|
|
||||||
}
|
|
||||||
if ( is_dir( $d1 ) ) {
|
|
||||||
$d2 = mkdir_safe( $d2, $force );
|
|
||||||
if (!$d2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$d = dir( $d1 );
|
|
||||||
while ( false !== ( $entry = $d->read() ) ) {
|
|
||||||
if ( $entry != '.' && $entry != '..' )
|
|
||||||
copy_folder_safe( "$d1/$entry", "$d2/$entry", $upd, $force, $exclude, $handler);
|
|
||||||
}
|
|
||||||
$d->close();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$ok = copy_safe( $d1, $d2, $upd );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mkdir_safe( $dir, $force ) {
|
try {
|
||||||
if (file_exists($dir)) {
|
$sourcePath = __DIR__;
|
||||||
if (is_dir($dir)) return $dir;
|
$targetPath = realpath($argv[1]);
|
||||||
elseif (!$force) return false;
|
|
||||||
unlink($dir);
|
if ($targetPath === false) {
|
||||||
|
throw new InvalidArgumentException("Invalid target path: {$argv[1]}");
|
||||||
}
|
}
|
||||||
return (mkdir($dir, 0777, true)) ? $dir : false;
|
|
||||||
|
$targetEntity = __getModuleEntity($targetPath);
|
||||||
|
$sourceEntity = __getModuleEntity($sourcePath);
|
||||||
|
|
||||||
|
echo "Cloning {$sourceEntity->getId()} -> {$targetEntity->getId()}..." . PHP_EOL;
|
||||||
|
|
||||||
|
$cloner = new ModuleCloner($sourceEntity, $targetEntity);
|
||||||
|
$cloner->clone();
|
||||||
|
|
||||||
|
echo "Success: Module cloned successfully." . PHP_EOL;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
echo "Critical Error: " . $e->getMessage() . PHP_EOL;
|
||||||
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function copy_safe ($f1, $f2, $upd) {
|
|
||||||
$time1 = filemtime($f1);
|
|
||||||
if (file_exists($f2)) {
|
|
||||||
$time2 = filemtime($f2);
|
|
||||||
if ($time2 >= $time1 && $upd) return false;
|
|
||||||
}
|
|
||||||
$ok = copy($f1, $f2);
|
|
||||||
if ($ok) touch($f2, $time1);
|
|
||||||
return $ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fs_log($str) {
|
|
||||||
return;
|
|
||||||
$log = fopen('./fs_log.txt', 'a');
|
|
||||||
$time = date('Y-m-d H:i:s');
|
|
||||||
fwrite($log, "$str ($time)\n");
|
|
||||||
fclose($log);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _fix(ModuleEntity $newEntity, ModuleEntity $nullableEntity){
|
|
||||||
$d = new RecursiveDirectoryIterator( $newEntity->getPath() );
|
|
||||||
foreach( new RecursiveIteratorIterator( $d, 1 ) as $path ){
|
|
||||||
if( is_file( $path ) && str_ends_with($path, '.php')){
|
|
||||||
$orig_file = file_get_contents($path);
|
|
||||||
|
|
||||||
$new_file = $orig_file;
|
|
||||||
$cr = 0;
|
|
||||||
$new_file = str_replace($nullableEntity->getLangPrefix(), $newEntity->getLangPrefix(), $new_file, $cr);
|
|
||||||
$new_file = str_replace($nullableEntity->getId(), $newEntity->getId(), $new_file, $cr);
|
|
||||||
$new_file = str_replace($nullableEntity->getPrefix(), $newEntity->getPrefix(), $new_file, $cr);
|
|
||||||
$new_file = str_replace($nullableEntity->getNamespace(), $newEntity->getNamespace(), $new_file, $cr);
|
|
||||||
|
|
||||||
$new_file = str_replace(
|
|
||||||
ucfirst($nullableEntity->getProductCode()) . ': ' . ucfirst($nullableEntity->getProductCode()),
|
|
||||||
ucfirst($newEntity->getProductCode()) . ': ' . ucfirst($newEntity->getProductCode()),
|
|
||||||
$new_file,
|
|
||||||
$cr
|
|
||||||
);
|
|
||||||
|
|
||||||
$new_file = str_replace(ucfirst($nullableEntity->getProductCode()), ucfirst($newEntity->getProductCode()), $new_file, $cr);
|
|
||||||
$new_file = str_replace(ucfirst($nullableEntity->getCompanyCode()), ucfirst($newEntity->getCompanyCode()), $new_file, $cr);
|
|
||||||
$new_file = str_replace($nullableEntity->getProductCode(), $newEntity->getProductCode(), $new_file, $cr);
|
|
||||||
$new_file = str_replace($nullableEntity->getCompanyCode(), $newEntity->getCompanyCode(), $new_file, $cr);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
echo $cr . ' ' .$path . PHP_EOL;
|
|
||||||
|
|
||||||
if($orig_file != $new_file){
|
|
||||||
file_put_contents($path, $new_file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
echo $newEntity->getId() . PHP_EOL;
|
|
||||||
|
|
||||||
|
|
||||||
copy_folder_safe($nullableEntity->getPath(), $newEntity->getPath(), true, true, [
|
|
||||||
'/\.git$/',
|
|
||||||
'/\.module\.json/',
|
|
||||||
'/clone\.php/',
|
|
||||||
'/readme\.md/',
|
|
||||||
'/src\/\.env$/',
|
|
||||||
'/src\/config\.php$/',
|
|
||||||
], function ($d1, &$d2) use ($nullableEntity, $newEntity){
|
|
||||||
$lastModuleName = collect(explode('.', $newEntity->fields()->id))->last();
|
|
||||||
$lastNullableModuleName = collect(explode('.', $nullableEntity->fields()->id))->last();
|
|
||||||
|
|
||||||
|
|
||||||
$d2 = str_replace(ucfirst($lastNullableModuleName), ucfirst($lastModuleName), $d2, $cr);
|
|
||||||
$d2 = str_replace(strtolower($lastNullableModuleName), strtolower($lastModuleName), $d2, $cr);
|
|
||||||
});
|
|
||||||
|
|
||||||
_fix($newEntity, $nullableEntity);
|
|
||||||
|
|||||||
+6
-5
@@ -1,13 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Bitrix\Main\EventManager;
|
use Bitrix\Main\EventManager;
|
||||||
|
use Bitrix\Main\Loader;
|
||||||
|
|
||||||
IncludeModuleLangFile(__FILE__);
|
IncludeModuleLangFile(__FILE__);
|
||||||
|
|
||||||
class bx_nullable extends \CModule
|
class bx_nullable extends \CModule
|
||||||
{
|
{
|
||||||
public $module = [];
|
public $module = [];
|
||||||
var $MODULE_GROUP_RIGHTS = 'Y';
|
public $MODULE_GROUP_RIGHTS = 'Y';
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -37,15 +38,15 @@ class bx_nullable extends \CModule
|
|||||||
|
|
||||||
if (is_array($this->module['deps'])) {
|
if (is_array($this->module['deps'])) {
|
||||||
foreach ($this->module['deps'] as $moduleId) {
|
foreach ($this->module['deps'] as $moduleId) {
|
||||||
if (!\Bitrix\Main\Loader::includeModule($moduleId)) {
|
if (!Loader::includeModule($moduleId)) {
|
||||||
$APPLICATION->ThrowException("Module {$moduleId} not installed!");
|
$APPLICATION->ThrowException("Module {$moduleId} not installed!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CheckVersion(PHP_VERSION, '5.4.0')) {
|
if (!(version_compare(PHP_VERSION, '8.0.0') >= 0)) {
|
||||||
$APPLICATION->ThrowException('Php version required more 5.4.0');
|
$APPLICATION->ThrowException('Php version required more 8.0.0');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +71,7 @@ class bx_nullable extends \CModule
|
|||||||
|
|
||||||
public function InstallCatalogExport()
|
public function InstallCatalogExport()
|
||||||
{
|
{
|
||||||
if (is_dir(__DIR__ . '/catalog_export') && \Bitrix\Main\Loader::includeModule('catalog')) {
|
if (is_dir(__DIR__ . '/catalog_export') && Loader::includeModule('catalog')) {
|
||||||
CopyDirFiles(__DIR__ . '/catalog_export', $_SERVER['DOCUMENT_ROOT'] . CATALOG_PATH2EXPORTS, true, true);
|
CopyDirFiles(__DIR__ . '/catalog_export', $_SERVER['DOCUMENT_ROOT'] . CATALOG_PATH2EXPORTS, true, true);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class NullableCollection extends AEntityCollection
|
|||||||
*/
|
*/
|
||||||
public function &get($id, $default = null)
|
public function &get($id, $default = null)
|
||||||
{
|
{
|
||||||
return parent::get($id, $default); // TODO: Change the autogenerated stub
|
return parent::get($id, $default);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,7 +33,7 @@ class NullableCollection extends AEntityCollection
|
|||||||
*/
|
*/
|
||||||
public function &first(callable $callback = null, $default = null)
|
public function &first(callable $callback = null, $default = null)
|
||||||
{
|
{
|
||||||
return parent::first($callback, $default); // TODO: Change the autogenerated stub
|
return parent::first($callback, $default);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,7 +41,7 @@ class NullableCollection extends AEntityCollection
|
|||||||
*/
|
*/
|
||||||
public function &last()
|
public function &last()
|
||||||
{
|
{
|
||||||
return parent::last(); // TODO: Change the autogenerated stub
|
return parent::last();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,7 +49,7 @@ class NullableCollection extends AEntityCollection
|
|||||||
*/
|
*/
|
||||||
public function shift()
|
public function shift()
|
||||||
{
|
{
|
||||||
return parent::shift(); // TODO: Change the autogenerated stub
|
return parent::shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,7 +57,7 @@ class NullableCollection extends AEntityCollection
|
|||||||
*/
|
*/
|
||||||
public function pop()
|
public function pop()
|
||||||
{
|
{
|
||||||
return parent::pop(); // TODO: Change the autogenerated stub
|
return parent::pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,7 +65,7 @@ class NullableCollection extends AEntityCollection
|
|||||||
*/
|
*/
|
||||||
public function filter($callback = null)
|
public function filter($callback = null)
|
||||||
{
|
{
|
||||||
return parent::filter($callback); // TODO: Change the autogenerated stub
|
return parent::filter($callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,6 +73,6 @@ class NullableCollection extends AEntityCollection
|
|||||||
*/
|
*/
|
||||||
public function all()
|
public function all()
|
||||||
{
|
{
|
||||||
return parent::all(); // TODO: Change the autogenerated stub
|
return parent::all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ class NullableEntity extends AEntity
|
|||||||
*/
|
*/
|
||||||
public function getFactory()
|
public function getFactory()
|
||||||
{
|
{
|
||||||
return parent::getFactory(); // TODO: Change the autogenerated stub
|
return parent::getFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class NullableFactory extends AORMFactory
|
|||||||
*/
|
*/
|
||||||
public function getCollection($filter = [], $order = [], $select = [], $limit = null, $offset = null)
|
public function getCollection($filter = [], $order = [], $select = [], $limit = null, $offset = null)
|
||||||
{
|
{
|
||||||
return parent::getCollection($filter, $order, $select, $limit, $offset); // TODO: Change the autogenerated stub
|
return parent::getCollection($filter, $order, $select, $limit, $offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,7 +62,7 @@ class NullableFactory extends AORMFactory
|
|||||||
*/
|
*/
|
||||||
public function getEntityByIds(array $arIds)
|
public function getEntityByIds(array $arIds)
|
||||||
{
|
{
|
||||||
return parent::getEntityByIds($arIds); // TODO: Change the autogenerated stub
|
return parent::getEntityByIds($arIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user