<?php
namespace Blackbit\DataDirectorBundle\lib\Pim\FieldType;
use Blackbit\DataDirectorBundle\lib\Pim\Cache\RuntimeCache;
use Blackbit\DataDirectorBundle\lib\Pim\Helper;
use Blackbit\DataDirectorBundle\lib\Pim\Import\CallbackFunction;
use Blackbit\DataDirectorBundle\lib\Pim\Item\Importer;
use Blackbit\DataDirectorBundle\lib\Pim\Item\ImporterInterface;
use Blackbit\DataDirectorBundle\lib\Pim\Item\ItemMoldBuilder;
use Blackbit\DataDirectorBundle\lib\Pim\Item\ParameterBagComposite;
use Blackbit\DataDirectorBundle\lib\Pim\Item\ParameterBagObject;
use Blackbit\DataDirectorBundle\lib\Pim\Logger\InMemoryLogger;
use Blackbit\DataDirectorBundle\model\PimcoreDbRepository;
use Blackbit\DataDirectorBundle\model\RawItemField;
use Pimcore;
use Pimcore\Db;
use Pimcore\Event\Model\ElementEventInterface;
use Pimcore\Localization\LocaleServiceInterface;
use Pimcore\Logger;
use Pimcore\Model\DataObject\ClassDefinition\Data\CalculatedValue;
use Pimcore\Model\DataObject\Objectbrick\Data\AbstractData;
use Pimcore\Model\Element\Service;
use Psr\Log\NullLogger;
trait CalculatedValueDataQuerySelectorPhpCompatibilityTrait
{
/**
* @internal
*
* @var string|null
*/
public $dataQuerySelector = null;
/** @var ImporterInterface */
private static $importer;
/** @var ItemMoldBuilder */
private static $itemMoldBuilder;
private static $fetchFromDatabase = true;
public static function disableDatabaseFetch()
{
self::$fetchFromDatabase = false;
}
public function clearDatabaseCache(ElementEventInterface $e)
{
try {
if ($e->getArgument('saveVersionOnly')) {
return;
}
} catch (\InvalidArgumentException $exception) {
}
$object = $e->getElement();
if($object instanceof Pimcore\Model\DataObject\Concrete) {
$dependentObjects = array_merge(
PimcoreDbRepository::getInstance()->findInSql(
'SELECT dependencies.targetid AS id, objects.'.Helper::prefixObjectSystemColumn('classId').' as classId
FROM dependencies
LEFT JOIN objects ON dependencies.targettype="object" AND dependencies.targetid=objects.'.Helper::prefixObjectSystemColumn('id').'
WHERE dependencies.sourceid = ? AND dependencies.sourcetype = ?',
[$object->getId(), 'object']
),
PimcoreDbRepository::getInstance()->findInSql(
'SELECT dependencies.sourceid AS id, objects.'.Helper::prefixObjectSystemColumn('classId').' as classId
FROM dependencies
LEFT JOIN objects ON dependencies.sourcetype="object" AND dependencies.sourceid=objects.'.Helper::prefixObjectSystemColumn('id').'
WHERE dependencies.targetid = ? AND dependencies.targettype = ?',
[$object->getId(), 'object']
)
);
$groupedDependentObjects = array();
foreach ($dependentObjects as $dependentObject) {
$groupedDependentObjects[$dependentObject['classId']][] = $dependentObject['id'];
}
if($dependentObjects) {
foreach ((new Pimcore\Model\DataObject\ClassDefinition\Listing())->load() as $classDefinition) {
$fields = [];
$localizedFields = [];
foreach ($classDefinition->getFieldDefinitions() as $fieldDefinition) {
if ($fieldDefinition instanceof CalculatedValueDataQuerySelector) {
$fields[] = $fieldDefinition->getName();
} elseif ($fieldDefinition instanceof Pimcore\Model\DataObject\ClassDefinition\Data\Localizedfields) {
foreach ($fieldDefinition->getFieldDefinitions() as $localizedFieldDefinition) {
if ($localizedFieldDefinition instanceof CalculatedValueDataQuerySelector) {
$localizedFields[] = $localizedFieldDefinition->getName();
}
}
}
}
if(count($fields) > 0) {
$assignments = array_map(static function ($field) {
return Db::get()->quoteIdentifier($field).'=NULL';
}, $fields);
foreach (array_chunk($groupedDependentObjects[$classDefinition->getId()] ?? [], 1000) as $dependentObjectChunk) {
try {
PimcoreDbRepository::retry(static function () use ($classDefinition, $dependentObjectChunk, $assignments) {
PimcoreDbRepository::getInstance()->execute('UPDATE object_query_'.$classDefinition->getId().' SET '.implode(',', $assignments).' WHERE oo_id IN (?)', [$dependentObjectChunk]);
});
} catch (\Throwable $e) {}
}
}
if(count($localizedFields) > 0) {
$assignments = array_map(static function ($field) {
return Db::get()->quoteIdentifier($field).'=NULL';
}, $localizedFields);
foreach (Pimcore\Tool::getValidLanguages() as $language) {
foreach (array_chunk($groupedDependentObjects[$classDefinition->getId()] ?? [], 1000) as $dependentObjectChunk) {
try {
PimcoreDbRepository::retry(static function () use ($classDefinition, $language, $dependentObjectChunk, $assignments) {
PimcoreDbRepository::getInstance()->execute('UPDATE object_localized_query_'.$classDefinition->getId().'_'.$language.' SET '.implode(',', $assignments).' WHERE ooo_id IN (?)', [$dependentObjectChunk]);
});
} catch(\Throwable $e) {}
}
}
}
}
}
}
}
/**
* @return string|null
*/
public function getDataQuerySelector(): ?string
{
return $this->dataQuerySelector;
}
/**
* @param string|null $dataQuerySelector
*/
public function setDataQuerySelector(?string $dataQuerySelector): void
{
$this->dataQuerySelector = $dataQuerySelector;
}
/**
* @param Pimcore\Model\DataObject\Data\CalculatedValue|null $data
* @param Pimcore\Model\DataObject\Concrete $object
* @param array $params
*
* @return string|null
* @see Data::getDataForEditmode
*
*/
public function doGetDataForEditmode($data, $object = null, $params = [])
{
return self::getResult($this->getDataQuerySelector(), $object, ['fieldName' => $this->getName(), 'locale' => $data instanceof Pimcore\Model\DataObject\Data\CalculatedValue ? $data->getPosition() : null]);
}
public function doGetDataForQueryResource($data, $object = null, $params = [])
{
if (is_numeric($data)) {
return str_pad($data, 10, '0', STR_PAD_LEFT);
}
return $data;
}
public static function getResult($dataQuerySelector, $object = null, $params = [])
{
if (!$dataQuerySelector) {
try {
$calculator = Pimcore\Model\DataObject\ClassDefinition\Helper\CalculatorClassResolver::resolveCalculatorClass(CalculatedValueCalculator::class);
$context = new Pimcore\Model\DataObject\Data\CalculatedValue($params['fieldName']);
$context->setContextualData('object', null, null, $params['locale'] ?? null);
$ownerType = 'object';
$ownerName = null;
$index = null;
$position = null;
if ($object instanceof AbstractData) {
$ownerType = 'objectbrick';
$ownerName = $object->getFieldname();
$index = $object->getType();
}
$context->setContextualData($ownerType, $ownerName, $index, $position);
return trim($calculator->compute($object, $context));
} catch (\Throwable $e) {
return null;
}
}
if(self::$fetchFromDatabase && $object instanceof Pimcore\Model\DataObject\Concrete && !empty($params['fieldName'])) {
$dbValue = PimcoreDbRepository::getInstance()->findOneInSql(
'SELECT '.Db::get()->quoteIdentifier($params['fieldName']).' FROM '.(!empty($params['locale']) ? 'object_localized_'.$object->getClassId().'_'.$params['locale'] : 'object_'.$object->getClassId()).' WHERE oo_id=?',
[$object->getId()]
) ?: '';
if ($dbValue) {
return $dbValue;
}
}
try {
if (!empty($params['locale'])) {
$originalLanguage = \Pimcore::getContainer()->get(LocaleServiceInterface::class)->getLocale();
\Pimcore::getContainer()->get(LocaleServiceInterface::class)->setLocale($params['locale']);
}
$phpPrefix = '<?php';
if (substr($dataQuerySelector, 0, strlen($phpPrefix)) === $phpPrefix || strpos($dataQuerySelector, 'return ') !== false) {
$dataQuerySelector = preg_replace_callback('/\{\{\s*(\S+?( +\S+?)*?)\s*\}\}/', function ($matches) use ($object) {
if ($matches[1] === '' && $matches[4] === '') {
return $matches[2];
}
if (substr($matches[1], 0, 6) !== '.:.:.:') {
$parts = \str_getcsv($matches[1], ':', '"');
if (count($parts) >= 3) {
if (self::getItemMoldBuilder()->getClass($parts[0]) !== null) {
if (strtolower(substr($parts[1], 0, 5)) !== 'getby') {
// if we get here, first data query selector part also exists as data object class -> check if field for $object with same name exists -> field has higher priority
$fieldDefinition = Importer::getFieldDefinition($object, $parts[0]);
if (!$fieldDefinition->getLocked()) {
$matches[1] = '.:.:.:'.$matches[1];
}
}
} else {
$matches[1] = '.:.:.:'.$matches[1];
}
} else {
$matches[1] = '.:.:.:'.$matches[1];
}
}
return var_export(self::getImporter()->getObjectByIdentifier($matches[1], $object), true);
}, $dataQuerySelector);
$currentObjectValues = function () use ($object, $dataQuerySelector) {
if (!preg_match('/currentObjectData[\'"]\][);]/', $dataQuerySelector)) {
if (preg_match_all('/currentObjectData[\'"]\]\[["\']([A-Za-z0-9]+)["\']\]/', $dataQuerySelector, $fieldsToBeSerialized)) {
$currentObjectValues = [];
foreach ($fieldsToBeSerialized[1] as $fieldToBeSerialized) {
if (isset($currentObjectValues[$fieldToBeSerialized])) {
continue;
}
$fieldSerialization = Importer::getSerializer()->serializeField($object, Importer::getFieldDefinition($object, $fieldToBeSerialized));
$currentObjectValues[$fieldToBeSerialized] = $fieldSerialization;
}
} else {
$currentObjectValues = Importer::getSerializer()->getAttributesArrayForObject($object);
}
} else {
$currentObjectValues = Importer::getSerializer()->getAttributesArrayForObject($object);
}
return $currentObjectValues;
};
$logger = new InMemoryLogger(function () {
return true;
});
$jsParams = [
'currentObjectData' => $currentObjectValues,
'request' => Helper::getRequest(),
'logger' => $logger
];
$returnValue = CallbackFunction::evaluateScript($dataQuerySelector, CallbackFunction::ENGINE_PHP, $jsParams);
$logs = $logger->getLogs();
if ($logs) {
$returnValue .= PHP_EOL.PHP_EOL.implode(PHP_EOL, $logs);
}
} else {
if (strpos($dataQuerySelector, '{{') === false && strpos($dataQuerySelector, '{%') === false) {
if (strpos($dataQuerySelector, '.:.:.:') !== 0) {
$parts = \str_getcsv($dataQuerySelector, ':', '"');
if (self::getItemMoldBuilder()->getClass($parts[0]) !== null) {
// if we get here first data query selector part also exists as data object class -> check if field for $object with same name exists -> field has higher priority
$fieldDefinition = Importer::getFieldDefinition($object, $parts[0]);
if (!$fieldDefinition->getLocked()) {
$dataQuerySelector = '.:.:.:'.$dataQuerySelector;
}
} else {
$dataQuerySelector = '.:.:.:'.$dataQuerySelector;
}
}
$returnValue = self::getImporter()->getObjectByIdentifier($dataQuerySelector, $object);
if ($returnValue !== null && !is_scalar($returnValue)) {
$returnValue = Importer::getLogOutput($returnValue);
} elseif (is_bool($returnValue)) {
$returnValue = (int)$returnValue;
}
} else {
$returnValue = self::getImporter()->replaceObjectIdentifier($dataQuerySelector, $object);
}
}
} catch (\Exception $e) {
$returnValue = (string)$e;
} finally {
if (!empty($params['locale'])) {
\Pimcore::getContainer()->get(LocaleServiceInterface::class)->setLocale($originalLanguage);
}
}
if($object instanceof Pimcore\Model\DataObject\Concrete && !empty($params['fieldName'])) {
try {
if (!empty($params['locale'])) {
PimcoreDbRepository::getInstance()->execute('SELECT 1 FROM object_localized_query_'.$object->getClassId().'_'.$params['locale'].' WHERE ooo_id=? FOR UPDATE NOWAIT', [$object->getId()]);
PimcoreDbRepository::getInstance()->execute('UPDATE object_localized_query_'.$object->getClassId().'_'.$params['locale'].' SET '.Db::get()->quoteIdentifier($params['fieldName']).'=? WHERE ooo_id=?', [$returnValue, $object->getId()]);
} else {
PimcoreDbRepository::getInstance()->execute('SELECT 1 FROM object_query_'.$object->getClassId().' WHERE oo_id=? FOR UPDATE NOWAIT', [$object->getId()]);
PimcoreDbRepository::getInstance()->execute('UPDATE object_query_'.$object->getClassId().' SET '.Db::get()->quoteIdentifier($params['fieldName']).'=? WHERE oo_id=?', [$returnValue, $object->getId()]);
}
} catch(\Throwable $e) {
}
}
return trim($returnValue);
}
/**
* @return ImporterInterface
*/
private static function getImporter()
{
if (self::$importer === null) {
/** @var ImporterInterface $importer */
self::$importer = clone \Pimcore::getContainer()->get(ImporterInterface::class);
self::$importer->setLogger(new NullLogger());
}
return self::$importer;
}
/**
* @return ItemMoldBuilder
*/
private static function getItemMoldBuilder()
{
if (self::$itemMoldBuilder === null) {
/** @var ImporterInterface $importer */
self::$itemMoldBuilder = \Pimcore::getContainer()->get(ItemMoldBuilder::class);
}
return self::$itemMoldBuilder;
}
/**
* {@inheritdoc}
*/
public function doGetGetterCode($class)
{
$key = $this->getName();
$code = '/**'."\n";
$code .= '* Get '.str_replace(['/**', '*/', '//'], '', $this->getName()).' - '.str_replace(['/**', '*/', '//'], '', $this->getTitle())."\n";
$code .= '* @return '.$this->getPhpdocReturnType()."\n";
$code .= '*/'."\n";
$code .= 'public function get'.ucfirst($key).'()'."\n";
$code .= '{'."\n";
if ($class instanceof Pimcore\Model\DataObject\Objectbrick\Definition) {
$code .= "\t".'$object = $this->getObject();'."\n";
} else {
$code .= "\t".'$object = $this;'."\n";
}
$code .= "\t".'$data = \\Blackbit\\DataDirectorBundle\\lib\\Pim\\FieldType\\CalculatedValueDataQuerySelector::getResult(\''.str_replace('\'','\\\'', $this->getDataQuerySelector()).'\', $object, [\'fieldName\' => \''.$this->getName().'\']);'."\n\n";
$code .= "\t".'return $data;'."\n";
$code .= "}\n\n";
return $code;
}
/**
* {@inheritdoc}
*/
public function doGetGetterCodeLocalizedfields($class)
{
$key = $this->getName();
$code = '/**'."\n";
$code .= '* Get '.str_replace(['/**', '*/', '//'], '', $this->getName()).' - '.str_replace(['/**', '*/', '//'], '', $this->getTitle())."\n";
$code .= '* @return '.$this->getPhpdocReturnType()."\n";
$code .= '*/'."\n";
$code .= 'public function get'.ucfirst($key).'($language = null)'."\n";
$code .= '{'."\n";
$code .= "\t".'if (!$language) {'."\n";
$code .= "\t\t".'try {'."\n";
$code .= "\t\t\t".'$locale = \Pimcore::getContainer()->get(\''.LocaleServiceInterface::class.'\')->getLocale();'."\n";
$code .= "\t\t\t".'if (\Pimcore\Tool::isValidLanguage($locale)) {'."\n";
$code .= "\t\t\t\t".'$language = (string) $locale;'."\n";
$code .= "\t\t\t".'} else {'."\n";
$code .= "\t\t\t\t".'throw new \Exception("Not supported language");'."\n";
$code .= "\t\t\t".'}'."\n";
$code .= "\t\t".'} catch (\Exception $e) {'."\n";
$code .= "\t\t\t".'$language = \Pimcore\Tool::getDefaultLanguage();'."\n";
$code .= "\t\t".'}'."\n";
$code .= "\t".'}'."\n";
if ($class instanceof Pimcore\Model\DataObject\Objectbrick\Definition) {
$code .= "\t".'$object = $this->getObject();'."\n";
} else {
$code .= "\t".'$object = $this;'."\n";
}
$code .= "\t".'$data'." = new \\Pimcore\\Model\\DataObject\\Data\\CalculatedValue('".$key."');\n";
$code .= "\t".'$data = \\Blackbit\\DataDirectorBundle\\lib\\Pim\\FieldType\\CalculatedValueDataQuerySelector::getResult(\''.str_replace('\'', '\\\'', $this->getDataQuerySelector()).'\', $object, [\'fieldName\' => \''.$this->getName().'\', \'locale\' => $language]);'."\n\n";
$code .= "\treturn ".'$data'.";\n";
$code .= "}\n\n";
return $code;
}
/**
* {@inheritdoc}
*/
public function doGetGetterCodeObjectbrick($brickClass)
{
$key = $this->getName();
$code = '';
$code .= '/**'."\n";
$code .= '* Set '.str_replace(['/**', '*/', '//'], '', $this->getName()).' - '.str_replace(['/**', '*/', '//'], '', $this->getTitle())."\n";
$code .= '* @return '.$this->getPhpdocReturnType()."\n";
$code .= '*/'."\n";
$code .= 'public function get'.ucfirst($key).'($language = null)'."\n";
$code .= '{'."\n";
$code .= "\t".'$data = \\Blackbit\\DataDirectorBundle\\lib\\Pim\\FieldType\\CalculatedValueDataQuerySelector::getResult(\''.str_replace('\'', '\\\'', $this->getDataQuerySelector()).'\', $this->getObject(), [\'fieldName\' => \''.$this->getName().'\']);'."\n\n";
$code .= "\treturn ".'$data'.";\n";
$code .= "}\n\n";
return $code;
}
/**
* {@inheritdoc}
*/
public function doGetGetterCodeFieldcollection($fieldcollectionDefinition)
{
$key = $this->getName();
$code = '';
$code .= '/**'."\n";
$code .= '* Get '.str_replace(['/**', '*/', '//'], '', $this->getName()).' - '.str_replace(['/**', '*/', '//'], '', $this->getTitle())."\n";
$code .= '* @return '.$this->getPhpdocReturnType()."\n";
$code .= '*/'."\n";
$code .= 'public function get'.ucfirst($key).'()'."\n";
$code .= '{'."\n";
$code .= "\t".'$data = \\Blackbit\\DataDirectorBundle\\lib\\Pim\\FieldType\\CalculatedValueDataQuerySelector::getResult(\''.str_replace('\'', '\\\'', $this->getDataQuerySelector()).'\', $this->getObject(), [\'fieldName\' => \''.$this->getName().'\']);'."\n\n";
$code .= "\t".'return $data;'."\n";
$code .= "}\n\n";
return $code;
}
public function getCalculatorClass(): string
{
return CalculatedValueCalculator::class;
}
}