Your IP : 3.144.23.59
<?php
namespace Bitrix\Translate\Index;
use Bitrix\Main;
use Bitrix\Translate;
use Bitrix\Translate\Index;
/**
* @see \Bitrix\Main\ORM\Objectify\Collection
*/
class PathIndexCollection
extends Index\Internals\EO_PathIndex_Collection
{
/** @var bool */
static $verbose = false;
/** @var string */
private static $documentRoot;
/** @var string[] */
private static $enabledLanguages;
/** @var string[] */
private static $availableLanguages;
/** @var bool */
private static $useTranslationRepository;
/** @var string[] */
private static $translationRepositoryLanguages;
/** @var string[] */
private static $translationEnabledLanguages;
/** @var string */
private static $translationRepositoryRoot;
/** @var array */
private $immediateChildren = [];
/** @var array */
private $ancestorsPaths = [];
/** @var string[] */
private $checkLanguages = [];
/**
* Sets up configuration.
*
* @return void
*/
private static function configure()
{
self::$documentRoot = \rtrim(Translate\IO\Path::tidy(Main\Application::getDocumentRoot()), '/');
self::$enabledLanguages = Translate\Config::getEnabledLanguages();
self::$availableLanguages = Translate\Config::getAvailableLanguages();
self::$useTranslationRepository = Main\Localization\Translation::useTranslationRepository();
if (self::$useTranslationRepository)
{
self::$translationRepositoryLanguages = Translate\Config::getTranslationRepositoryLanguages();
self::$translationRepositoryRoot = \rtrim(Main\Localization\Translation::getTranslationRepositoryPath(), '/');
// only active languages
self::$translationEnabledLanguages = \array_intersect(self::$translationRepositoryLanguages, self::$enabledLanguages);
}
}
/**
* Counts items to process.
*
* @param Translate\Filter|null $filter Params to filter file list.
*
* @return int
*/
public function countItemsToProcess(?Translate\Filter $filter = null): int
{
if (isset($filter, $filter->path))
{
$relPath = '/'. \trim($filter->path, '/');
$totalItems = (int)Index\Internals\PathLangTable::getCount(['=%PATH' => $relPath .'%']);
}
else
{
$totalItems = (int)Index\Internals\PathLangTable::getCount();
}
return $totalItems;
}
/**
* Collect path structure.
*
* @param Translate\Filter|null $filter Params to filter file list.
* @param Translate\Controller\ITimeLimit|null $timer Time counter.
* @param Translate\Filter|null $seek Params to seek position.
*
* @return int
*/
public function collect(?Translate\Filter $filter = null, ?Translate\Controller\ITimeLimit $timer = null, ?Translate\Filter $seek = null): int
{
self::configure();
if (isset($filter, $filter->path))
{
$relPath = $filter->path;
}
else
{
$relPath = Translate\Config::getDefaultPath();
}
$relPath = '/'. \trim($relPath, '/');
if (self::$useTranslationRepository)
{
$this->checkLanguages = self::$translationEnabledLanguages;
if (isset($filter, $filter->langId))
{
$this->checkLanguages = \array_intersect($filter->langId, $this->checkLanguages);
}
}
$pathFilter = [
'=%PATH' => $relPath.'%'
];
if (isset($seek, $seek->pathLangId))
{
$pathFilter['>ID'] = $seek->pathLangId;
}
$cachePathLangRes = Index\Internals\PathLangTable::getList([
'filter' => $pathFilter,
'order' => ['ID' => 'ASC'],
'select' => ['ID', 'PATH'],
]);
$processedItemCount = 0;
while ($pathLang = $cachePathLangRes->fetch())
{
$this->collectFilePath($pathLang['PATH']);
$processedItemCount ++;
if ($timer !== null && $timer->hasTimeLimitReached())
{
if ($seek !== null)
{
$seek->nextLangPathId = (int)$pathLang['ID'];
}
break;
}
}
return $processedItemCount;
}
/**
* Collect path structure.
*
* @param string $relPath Path to lang folder to index.
*
* @return int
*/
private function collectFilePath($relPath): int
{
$fullPath = Translate\IO\Path::tidy(self::$documentRoot.'/'.$relPath);
$topPath = $this->constructAncestorsByPath($relPath);
$topPathId = (int)$topPath['ID'];
$topDepthLevel = (int)$topPath['DEPTH_LEVEL'];
$isTopLang = $topPath['IS_LANG'];
$topLangId = null;
if ($isTopLang)
{
$topLangId = Translate\IO\Path::extractLangId($relPath);
}
$relPath = Translate\IO\Path::replaceLangId($relPath, '#LANG_ID#');
if ($isTopLang)
{
if ($langSettings = Translate\Settings::instantiateByPath(self::$documentRoot.'/'.$relPath))
{
if (!$langSettings->isExists() || !$langSettings->load())
{
unset($langSettings);
}
}
}
/**
* @param string $parentFullPath Full real path of the parent folder.
* @param string $parentRelPath Relative project path of the parent folder.
* @param int $parentId The Id of of the parent folder index record.
* @param bool $isParentLang The flag that is parent folder is language folder.
* @param string $parentLangId The lang Id of the parent folder.
* @param int $depthLevel Current depth level.
*
* @return \Generator|int
*/
$lookForLangDirectory =
function (
$parentFullPath,
$parentRelPath,
$parentId,
$isParentLang = false,
$parentLangId = null,
$depthLevel = 0
)
use (
&$lookForLangDirectory,
/** @var Translate\Settings */
&$langSettings
)
{
$processedItemCount = 0;
$this->getImmediateChildren($parentId);
if ($isParentLang)
{
$childrenList = Translate\IO\FileSystemHelper::getFileList($parentFullPath);
if (!empty($childrenList))
{
foreach ($childrenList as $fullPath)
{
$name = \basename($fullPath);
if (\in_array($name, Translate\IGNORE_FS_NAMES))
{
continue;
}
if (!Translate\IO\Path::isPhpFile($name))
{
continue;
}
if (!\is_file($fullPath))
{
continue;
}
$relPath = Translate\IO\Path::replaceLangId($parentRelPath . '/'. $name, '#LANG_ID#');
$pathId = null;
if (isset($this->immediateChildren[$parentId][$relPath]))
{
$pathId = $this->immediateChildren[$parentId][$relPath];
}
if (self::$verbose)
{
echo "File path: {$relPath}";
}
if ($pathId === null)
{
$nodeData = [
'PARENT_ID' => $parentId,
'NAME' => $name,
'PATH' => $relPath,
'IS_LANG' => 'Y',
'IS_DIR' => 'N',
'DEPTH_LEVEL' => $depthLevel,
];
if ($langSettings instanceof Translate\Settings)
{
$settings = $langSettings->getOptions($relPath);
if (!empty($settings[Translate\Settings::OPTION_LANGUAGES]))
{
$nodeData['OBLIGATORY_LANGS'] = \implode(',', $settings[Translate\Settings::OPTION_LANGUAGES]);
}
}
$res = Index\Internals\PathIndexTable::add($nodeData);
$pathId = $res->getId();
$this->immediateChildren[$parentId][$relPath] = $pathId;
}
if (self::$verbose)
{
echo "\tIndex id: {$pathId}\n";
}
//yield $pathId;
$processedItemCount ++;
}
}
}
// dir only
$childrenList = Translate\IO\FileSystemHelper::getFolderList($parentFullPath);
if (empty($childrenList))
{
$childrenList = [];
}
if ($parentLangId === null && \basename($parentFullPath) === 'lang')
{
foreach ($childrenList as $i => $fullPath)
{
$name = \basename($fullPath);
if (\in_array($name, Translate\IGNORE_FS_NAMES))
{
unset($childrenList[$i]);
}
if (!\in_array($name, self::$enabledLanguages))
{
unset($childrenList[$i]);
}
}
unset($i, $fullPath, $name);
if (self::$useTranslationRepository)
{
// translation Repository
foreach ($this->checkLanguages as $langId)
{
$fullPathLang = Main\Localization\Translation::convertLangPath($parentFullPath.'/'.$langId, $langId);
if (\file_exists($fullPathLang))
{
$childrenList[] = $fullPathLang;
}
}
unset($langId, $fullPathLang);
}
}
if (!empty($childrenList))
{
$ignoreDev = \implode('|', Translate\IGNORE_MODULE_NAMES);
foreach ($childrenList as $fullPath)
{
$name = \basename($fullPath);
if (\in_array($name, Translate\IGNORE_FS_NAMES))
{
continue;
}
$relPath = $parentRelPath . '/'. $name;
if (!\is_dir($fullPath))
{
continue;
}
if (\in_array($relPath, Translate\IGNORE_BX_NAMES))
{
continue;
}
// /bitrix/modules/[smth]/dev/
if (\preg_match("#^bitrix/modules/[^/]+/({$ignoreDev})$#", \trim($relPath, '/')))
{
continue;
}
if ($isParentLang && \in_array($name, Translate\IGNORE_LANG_NAMES))
{
continue;
}
$isLang = $isParentLang || ($name === 'lang');
if ($isLang)
{
if (\in_array($name, self::$availableLanguages))
{
// only active languages
if (!\in_array($name, self::$enabledLanguages))
{
continue;
}
$parentLangId = $name;
$name = '#LANG_ID#';
}
$relPath = Translate\IO\Path::replaceLangId($relPath, '#LANG_ID#');
}
$pathId = null;
if (isset($this->immediateChildren[$parentId][$relPath]))
{
$pathId = $this->immediateChildren[$parentId][$relPath];
}
if (self::$verbose)
{
echo "Path folder: {$relPath}";
}
if ($pathId === null)
{
$nodeData = [
'PARENT_ID' => $parentId,
'NAME' => $name,
'PATH' => $relPath,
'IS_LANG' => $isLang ? 'Y' : 'N',
'IS_DIR' => 'Y',
'DEPTH_LEVEL' => $depthLevel,
];
if ($langSettings instanceof Translate\Settings)
{
$settings = $langSettings->getOptions($relPath);
if (!empty($settings[Translate\Settings::OPTION_LANGUAGES]))
{
$nodeData['OBLIGATORY_LANGS'] = \implode(',', $settings[Translate\Settings::OPTION_LANGUAGES]);
}
}
$res = Index\Internals\PathIndexTable::add($nodeData);
$pathId = $res->getId();
$this->immediateChildren[$parentId][$relPath] = $pathId;
$this->immediateChildren[$pathId] = [];
$this->ancestorsPaths[$relPath] = [
'ID' => $pathId,
'DEPTH_LEVEL' => $depthLevel,
'IS_LANG' => $isLang,
'PATH' => $relPath,
];
}
if (self::$verbose)
{
echo "\tIndex id: {$pathId}\n";
}
$processedItemCount += $lookForLangDirectory($fullPath, $relPath, $pathId, $isLang, $parentLangId, $depthLevel + 1);// go deeper
$processedItemCount ++;
}
}
return $processedItemCount;
};
$processedItemCount = $lookForLangDirectory($fullPath, $relPath, $topPathId, $isTopLang, $topLangId, $topDepthLevel + 1);
if ($isTopLang && isset($langSettings))
{
/** @var Translate\Settings $langSettings */
$settings = $langSettings->getOptions('*');
if (!empty($settings) && !empty($settings[Translate\Settings::OPTION_LANGUAGES]))
{
Index\Internals\PathIndexTable::bulkUpdate(
['OBLIGATORY_LANGS' => \implode(',', $settings[Translate\Settings::OPTION_LANGUAGES])],
[
'LOGIC' => 'OR',
'=PATH' => $relPath,
'=%PATH' => $relPath. '/%',
]
);
}
foreach ($langSettings as $settingPath => $settings)
{
if (\strpos($settingPath, '*') !== false && $settingPath !== '*' && !empty($settings['languages']))
{
$settingPath = \str_replace('*', '', $settingPath);
Index\Internals\PathIndexTable::bulkUpdate(
['OBLIGATORY_LANGS' => \implode(',', $settings[Translate\Settings::OPTION_LANGUAGES])],
[
'LOGIC' => 'OR',
'=PATH' => $relPath .'/#LANG_ID#/'. $settingPath,
'=%PATH' => $relPath .'/#LANG_ID#/'. $settingPath. '/%',
]
);
}
}
foreach ($langSettings as $settingPath => $settings)
{
if (Translate\IO\Path::isPhpFile($settingPath) && !empty($settings[Translate\Settings::OPTION_LANGUAGES]))
{
Index\Internals\PathIndexTable::bulkUpdate(
['OBLIGATORY_LANGS' => \implode(',', $settings[Translate\Settings::OPTION_LANGUAGES])],
['=PATH' => $relPath .'/#LANG_ID#/'. $settingPath]
);
}
}
}
return $processedItemCount;
}
/**
* Searchs or creates ancestor index by path.
*
* @param string $path Path to search.
*
* @return array|null
*/
public function constructAncestorsByPath($path): ?array
{
if (isset($this->ancestorsPaths[$path]))
{
return $this->ancestorsPaths[$path];
}
$pathParts = \explode('/', \trim($path, '/'));
$searchPath = '';
$ancestorsPathSearch = [];
foreach ($pathParts as $part)
{
$searchPath .= '/'. $part;
if (isset($this->ancestorsPaths[$searchPath]))
{
continue;
}
$ancestorsPathSearch[] = $searchPath;
}
$pathRes = Index\Internals\PathIndexTable::getList([
'select' => ['ID', 'DEPTH_LEVEL', 'IS_LANG', 'PATH'],
'filter' => ['=PATH' => $ancestorsPathSearch],
]);
while ($pathInx = $pathRes->fetch())
{
$pathInx['IS_LANG'] = ($pathInx['IS_LANG'] == 'Y');
$this->ancestorsPaths[$pathInx['PATH']] = $pathInx;
}
if (isset($this->ancestorsPaths[$path]))
{
return $this->ancestorsPaths[$path];
}
$pathInx = null;
$searchPath = '';
$searchParentId = 0;
$searchDepthLevel = 0;
$isLang = false;
foreach ($pathParts as $part)
{
$searchPath .= '/'. $part;
if (isset($this->ancestorsPaths[$searchPath]) && $searchPath !== $path)
{
$searchParentId = (int)$this->ancestorsPaths[$searchPath]['ID'];
$searchDepthLevel = (int)$this->ancestorsPaths[$searchPath]['DEPTH_LEVEL'] + 1;
$isLang = $this->ancestorsPaths[$searchPath]['IS_LANG'];
continue;
}
if ($isLang === false)
{
$isLang = ($part === 'lang');
}
$nodeData = [
'NAME' => $part,
'PATH' => $searchPath,
'PARENT_ID' => $searchParentId,
'DEPTH_LEVEL' => $searchDepthLevel,
'IS_LANG' => $isLang ? 'Y' : 'N',
'IS_DIR' => (Translate\IO\Path::isPhpFile($part) ? 'N' : 'Y'),
];
$pathInx = Index\Internals\PathIndexTable::add($nodeData);
$searchParentId = $pathInx->getId();
$this->ancestorsPaths[$searchPath] = [
'ID' => $searchParentId,
'DEPTH_LEVEL' => $searchDepthLevel,
'IS_LANG' => $isLang,
'PATH' => $searchPath,
];
$searchDepthLevel ++;
}
return $this->ancestorsPaths[$path];
}
/**
* Looks for immediate children.
*
* @param int $parentId Parent Id.
*
* @return Index\PathIndex[]
*/
private function getImmediateChildren($parentId): array
{
if (!isset($this->immediateChildren[$parentId]))
{
$this->immediateChildren[$parentId] = [];
$nodeRes = Index\Internals\PathIndexTable::getList([
'filter' => ['=PARENT_ID' => $parentId],
'select' => ['ID', 'PATH'],
]);
while ($nodeInx = $nodeRes->fetch())
{
$this->immediateChildren[$parentId][$nodeInx['PATH']] = (int)$nodeInx['ID'];
}
}
return $this->immediateChildren[$parentId];
}
/**
* Looks for ancestors by path.
*
* @param int $nodeId Index path to search ancestors.
* @param int $topNodeId The highest index path.
*
* @return Index\PathIndex[]
*/
private function getAncestors($nodeId, $topNodeId = -1): array
{
$nodeRes = Index\Internals\PathIndexTable::getList([
'filter' => ['=ID' => (int)$nodeId],
]);
$result = [];
if ($nodeInx = $nodeRes->fetchObject())
{
$result[$nodeInx->getId()] = $nodeInx;
if ((int)$nodeInx->getParentId() > 0)
{
$nodeRes = Index\Internals\PathIndexTable::getList([
'filter' => [
'=DESCENDANTS.PARENT_ID' => $nodeInx->getId(),//ancestor
],
'order' => ['DESCENDANTS.DEPTH_LEVEL' => 'DESC'],
]);
while ($nodeInx = $nodeRes->fetchObject())
{
$result[$nodeInx->getId()] = $nodeInx;
if ((int)$nodeInx->getParentId() == 0)
{
break;
}
if ($topNodeId > 0 && (int)$nodeInx->getId() == $topNodeId)
{
break;
}
}
}
}
return \array_reverse($result, true);
}
/**
* Rearrange tree as nested set structure.
*
* @return self
*/
public function arrangeTree(): self
{
$pathList = Index\Internals\PathIndexTable::getList([
'filter' => [
'=PARENT_ID' => 0,
'=IS_DIR' => 'Y',
],
'select' => ['ID'],
]);
while ($path = $pathList->fetch())
{
Index\Internals\PathIndexTable::arrangeTree($path['ID']);
}
return $this;
}
/**
* Drop index.
*
* @param Translate\Filter|null $filter Params to filter file list.
*
* @return self
*/
public function purge(?Translate\Filter $filter = null): self
{
Index\Internals\PathIndexTable::purge($filter);
return $this;
}
/**
* Unvalidate index.
*
* @param Translate\Filter|null $filter Params to filter file list.
* @param bool $recursively Drop index recursively.
*
* @return self
*/
public function validate(?Translate\Filter $filter = null, bool $recursively = true): self
{
$update = ['INDEXED' => 'Y', 'INDEXED_TIME' => new Main\Type\DateTime()];
if ($recursively)
{
$filterOut = Index\Internals\FileIndexTable::processFilter($filter);
Index\Internals\FileIndexTable::bulkUpdate($update, $filterOut);
}
$filterOut = Index\Internals\PathIndexTable::processFilter($filter);
Index\Internals\PathIndexTable::bulkUpdate($update, $filterOut);
return $this;
}
/**
* Unvalidate index.
*
* @param Translate\Filter|null $filter Params to filter file list.
* @param bool $recursively Drop index recursively.
*
* @return self
*/
public function unvalidate(Translate\Filter $filter = null, bool $recursively = true): self
{
if ($recursively)
{
$filterOut = Index\Internals\FileIndexTable::processFilter($filter);
Index\Internals\FileIndexTable::bulkUpdate(['INDEXED' => 'N'], $filterOut);
}
$filterOut = Index\Internals\PathIndexTable::processFilter($filter);
Index\Internals\PathIndexTable::bulkUpdate(['INDEXED' => 'N'], $filterOut);
return $this;
}
/**
* Collect sssignment file to module.
*
* @param Translate\Filter|null $filter Params to filter file list.
*
* @return self
*/
public function collectModuleAssignment(?Translate\Filter $filter = null): self
{
$searchPath = isset($filter, $filter->path) ? $filter->path : '';
if (!empty($searchPath))
{
$pathStartRes = Index\Internals\PathIndexTable::getList([
'filter' => [
'=PATH' => $searchPath,
],
'select' => ['ID', 'PATH'],
]);
if ($path = $pathStartRes->fetchObject())
{
$relPathParts = \explode('/', \trim($path->getPath(), '/'));
// /bitrix/modules/[smth]/
if (\count($relPathParts) >= 3 && $relPathParts[0] == 'bitrix' && $relPathParts[1] == 'modules')
{
$moduleId = $path->detectModuleId();
if ($moduleId !== null)
{
Index\Internals\PathIndexTable::bulkUpdate(
['MODULE_ID' => $moduleId],
['=DESCENDANTS.PARENT_ID' => $path->getId()]
);
}
}
//todo: else select sub nodes
}
}
else
{
$pathModulesRes = Index\Internals\PathIndexTable::getList([
'filter' => [
'=PATH' => '/bitrix/modules',
],
'select' => ['ID'],
]);
while ($pathModules = $pathModulesRes->fetch())
{
$pathList = Index\Internals\PathIndexTable::getList([
'filter' => [
'=PARENT_ID' => $pathModules['ID'],
],
'select' => ['ID', 'PATH'],
]);
while ($path = $pathList->fetchObject())
{
$moduleId = $path->detectModuleId();
if ($moduleId !== null)
{
Index\Internals\PathIndexTable::bulkUpdate(
['MODULE_ID' => $moduleId],
['=DESCENDANTS.PARENT_ID' => $path->getId()]
);
}
}
}
}
return $this;
}
/**
* Collect file asssignment.
*
* @param Translate\Filter|null $filter Params to filter file list.
*
* @return self
*/
public function collectAssignment(?Translate\Filter $filter = null): self
{
// /bitrix/(mobileapp|templates|components|activities|wizards|gadgets|js|..)
foreach (Translate\ASSIGNMENT_TYPES as $assignmentId)
{
$pathEntryRes = Index\Internals\PathIndexTable::getList([
'filter' => [
'=PATH' => '/bitrix/'. $assignmentId,
],
'select' => ['ID', 'PATH'],
]);
while ($path = $pathEntryRes->fetchObject())
{
Index\Internals\PathIndexTable::bulkUpdate(
['ASSIGNMENT' => $assignmentId],
['=DESCENDANTS.PARENT_ID' => $path->getId()]
);
}
}
$pathModulesRes = Index\Internals\PathIndexTable::getList([
'filter' => [
'=PATH' => '/bitrix/modules',
],
'select' => ['ID'],
]);
while ($pathModules = $pathModulesRes->fetch())
{
$pathList = Index\Internals\PathIndexTable::getList([
'filter' => [
'=PARENT_ID' => $pathModules['ID'],
'!=MODULE_ID' => null,
],
'select' => ['ID', 'PATH', 'MODULE_ID'],
]);
while ($modulePath = $pathList->fetchObject())
{
$moduleId = $modulePath->getModuleId();
foreach (Translate\ASSIGNMENT_TYPES as $assignmentId)
{
$filterPaths = [
// /bitrix/modules/[moduleName]/install/[smth]
'/bitrix/modules/'.$moduleId.'/install/'. $assignmentId,
// /bitrix/modules/[moduleName]/lang/#LANG_ID#/[smth]
'/bitrix/modules/'.$moduleId.'/lang/#LANG_ID#/'. $assignmentId,
// /bitrix/modules/[moduleName]/lang/#LANG_ID#/install/[smth]
'/bitrix/modules/'.$moduleId.'/lang/#LANG_ID#/install/'. $assignmentId,
// /bitrix/modules/[moduleName]/install/bitrix/templates/[templateName]
'/bitrix/modules/'.$moduleId.'/install/bitrix/'. $assignmentId,
// /bitrix/modules/[moduleName]/handlers/delivery/[smth]
// /bitrix/modules/[moduleName]/handlers/paysystem/[smth]
'/bitrix/modules/'.$moduleId.'/handlers/'. $assignmentId,
];
if ($assignmentId == 'templates')
{
// /bitrix/modules/[moduleName]/install/public/templates/[templateName]
$filterPaths[] = '/bitrix/modules/'.$moduleId.'/install/public/'. $assignmentId;
}
$pathEntryRes = Index\Internals\PathIndexTable::getList([
'filter' => [
'=PATH' => $filterPaths,
'=DESCENDANTS.PARENT_ID' => $modulePath->getId(),
],
'select' => ['ID', 'PATH'],
]);
while ($path = $pathEntryRes->fetchObject())
{
Index\Internals\PathIndexTable::bulkUpdate(
['ASSIGNMENT' => $assignmentId],
['=DESCENDANTS.PARENT_ID' => $path->getId()]
);
}
}
}
}
return $this;
}
}