Your IP : 3.141.42.94
<?php
namespace Bitrix\DocumentGenerator;
use Bitrix\DocumentGenerator\Body\Docx;
use Bitrix\DocumentGenerator\DataProvider\ArrayDataProvider;
use Bitrix\DocumentGenerator\Integration\TransformerManager;
use Bitrix\DocumentGenerator\Model\DocumentTable;
use Bitrix\DocumentGenerator\Model\ExternalLinkTable;
use Bitrix\DocumentGenerator\Model\FileTable;
use Bitrix\DocumentGenerator\Storage\Disk;
use Bitrix\Main\Engine\Response\DataType\ContentUri;
use Bitrix\Main\Error;
use Bitrix\Main\Event;
use Bitrix\Main\EventManager;
use Bitrix\Main\Loader;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Numerator\Numerator;
use Bitrix\Main\Result;
use Bitrix\Main\Engine\UrlManager;
use Bitrix\Main\Type\DateTime;
use Bitrix\Main\Web\Uri;
/**
* Class Document
* @package Bitrix\DocumentGenerator
* @property-read int ID
* @property-read int FILE_ID
* @property-read int IMAGE_ID
* @property-read int PDF_ID
*/
class Document
{
public const THIS_PLACEHOLDER = 'this';
public const STAMPS_ENABLED_PLACEHOLDER = 'stampsEnabled';
public const IMAGE = 'jpg';
public const PDF = 'pdf';
public const ERROR_NO_TRANSFORMER_MODULE = 'ERROR_NO_TRANSFORMER_MODULE';
/** @var array Current field descriptions */
protected $fields = [];
/** @var array Field description */
protected $externalFields = [];
/** @var Body|null */
protected $body;
protected $values = [];
protected $result;
/** @var Template */
protected $template;
protected $fieldNames = [];
protected $data = [];
protected $transformer;
protected $externalValues = [];
protected $selectFields = [];
protected $isCheckAccess = false;
protected $userId;
/**
* Document constructor.
* @param Body $body
* @param array $fields
* @param array $data
* @param mixed $value
*/
private function __construct(Body $body, array $fields = [], array $data = [], $value = null)
{
$this->body = $body;
$this->fields = array_merge($this->getDefaultFields(), $fields);
$this->data = $data;
$this->result = new Result();
$this->values = [
Template::DOCUMENT_PROVIDER_PLACEHOLDER => $this,
];
if($value)
{
$this->values[Template::MAIN_PROVIDER_PLACEHOLDER] = $value;
}
}
/**
* Creates new document from template on specific value.
* Template should have specified sourceType.
*
* @param Template $template
* @param mixed $value
* @param array $data
* @return Document|null
*/
public static function createByTemplate(Template $template, $value, array $data = []): ?Document
{
$fields = $template->getFields();
$body = $template->getBody();
if(!$body && $data['FILE_ID'] > 0)
{
$bodyClassName = $template->getBodyClassName();
$body = new $bodyClassName(FileTable::getContent($data['FILE_ID']));
}
if(!$body)
{
return null;
}
$documentClassName = Driver::getInstance()->getDocumentClassName();
/** @var static $document */
$document = new $documentClassName($body, $fields, $data, $value);
$document->setTemplate($template);
if($template->WITH_STAMPS === 'Y')
{
$document->enableStamps(true);
}
return $document;
}
/**
* Loads document from database by id
*
* @param $documentId
* @return Document|null
*/
public static function loadById(int $documentId): ?Document
{
if($documentId <= 0)
{
return null;
}
$documentData = DocumentTable::getById($documentId)->fetch();
if($documentData)
{
$template = Template::loadById($documentData['TEMPLATE_ID']);
if($template)
{
$template->setSourceType($documentData['PROVIDER']);
$document = static::createByTemplate($template, $documentData['VALUE'], $documentData);
}
else
{
$body = new Docx(FileTable::getContent($documentData['FILE_ID']));
$documentClassName = Driver::getInstance()->getDocumentClassName();
$document = new $documentClassName($body, [], $documentData, $documentData['VALUE']);
}
if(is_array($documentData['VALUES']))
{
$document->setValues($documentData['VALUES']);
}
return $document;
}
return null;
}
public function __get($name)
{
if(isset($this->data[$name]))
{
return $this->data[$name];
}
return null;
}
/**
* @param int $pdfId
* @return Document
*/
public function setPdfId(int $pdfId): Document
{
$this->data['PDF_ID'] = $pdfId;
return $this;
}
/**
* @param int $imageId
* @return Document
*/
public function setImageId(int $imageId): Document
{
$this->data['IMAGE_ID'] = $imageId;
return $this;
}
/**
* Add new values or rewrite old ones, but does not clear all the list.
*
* @param array $values
* @return $this
*/
public function setValues(array $values): Document
{
foreach($values as $placeholder => $value)
{
if($placeholder === Template::MAIN_PROVIDER_PLACEHOLDER)
{
$this->values[$placeholder] = $value;
}
else
{
$this->externalValues[$placeholder] = $value;
}
}
// rewrite values
if(isset($this->fields[Template::MAIN_PROVIDER_PLACEHOLDER]))
{
$this->fields[Template::MAIN_PROVIDER_PLACEHOLDER]['OPTIONS']['VALUES'] = $this->getExternalValues();
}
return $this;
}
/**
* Add new fields or rewrite old ones (except 'SOURCE' and 'DOCUMENT'), but does not clear all the list.
*
* @param array $fields
* @return $this
*/
public function setFields(array $fields): Document
{
foreach($fields as $name => $field)
{
// do not let change these fields
if(
$name === Template::DOCUMENT_PROVIDER_PLACEHOLDER
|| $name === Template::MAIN_PROVIDER_PLACEHOLDER
)
{
continue;
}
$this->externalFields[$name] = $field;
}
return $this;
}
/**
* If $requiredOnly is true returns a list of required with empty values.
* If false - returns a list of not found placeholders without a value.
*
* @param bool $requiredOnly
* @return array
*/
public function checkFields(bool $requiredOnly = true): array
{
$emptyFields = [];
if($this->result->isSuccess())
{
$this->resolveProviders();
}
$fieldNames = $this->getFieldNames();
if($this->result->isSuccess())
{
$values = $this->getValues($fieldNames);
foreach($fieldNames as $placeholder)
{
if(
isset($this->fields[$placeholder]['REQUIRED'])
&& $this->fields[$placeholder]['REQUIRED'] === 'Y'
&& empty($values[$placeholder])
)
{
$emptyFields[$placeholder] = $this->fields[$placeholder];
}
elseif(
!$requiredOnly
&& empty($values[$placeholder])
&& !isset($this->getExternalValues()[$placeholder])
)
{
$emptyFields[$placeholder] = [];
}
}
foreach($this->selectFields as $placeholder => $field)
{
if(
$field['VALUE']
&& is_array($field['VALUE'])
&& DataProviderManager::getInstance()->getValueFromList($field['VALUE']) === $field['VALUE']
)
{
$emptyFields[$placeholder] = $field;
}
}
}
return $emptyFields;
}
/**
* @return Template|null
*/
public function getTemplate(): ?Template
{
return $this->template;
}
/**
*
*
* @param bool $sendToTransformation
* @param bool $skipTransformationError
* @return Result
*/
public function getFile(bool $sendToTransformation = true, bool $skipTransformationError = false): Result
{
if(!$this->result->isSuccess())
{
return $this->result;
}
if(!$this->ID)
{
$this->process()->save();
}
if($this->result->isSuccess())
{
$data = [];
$provider = $this->getProvider();
if($provider)
{
$data = $provider->getAdditionalDocumentInfo($this);
}
$data = array_merge($data, [
'downloadUrl' => $this->getDownloadUrl(),
'publicUrl' => $this->getPublicUrl(),
'title' => $this->getTitle(),
'number' => $this->getNumber(),
'id' => $this->ID,
'createTime' => $this->getCreateTime(),
'updateTime' => $this->getUpdateTime(),
'stampsEnabled' => $this->isStampsEnabled(),
'isTransformationError' => false,
'value' => $this->getValue(Template::MAIN_PROVIDER_PLACEHOLDER),
'values' => $this->getExternalValues(),
]);
$template = $this->getTemplate();
if($template)
{
$data['templateId'] = $template->ID;
}
$provider = $this->getProvider();
if($provider)
{
$data['provider'] = get_class($provider);
}
if($sendToTransformation)
{
if(!$this->PDF_ID || !$this->IMAGE_ID)
{
$transformResult = $this->transform();
if($transformResult->isSuccess())
{
$data['isTransformationError'] = false;
}
else
{
$data['isTransformationError'] = true;
$error = $transformResult->getErrors()[0];
$data['transformationErrorMessage'] = $error->getMessage();
$data['transformationErrorCode'] = $error->getCode();
if(!$skipTransformationError)
{
$this->result->addErrors($transformResult->getErrors());
}
}
}
}
$pullTag = $this->getPullTag();
if($pullTag)
{
$data['pullTag'] = $pullTag;
}
if($this->IMAGE_ID > 0)
{
$data['imageUrl'] = $this->getImageUrl();
}
if($this->PDF_ID > 0)
{
$data['pdfUrl'] = $this->getPdfUrl();
$data['printUrl'] = $this->getPrintUrl();
$data['emailDiskFile'] = $this->getEmailDiskFile();
}
else
{
$data['emailDiskFile'] = $this->getEmailDiskFile(true);
}
$this->result->setData($data);
}
else
{
$this->result->setData([]);
}
return $this->result;
}
/**
* @param array $values
* @param bool $sendToTransformation
* @param bool $skipTransformationError
* @return Result
*/
public function update(
array $values,
bool $sendToTransformation = true,
bool $skipTransformationError = false
): Result
{
if($this->ID > 0)
{
$this->values = [
Template::MAIN_PROVIDER_PLACEHOLDER => $this->values[Template::MAIN_PROVIDER_PLACEHOLDER],
Template::DOCUMENT_PROVIDER_PLACEHOLDER => $this->values[Template::DOCUMENT_PROVIDER_PLACEHOLDER],
];
$this->selectFields = [];
return $this->setValues($values)->process()->save()->getFile($sendToTransformation, $skipTransformationError);
}
return $this->result->addError(new Error('Cant update not saved document'));
}
/**
* @return string
*/
public function getTitle(): string
{
if(isset($this->externalValues['DocumentTitle']))
{
$title = $this->externalValues['DocumentTitle'];
}
elseif(isset($this->data['TITLE']))
{
$title = $this->data['TITLE'];
}
else
{
$title = '';
if($this->template)
{
$title .= $this->template->NAME;
}
$title .= ' '.ltrim($this->getNumber());
$this->data['TITLE'] = $title;
}
return $title;
}
/**
* @param bool $preview
* @return string
*/
public function getNumber(bool $preview = true): string
{
if(isset($this->externalValues['DocumentNumber']))
{
$number = $this->externalValues['DocumentNumber'];
}
elseif(isset($this->data['NUMBER']))
{
$number = $this->data['NUMBER'];
}
else
{
$number = '';
if($this->template)
{
$numerator = Numerator::load($this->template->NUMERATOR_ID, $this->getProvider());
if(!$numerator)
{
$numerator = Driver::getInstance()->getDefaultNumerator($this->getProvider());
}
if($numerator)
{
if($preview === false)
{
$number = $numerator->getNext();
$this->data['NUMBER'] = $number;
}
else
{
$number = $numerator->previewNextNumber();
}
}
}
if(!$number)
{
$this->result->addError(new Error('Error getting next number'));
}
}
return $number;
}
public function getCreateTime(): DateTime
{
if(!isset($this->data['CREATE_TIME']) || empty($this->data['CREATE_TIME']))
{
$this->data['CREATE_TIME'] = new DateTime();
}
return $this->data['CREATE_TIME'];
}
public function getUpdateTime(): DateTime
{
if(!isset($this->data['UPDATE_TIME']) || empty($this->data['UPDATE_TIME']))
{
$this->data['UPDATE_TIME'] = new DateTime();
}
return $this->data['UPDATE_TIME'];
}
/**
* @return DataProvider|Nameable|null
*/
public function getProvider(): ?DataProvider
{
if(isset($this->fields[Template::MAIN_PROVIDER_PLACEHOLDER]))
{
$mainField = $this->fields[Template::MAIN_PROVIDER_PLACEHOLDER];
return DataProviderManager::getInstance()->createDataProvider(
$mainField,
$this->getValue(Template::MAIN_PROVIDER_PLACEHOLDER)
);
}
if($this->data['PROVIDER'] && $this->data['VALUE'])
{
return DataProviderManager::getInstance()->getDataProvider($this->data['PROVIDER'], $this->data['VALUE']);
}
return null;
}
/**
* @param bool $status
* @return $this
*/
public function enableStamps(bool $status = true): Document
{
$this->setValues([static::STAMPS_ENABLED_PLACEHOLDER => ($status === true)]);
return $this;
}
/**
* @return bool
*/
public function isStampsEnabled(): bool
{
return ($this->getValue(static::STAMPS_ENABLED_PLACEHOLDER) === true);
}
/**
* @return string
*/
public function getCreationMethod(): ?string
{
return $this->getValue(CreationMethod::CREATION_METHOD_PLACEHOLDER);
}
/**
* Returns array with all placeholders, their field descriptions and actual values.
*
* @param array $fieldNames
* @param bool $isConvertValuesToString
* @param bool $groupsAsArrays
* @return array
*/
public function getFields(
array $fieldNames = [],
bool $isConvertValuesToString = false,
bool $groupsAsArrays = false
): array
{
DataProviderManager::getInstance()->setContext(Context::createFromDocument($this));
Loc::loadLanguageFile(__FILE__);
$this->resolveProviders();
$fields = [];
$default = !($this->ID > 0);
if(empty($fieldNames))
{
$fieldNames = $this->getFieldNames();
}
$externalValues = $this->getExternalValues(true);
foreach($fieldNames as $placeholder)
{
$this->getValue($placeholder);
$defaultValue = $value = $this->values[$placeholder];
if(!$default)
{
if(isset($externalValues[$placeholder]))
{
$value = $externalValues[$placeholder];
}
}
if($value instanceof ArrayDataProvider)
{
continue;
}
if(is_array($value))
{
$value = '';
}
$valueParts = explode('.', $value);
if(
isset($fields[$placeholder])
|| ($valueParts[0] && in_array($valueParts[0], $this->fieldNames, true))
)
{
continue;
}
$field = [
'VALUE' => '',
];
if(isset($this->fields[$placeholder]))
{
$field = $this->fields[$placeholder];
$field['CHAIN'] = $field['VALUE'];
}
$field['VALUE'] = $this->normalizeValue($value, $isConvertValuesToString);
$field['DEFAULT'] = $this->normalizeValue($defaultValue, $isConvertValuesToString);
$fields[$placeholder] = $field;
}
foreach($this->selectFields as $placeholder => $field)
{
if(is_array($field['VALUE']) && empty($field['VALUE']))
{
continue;
}
if(empty($field['GROUP']))
{
$field['GROUP'] = $this->getFieldGroup('this.SOURCE.'.$placeholder.'.popMe');
}
$fields[$placeholder] = $field;
}
foreach($fields as &$field)
{
if(is_array($field['GROUP']))
{
array_pop($field['GROUP']);
}
if(!$groupsAsArrays && is_array($field['GROUP']))
{
$field['GROUP'] = array_pop($field['GROUP']);
}
}
return $fields;
}
/**
* @return int
*/
public function getUserId(): ?int
{
$userId = $this->userId;
if($userId === null)
{
$userId = Driver::getInstance()->getUserId();
}
return $userId;
}
/**
* @param int $userId
* @return $this
*/
public function setUserId(int $userId): Document
{
$userId = (int)$userId;
$this->userId = $userId;
return $this;
}
/**
* @param mixed $value
* @param bool $isConvertToString
* @return string|object|Value
*/
protected function normalizeValue($value, bool $isConvertToString = false)
{
$result = $value;
if(is_array($value) || is_bool($value))
{
$result = '';
}
elseif($isConvertToString && $value instanceof Value)
{
$result = $value->getValue();
if(is_object($result) || is_array($result) || is_bool($result))
{
$result = $value->toString();
}
}
return $result;
}
/**
* @param string $value
* @return string
*/
protected function getFieldGroup($value): string
{
$group = Loc::getMessage('DOCUMENT_GROUP_NAME');
if(empty($value) || mb_strpos($value, 'this.SOURCE.') !== 0)
{
return $group;
}
$value = str_replace('this.SOURCE.', '', $value);
if(empty($value))
{
return $group;
}
$valueParts = explode('.', $value);
array_pop($valueParts);
$providerName = implode('.', $valueParts);
if(empty($providerName))
{
return $this->getProvider()->getLangName();
}
$field = DataProviderManager::getInstance()->getProviderField($this->getProvider(), $providerName);
if(is_array($field) && isset($field['TITLE']))
{
return $field['TITLE'];
}
return $group;
}
/**
* Process document and returns Result.
*
* @return $this
*/
protected function process(): Document
{
// here we get actual number
$this->getNumber(false);
EventManager::getInstance()->send(new Event(Driver::MODULE_ID, 'onBeforeProcessDocument', ['document' => $this]));
if(!$this->template)
{
$this->result->addError(new Error('Cant process document without template'));
return $this;
}
if($this->template->isDeleted())
{
$this->result->addError(new Error('Cant process document on deleted template'));
return $this;
}
if(!$this->template->getSourceType())
{
$this->result->addError(new Error('Cant process document on template without sourceType'));
return $this;
}
DataProviderManager::getInstance()->setContext(Context::createFromDocument($this));
$requiredFields = $this->checkFields();
foreach($requiredFields as $placeholder => $field)
{
$this->result->addError(new Error('No value for required placeholder '.$placeholder));
}
if($this->result->isSuccess())
{
$values = $this->getValues($this->getFieldNames());
if(!$this->isStampsEnabled())
{
foreach($this->fields as $placeholder => $field)
{
if(
isset($field['TYPE']) &&
$field['TYPE'] === DataProvider::FIELD_TYPE_STAMP
)
{
$values[$placeholder] = ' ';
}
}
}
$bodyResult = $this->body->setValues($values)->setFields($this->fields)->process();
if($bodyResult->isSuccess())
{
$resultData = ['BODY' => $this->body];
$this->result->setData($resultData);
}
else
{
$this->result = $bodyResult;
}
}
return $this;
}
/**
* @return $this
* @throws \Exception
*/
protected function save(): Document
{
if($this->result->isSuccess())
{
$resultData = $this->result->getData();
$saveResult = $this->body->save([
'fileName' => $this->getFileName(),
'templateId' => $this->template->ID,
'value' => $this->getValue(Template::MAIN_PROVIDER_PLACEHOLDER),
]);
if(!$saveResult->isSuccess())
{
$this->result->addErrors($saveResult->getErrors());
}
else
{
$data = [
'TEMPLATE_ID' => $this->template->ID,
'VALUE' => $this->getValue(Template::MAIN_PROVIDER_PLACEHOLDER),
'FILE_ID' => $saveResult->getId(),
'VALUES' => $this->getExternalValues(true),
'PROVIDER' => $this->template->getSourceType(),
'IMAGE_ID' => null,
'PDF_ID' => null,
'UPDATE_TIME' => new DateTime(),
'TITLE' => $this->getTitle(),
'NUMBER' => $this->getNumber(false),
];
if($this->ID > 0)
{
$data['UPDATED_BY'] = $this->getUserId();
$result = DocumentTable::update($this->ID, $data);
$eventName = 'onUpdateDocument';
}
else
{
$data['CREATED_BY'] = $this->getUserId();
$result = DocumentTable::add($data);
$eventName = 'onCreateDocument';
}
if($result->isSuccess())
{
$data['ID'] = $result->getId();
$this->data = $data;
$resultData['DOCUMENT_ID'] = $result->getId();
if($eventName)
{
EventManager::getInstance()->send(new Event(Driver::MODULE_ID, $eventName, ['document' => $this]));
}
}
else
{
$this->result->addErrors($result->getErrors());
}
}
$this->result->setData($resultData);
}
return $this;
}
protected function actualizeFields(): void
{
$provider = $this->getProvider();
if(!$provider)
{
return;
}
$placeholders = array_keys($this->fields);
$fields = DataProviderManager::getInstance()->getProviderFields($provider, $placeholders, true);
foreach($fields as $field)
{
array_unshift($field['GROUP'], Loc::getMessage('DOCUMENT_GROUP_NAME'));
$placeholder = DataProviderManager::getInstance()->valueToPlaceholder($field['VALUE']);
unset($field['VALUE']);
if(!isset($this->fields[$placeholder]))
{
$this->fields[$placeholder] = [];
}
$this->fields[$placeholder] = array_merge($this->fields[$placeholder], $field);
}
foreach($this->externalFields as $placeholder => $field)
{
$this->fields[$placeholder] = $field;
}
}
/**
* Link providers by their names and values.
*/
protected function resolveProviders(): void
{
$this->actualizeFields();
foreach($this->fields as $name => $field)
{
$this->resolveProvider($field, $name);
}
}
/**
* @param array $field Field description.
* @param string $name
*/
protected function resolveProvider(array $field, string $name): void
{
if(!$field['PROVIDER'])
{
return;
}
if(isset($field['VALUE']))
{
$this->values[$name] = $field['VALUE'];
}
$parentDataProvider = null;
if($name !== Template::MAIN_PROVIDER_PLACEHOLDER && $name !== Template::DOCUMENT_PROVIDER_PLACEHOLDER)
{
$parentDataProvider = $this->getProvider();
if(!$parentDataProvider)
{
$parentDataProvider = null;
}
}
$value = $this->getValue($name);
$dataProvider = DataProviderManager::getInstance()->createDataProvider($field, $value, $parentDataProvider);
if($dataProvider && $dataProvider->isLoaded())
{
if($this->isCheckAccess && !DataProviderManager::getInstance()->checkDataProviderAccess($dataProvider))
{
$this->result->addError(new Error('Access denied to provider '.$field['PROVIDER'].' for placeholder '.$name));
return;
}
if($dataProvider instanceof ArrayDataProvider && $dataProvider->getItemKey())
{
$this->fieldNames[$name] = $name;
}
$providerFields = $dataProvider->getFields();
foreach($providerFields as $placeholder => $providerField)
{
$fullName = $name.'.'.$placeholder;
if(!isset($this->fields[$fullName]))
{
$this->fields[$fullName] = [];
}
$providerValue = $dataProvider->getValue($placeholder);
if($providerValue instanceof ArrayDataProvider)
{
// here we add inner item of the ArrayDataProvider to the fields.
$this->fields[$placeholder] = [
'VALUE' => static::THIS_PLACEHOLDER.'.'.$fullName,
];
$this->fieldNames[$placeholder] = $placeholder;
}
$this->fields[$fullName] = array_merge($this->fields[$fullName], ['VALUE' => $providerValue]);
}
if(isset($this->externalValues[$name]))
{
$this->values[$name] = $dataProvider;
unset($this->externalValues[$name]);
}
}
else
{
$this->result->addError(new Error('Cant resolve provider '.$field['PROVIDER'].' for placeholder '.$name));
}
}
/**
* Get values for $fields.
*
* @param array $fieldNames
* @return array
*/
protected function getValues(array $fieldNames): array
{
$values = [];
foreach($fieldNames as $fieldName)
{
$values[$fieldName] = $this->normalizeValue($this->getValue($fieldName));
}
return $values;
}
/**
* Returns value by its $name.
*
* @param string $name
* @return array|string
*/
protected function getValue($name)
{
if(isset($this->values[$name]))
{
$value = $this->values[$name];
}
elseif(isset($this->fields[$name]['VALUE']))
{
$value = $this->fields[$name]['VALUE'];
}
else
{
$value = $this->getProviderValue($name);
}
$value = $this->resolveValue($value);
if($value && $this->fields[$name]['PROVIDER'] && isset($this->fields[$name]['PROVIDER_NAME']))
{
/** @var DataProvider $dataProvider */
$dataProvider = DataProviderManager::getInstance()->createDataProvider($this->fields[$name], $value);
if($dataProvider && $dataProvider->isLoaded())
{
if($this->isCheckAccess && !DataProviderManager::getInstance()->checkDataProviderAccess($dataProvider))
{
$value = null;
}
else
{
$value = $dataProvider->getValue($this->fields[$name]['PROVIDER_NAME']);
}
}
}
// save found calculated value.
$this->values[$name] = $value;
// if this value has been overwritten - use it.
$externalValues = $this->getExternalValues();
if(
isset($externalValues[$name]) &&
$externalValues[$name] != $this->values[$name] &&
(
!is_array($this->values[$name]) && $externalValues[$name] != htmlspecialcharsbx($this->values[$name])
)
)
{
$value = $externalValues[$name];
$value = $this->resolveValue($value);
$value = DataProviderManager::getInstance()->prepareValue($value, $this->fields[$name]);
}
return $value;
}
/**
* @param mixed $value
* @return mixed
*/
protected function resolveValue($value)
{
if(is_string($value))
{
$valueNameParts = explode('.', $value);
if(count($valueNameParts) > 1 && $valueNameParts[0] === static::THIS_PLACEHOLDER)
{
array_shift($valueNameParts);
$valueName = implode('.', $valueNameParts);
$value = $this->getValue($valueName);
// next code is needed when we have a placeholder that points to some item value from ArrayDataProvider.
if($value === $valueName)
{
$value = static::THIS_PLACEHOLDER.'.'.$valueName;
}
}
}
elseif(is_callable($value))
{
$value = $value();
}
return $value;
}
/**
* This method resolves provider from $name.
* For example, 'basket.price' is a placeholder.
* First we need to get value for 'basket', and if it is a dataProvider - we can get 'price' value from it
* $name may consist from infinite number of providers (provider1.provider2.provider3.provider4...name).
* Move from left to right assuming that each next value is a provider.
*
* @param string $name
* @param DataProvider|null $dataProvider
* @param string $fullChain
* @return mixed
*/
protected function getProviderValue($name, DataProvider $dataProvider = null, $fullChain = '')
{
$value = '';
// if not a string - there is no provider chain.
if(!is_string($name))
{
return $value;
}
$nameParts = explode('.', $name);
if(count($nameParts) > 1)
{
// here we move from left to right.
$providerName = $nameParts[0];
if($dataProvider === null)
{
// if it is the first iteration - get value from $this.
$value = $this->getValue($providerName);
}
else
{
$value = $dataProvider->getValue($providerName);
}
// here we handle multiple values for inner providers
if(is_array($value) && $dataProvider)
{
$value = $this->handleMultipleProviderValue($value, $dataProvider, $fullChain, $providerName);
if(is_array($value))
{
return $value;
}
// initialize child data provider manually
$value = DataProviderManager::getInstance()->createDataProvider(
$dataProvider->getFields()[$providerName],
$value,
$dataProvider,
$providerName
);
}
array_shift($nameParts);
// combine valueName from all parts but first
$valueName = implode('.', $nameParts);
if($value instanceof ArrayDataProvider)
{
// if current value is an ArrayDataProvider and $valueName doesn't point to ArrayDataProvider outer field
// then we assume that this $name points to the field of ArrayDataProvider item - thus
// we need to return $name as it is.
// In Body there will be a cycle with this $name as placeholder.
$providerFields = $value->getFields();
if(
$valueName !== ArrayDataProvider::NUMBER_PLACEHOLDER
&& !in_array($valueName, $providerFields, true)
)
{
return $name;
}
}
// if there is PROVIDER in field description then we need to initialize new provider of this type on $value.
if(isset($this->fields[$providerName]['PROVIDER']))
{
$value = DataProviderManager::getInstance()->createDataProvider(
$this->fields[$providerName],
$value,
$dataProvider,
$providerName
);
}
if($value instanceof DataProvider)
{
if(
$this->isCheckAccess
&& $value->isLoaded()
&& !DataProviderManager::getInstance()->checkDataProviderAccess($value)
)
{
$value = null;
}
else
{
$value = $this->getProviderValue($valueName, $value, $name);
}
}
}
// if it is not the first iteration, there are no more providers in chain and we got $dataProvider.
elseif($dataProvider)
{
$value = $dataProvider->getValue($name);
}
return $value;
}
/**
* @param array $value
* @param DataProvider $dataProvider
* @param $placeholder
* @param $providerName
* @return mixed
*/
protected function handleMultipleProviderValue(
array $value,
DataProvider $dataProvider,
string $placeholder,
string $providerName
)
{
$fullPlaceholder = $placeholder;
$placeholderParts = explode('.', $placeholder);
$placeholder = '';
foreach($placeholderParts as $part)
{
if($part === Template::MAIN_PROVIDER_PLACEHOLDER)
{
continue;
}
if($placeholder !== '')
{
$placeholder .= '.';
}
$placeholder .= $part;
if($part === $providerName)
{
break;
}
}
if(!$placeholder)
{
$this->result->addError(new Error('Multiple values for root provider are not allowed'));
return false;
}
if(isset($this->selectFields[$placeholder]))
{
return DataProviderManager::getInstance()->getValueFromList($value);
}
if($dataProvider === null)
{
$fields = $this->getProvider()->getFields();
}
else
{
$fields = $dataProvider->getFields();
}
if(isset($fields[$providerName]))
{
$group = [];
foreach($this->fields as $field)
{
if(is_string($field['VALUE']) && mb_strpos($field['VALUE'], $fullPlaceholder) !== false)
{
$group = $field['GROUP'];
break;
}
}
if(is_array($group))
{
// 3 = 1 (document) + 1(minimum) + 1(will be popped)
$group = array_slice($group, 0, (3 + substr_count($placeholder, '.')));
}
$title = $providerName;
if(isset($fields[$providerName]['TITLE']))
{
$title = $fields[$providerName]['TITLE'];
}
$this->selectFields[$placeholder] = [
'TITLE' => $title,
'VALUE' => $value,
'GROUP' => $group,
];
}
return DataProviderManager::getInstance()->getValueFromList($value);
}
/**
* Add $fieldNames to $this->excludeFields (it is not rewrite them)
*
* @param array $fieldNames
* @return $this
*/
public function excludeFields(array $fieldNames): Document
{
$this->body->setExcludedPlaceholders($fieldNames);
return $this;
}
/**
* @param Storage $storage
* @return $this
*/
public function setStorage(Storage $storage): Document
{
$this->body->setStorage($storage);
return $this;
}
/**
* @param Template $template
* @return $this
*/
public function setTemplate(Template $template): Document
{
$this->template = $template;
return $this;
}
/**
* Returns array of external values.
* If $unique is true - returns values that are not equal to calculated
*
* @param bool $unique
* @return array
*/
protected function getExternalValues(bool $unique = false): array
{
$result = $this->externalValues;
if($unique)
{
$result = [];
foreach($this->externalValues as $placeholder => $value)
{
$this->getValue($placeholder);
if($value != $this->values[$placeholder])
{
if(is_array($this->values[$placeholder]) || $value != htmlspecialcharsbx($this->values[$placeholder]))
{
if(!is_object($value) || class_exists($value))
{
$result[$placeholder] = $value;
}
}
}
}
}
return $result;
}
/**
* @param $userId
* @return boolean
*/
public function hasAccess(int $userId = null): bool
{
if($userId === null)
{
$userId = $this->getUserId();
}
$this->isCheckAccess = true;
$sourceProvider = $this->getProvider();
if($sourceProvider)
{
return DataProviderManager::getInstance()->checkDataProviderAccess($sourceProvider, $userId);
}
return true;
}
/**
* @return TransformerManager
* @throws \Bitrix\Main\LoaderException
*/
protected function getTransformer(): ?TransformerManager
{
if($this->transformer === null && Loader::includeModule('transformer'))
{
$this->transformer = new TransformerManager($this);
}
return $this->transformer;
}
protected function getPullTag(): ?string
{
$transformer = $this->getTransformer();
if($transformer)
{
return $transformer->getPullTag();
}
return null;
}
protected function transform(): Result
{
$transformer = $this->getTransformer();
if($transformer)
{
return $transformer->transform([static::IMAGE, static::PDF]);
}
return (new Result())->addError(new Error(Loc::getMessage('DOCUMENT_TRANSOFMER_MODULE_ERROR'), static::ERROR_NO_TRANSFORMER_MODULE));
}
public function getImageUrl(bool $absolute = false): Uri
{
return new ContentUri(UrlManager::getInstance()->create('documentgenerator.api.document.getimage', ['id' => $this->ID, 'ts' => $this->getUpdateTime()->getTimestamp()], $absolute)->getUri());
}
public function getPdfUrl(bool $absolute = false): Uri
{
return new ContentUri(UrlManager::getInstance()->create('documentgenerator.api.document.getpdf', ['id' => $this->ID, 'ts' => $this->getUpdateTime()->getTimestamp()], $absolute)->getUri());
}
public function getPrintUrl(bool $absolute = false): Uri
{
return new ContentUri(UrlManager::getInstance()->create('documentgenerator.api.document.showpdf', ['id' => $this->ID, 'print' => 'y', 'ts' => $this->getUpdateTime()->getTimestamp()], $absolute)->getUri());
}
public function getDownloadUrl(bool $absolute = false): Uri
{
return new ContentUri(UrlManager::getInstance()->create('documentgenerator.api.document.getfile', ['id' => $this->ID, 'ts' => $this->getUpdateTime()->getTimestamp()], $absolute)->getUri());
}
/**
* @param bool $status
* @return Result
*/
public function enablePublicUrl(bool $status = true): Result
{
$result = new Result();
if(!$this->ID)
{
return $result->addError(new Error('Document is not saved'));
}
$link = ExternalLinkTable::getByDocumentId($this->ID);
if($status)
{
if(!$link)
{
$result = ExternalLinkTable::add([
'HASH' => md5(uniqid($this->ID, true) . \CMain::getServerUniqID()),
'DOCUMENT_ID' => $this->ID,
]);
}
}
elseif($link)
{
$result = ExternalLinkTable::deleteByDocumentId($this->ID);
}
return $result;
}
/**
* @param bool $absolute
* @return Uri|false
*/
public function getPublicUrl(bool $absolute = true): ?Uri
{
$link = ExternalLinkTable::getByDocumentId($this->ID);
if(!$link)
{
return null;
}
if($link)
{
if($absolute)
{
$link = UrlManager::getInstance()->getHostUrl() . $link;
}
return new Uri($link);
}
return null;
}
/**
* @return array
*/
protected function getFieldNames(): array
{
$fieldNames = [];
foreach($this->getDefaultFields() as $placeholder => $field)
{
$fieldNames[$placeholder] = $placeholder;
}
foreach($this->externalFields as $placeholder => $field)
{
$fieldNames[$placeholder] = $placeholder;
}
if(!$this->body)
{
$this->result->addError(new Error('no body'));
}
else
{
$fieldNames = array_merge($this->body->getFieldNames(), $this->fieldNames, $fieldNames);
}
return $fieldNames;
}
/**
* @param bool $isDocxIfNoPdf
* @return int
*/
public function getEmailDiskFile(bool $isDocxIfNoPdf = false): int
{
if($this->PDF_ID > 0)
{
$file = FileTable::getById($this->PDF_ID)->fetch();
if($file)
{
$storage = new $file['STORAGE_TYPE'];
if($storage instanceof Disk)
{
return (int) $file['STORAGE_WHERE'];
}
}
}
if($isDocxIfNoPdf && $this->FILE_ID > 0)
{
$file = FileTable::getById($this->FILE_ID)->fetch();
if($file)
{
$storage = new $file['STORAGE_TYPE'];
if($storage instanceof Disk)
{
return (int) $file['STORAGE_WHERE'];
}
}
}
return 0;
}
/**
* @param string $extension
* @return string
*/
public function getFileName(string $extension = ''): string
{
if($extension === '')
{
$extension = $this->body->getFileExtension();
}
return $this->getTitle().'.'.$extension;
}
/**
* Uploads new externally-formed document
*
* @param Template $template
* @param $value
* @param $title
* @param $number
* @param $fileId
* @param null $pdfId
* @param null $imageId
* @return Result
* @throws \Exception
*/
public static function upload(
Template $template,
$value,
string $title,
string $number,
int $fileId,
int $pdfId = null,
int $imageId = null
): Result
{
$result = new Result();
$fileData = FileTable::getById($fileId);
if(!$fileData)
{
return $result->addError(new Error('Wrong fileId - data not found'));
}
if($pdfId)
{
$fileData = FileTable::getById($pdfId);
if(!$fileData)
{
return $result->addError(new Error('Wrong pdfId - data not found'));
}
}
if($imageId)
{
$fileData = FileTable::getById($imageId);
if(!$fileData)
{
return $result->addError(new Error('Wrong imageId - data not found'));
}
}
$data = [
'ACTIVE' => 'Y',
'TEMPLATE_ID' => $template->ID,
'VALUE' => $value,
'FILE_ID' => $fileId,
'PROVIDER' => $template->getSourceType(),
'IMAGE_ID' => $imageId,
'PDF_ID' => $pdfId,
'UPDATE_TIME' => new DateTime(),
'TITLE' => $title,
'NUMBER' => $number,
'CREATED_BY' => Driver::getInstance()->getUserId(),
'VALUES' => [
CreationMethod::CREATION_METHOD_PLACEHOLDER => CreationMethod::METHOD_REST,
],
];
$result = DocumentTable::add($data);
if($result->isSuccess())
{
$document = static::loadById($result->getId());
EventManager::getInstance()->send(new Event(Driver::MODULE_ID, 'onCreateDocument', ['document' => $document]));
$result = $document->getFile(true, true);
}
return $result;
}
/**
* @param bool $isCheckAccess
* @return Document
*/
public function setIsCheckAccess(bool $isCheckAccess): Document
{
$this->isCheckAccess = $isCheckAccess;
return $this;
}
/**
* @return bool
*/
public function getIsCheckAccess(): bool
{
return ($this->isCheckAccess === true);
}
/**
* @return array
*/
protected function getDefaultFields(): array
{
return [
'DocumentTitle' => [
'TITLE' => Loc::getMessage('DOCUMENT_TITLE_FIELD_NAME'),
'VALUE' => function()
{
return $this->getTitle();
},
'GROUP' => [
Loc::getMessage('DOCUMENT_GROUP_NAME'),
'',
],
'CHAIN' => 'this.DOCUMENT.DOCUMENT_TITLE',
'REQUIRED' => 'Y',
]
];
}
}